001package ezvcard.util; 002 003import java.util.TimeZone; 004 005import ezvcard.Messages; 006 007/* 008 Copyright (c) 2012-2018, Michael Angstadt 009 All rights reserved. 010 011 Redistribution and use in source and binary forms, with or without 012 modification, are permitted provided that the following conditions are met: 013 014 1. Redistributions of source code must retain the above copyright notice, this 015 list of conditions and the following disclaimer. 016 2. Redistributions in binary form must reproduce the above copyright notice, 017 this list of conditions and the following disclaimer in the documentation 018 and/or other materials provided with the distribution. 019 020 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 021 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 022 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 023 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 024 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 025 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 026 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 027 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 028 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 029 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 030 */ 031 032/** 033 * Represents a UTC offset. 034 * @author Michael Angstadt 035 */ 036public final class UtcOffset { 037 private final long millis; 038 039 /** 040 * @param positive true if the offset is positive, false if it is negative 041 * @param hour the hour component of the offset (the sign of this integer is 042 * ignored) 043 * @param minute the minute component of the offset (the sign of this 044 * integer is ignored) 045 */ 046 public UtcOffset(boolean positive, int hour, int minute) { 047 /* 048 * Note: The (hour, minute) constructor was removed because it could not 049 * handle timezones such as "-0030". 050 */ 051 int sign = positive ? 1 : -1; 052 hour = Math.abs(hour); 053 minute = Math.abs(minute); 054 055 millis = sign * (hoursToMillis(hour) + minutesToMillis(minute)); 056 } 057 058 /** 059 * @param millis the offset in milliseconds 060 */ 061 public UtcOffset(long millis) { 062 this.millis = millis; 063 } 064 065 /** 066 * Parses a UTC offset from a string. 067 * @param text the text to parse (e.g. "-0500") 068 * @return the parsed UTC offset 069 * @throws IllegalArgumentException if the text cannot be parsed 070 */ 071 public static UtcOffset parse(String text) { 072 int i = 0; 073 char sign = text.charAt(i); 074 boolean positive = true; 075 if (sign == '-') { 076 positive = false; 077 i++; 078 } else if (sign == '+') { 079 i++; 080 } 081 082 int maxLength = i + 4; 083 int colon = text.indexOf(':', i); 084 if (colon >= 0) { 085 maxLength++; 086 } 087 if (text.length() > maxLength) { 088 throw Messages.INSTANCE.getIllegalArgumentException(40, text); 089 } 090 091 String hourStr, minuteStr = null; 092 if (colon < 0) { 093 hourStr = text.substring(i); 094 int minutePos = hourStr.length() - 2; 095 if (minutePos > 0) { 096 minuteStr = hourStr.substring(minutePos); 097 hourStr = hourStr.substring(0, minutePos); 098 } 099 } else { 100 hourStr = text.substring(i, colon); 101 if (colon < text.length() - 1) { 102 minuteStr = text.substring(colon + 1); 103 } 104 } 105 106 int hour, minute; 107 try { 108 hour = Integer.parseInt(hourStr); 109 minute = (minuteStr == null) ? 0 : Integer.parseInt(minuteStr); 110 } catch (NumberFormatException e) { 111 throw Messages.INSTANCE.getIllegalArgumentException(40, text); 112 } 113 114 return new UtcOffset(positive, hour, minute); 115 } 116 117 /** 118 * Creates a UTC offset from a {@link TimeZone} object. 119 * @param timezone the timezone 120 * @return the UTC offset 121 */ 122 public static UtcOffset parse(TimeZone timezone) { 123 long offset = timezone.getOffset(System.currentTimeMillis()); 124 return new UtcOffset(offset); 125 } 126 127 /** 128 * Gets the offset in milliseconds. 129 * @return the offset in milliseconds 130 */ 131 public long getMillis() { 132 return millis; 133 } 134 135 /** 136 * Converts this offset to its ISO string representation using "basic" 137 * format. 138 * @return the ISO string representation (e.g. "-0500") 139 */ 140 @Override 141 public String toString() { 142 return toString(false); 143 } 144 145 /** 146 * Converts this offset to its ISO string representation. 147 * @param extended true to use extended format (e.g. "-05:00"), false to use 148 * basic format (e.g. "-0500") 149 * @return the ISO string representation 150 */ 151 public String toString(boolean extended) { 152 StringBuilder sb = new StringBuilder(); 153 154 boolean positive = (millis >= 0); 155 long hour = Math.abs(millisToHours(millis)); 156 long minute = Math.abs(millisToMinutes(millis)); 157 158 sb.append(positive ? '+' : '-'); 159 160 if (hour < 10) { 161 sb.append('0'); 162 } 163 sb.append(hour); 164 165 if (extended) { 166 sb.append(':'); 167 } 168 169 if (minute < 10) { 170 sb.append('0'); 171 } 172 sb.append(minute); 173 174 return sb.toString(); 175 } 176 177 @Override 178 public int hashCode() { 179 final int prime = 31; 180 int result = 1; 181 result = prime * result + (int) (millis ^ (millis >>> 32)); 182 return result; 183 } 184 185 @Override 186 public boolean equals(Object obj) { 187 if (this == obj) return true; 188 if (obj == null) return false; 189 if (getClass() != obj.getClass()) return false; 190 UtcOffset other = (UtcOffset) obj; 191 if (millis != other.millis) return false; 192 return true; 193 } 194 195 private static long hoursToMillis(long hours) { 196 return hours * 60 * 60 * 1000; 197 } 198 199 private static long minutesToMillis(long minutes) { 200 return minutes * 60 * 1000; 201 } 202 203 private static long millisToHours(long millis) { 204 return millis / 1000 / 60 / 60; 205 } 206 207 private static long millisToMinutes(long millis) { 208 return (millis / 1000 / 60) % 60; 209 } 210}