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    }