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 }