001 package ezvcard.util;
002
003 import java.util.TimeZone;
004 import java.util.regex.Matcher;
005 import java.util.regex.Pattern;
006
007 /*
008 Copyright (c) 2013, 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 */
036 public final class UtcOffset {
037 private final int hour;
038 private final int minute;
039
040 /**
041 * Creates a new UTC offset.
042 * @param hour the hour component (may be negative)
043 * @param minute the minute component (must be between 0 and 59)
044 */
045 public UtcOffset(int hour, int minute) {
046 this.hour = hour;
047 this.minute = minute;
048 }
049
050 /**
051 * Parses a UTC offset from a string.
052 * @param text the text to parse (e.g. "-0500")
053 * @return the parsed UTC offset
054 * @throws IllegalArgumentException if the text cannot be parsed
055 */
056 public static UtcOffset parse(String text) {
057 Pattern timeZoneRegex = Pattern.compile("^([-\\+])?(\\d{1,2})(:?(\\d{2}))?$");
058 Matcher m = timeZoneRegex.matcher(text);
059
060 if (!m.find()) {
061 throw new IllegalArgumentException("Offset string is not in ISO8610 format: " + text);
062 }
063
064 String sign = m.group(1);
065 boolean positive;
066 if ("-".equals(sign)) {
067 positive = false;
068 } else {
069 positive = true;
070 }
071
072 String hourStr = m.group(2);
073 int hourOffset = Integer.parseInt(hourStr);
074 if (!positive) {
075 hourOffset *= -1;
076 }
077
078 String minuteStr = m.group(4);
079 int minuteOffset = (minuteStr == null) ? 0 : Integer.parseInt(minuteStr);
080
081 return new UtcOffset(hourOffset, minuteOffset);
082 }
083
084 /**
085 * Creates a UTC offset from a {@link TimeZone} object.
086 * @param timezone the timezone
087 * @return the UTC offset
088 */
089 public static UtcOffset parse(TimeZone timezone) {
090 long offsetMs = timezone.getOffset(System.currentTimeMillis());
091 int hours = (int) (offsetMs / 1000 / 60 / 60);
092 int minutes = (int) ((offsetMs / 1000 / 60) % 60);
093 if (minutes < 0) {
094 minutes *= -1;
095 }
096 return new UtcOffset(hours, minutes);
097 }
098
099 /**
100 * Gets the hour component.
101 * @return the hour component
102 */
103 public int getHour() {
104 return hour;
105 }
106
107 /**
108 * Gets the minute component.
109 * @return the minute component
110 */
111 public int getMinute() {
112 return minute;
113 }
114
115 /**
116 * Converts this offset to its ISO string representation using "basic"
117 * format.
118 * @return the ISO string representation (e.g. "-0500")
119 */
120 @Override
121 public String toString() {
122 return toString(false);
123 }
124
125 /**
126 * Converts this offset to its ISO string representation.
127 * @param extended true to use extended format (e.g. "-05:00"), false to use
128 * basic format (e.g. "-0500")
129 * @return the ISO string representation
130 */
131 public String toString(boolean extended) {
132 StringBuilder sb = new StringBuilder();
133
134 boolean positive = hour >= 0;
135 sb.append(positive ? '+' : '-');
136
137 int hour = Math.abs(this.hour);
138 if (hour < 10) {
139 sb.append('0');
140 }
141 sb.append(hour);
142
143 if (extended) {
144 sb.append(':');
145 }
146
147 if (minute < 10) {
148 sb.append('0');
149 }
150 sb.append(minute);
151
152 return sb.toString();
153 }
154
155 @Override
156 public int hashCode() {
157 final int prime = 31;
158 int result = 1;
159 result = prime * result + hour;
160 result = prime * result + minute;
161 return result;
162 }
163
164 @Override
165 public boolean equals(Object obj) {
166 if (this == obj)
167 return true;
168 if (obj == null)
169 return false;
170 if (getClass() != obj.getClass())
171 return false;
172 UtcOffset other = (UtcOffset) obj;
173 if (hour != other.hour)
174 return false;
175 if (minute != other.minute)
176 return false;
177 return true;
178 }
179 }