001package biweekly.util; 002 003import java.util.regex.Matcher; 004import java.util.regex.Pattern; 005 006/* 007 Copyright (c) 2013-2015, Michael Angstadt 008 All rights reserved. 009 010 Redistribution and use in source and binary forms, with or without 011 modification, are permitted provided that the following conditions are met: 012 013 1. Redistributions of source code must retain the above copyright notice, this 014 list of conditions and the following disclaimer. 015 2. Redistributions in binary form must reproduce the above copyright notice, 016 this list of conditions and the following disclaimer in the documentation 017 and/or other materials provided with the distribution. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 026 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030 031/** 032 * Represents a UTC offset. 033 * @author Michael Angstadt 034 */ 035public final class UtcOffset { 036 private final int hour; 037 private final int minute; 038 039 /** 040 * Creates a new UTC offset. 041 * @param hour the hour component (may be negative) 042 * @param minute the minute component (must be between 0 and 59) 043 */ 044 public UtcOffset(int hour, int minute) { 045 this.hour = hour; 046 this.minute = minute; 047 } 048 049 /** 050 * Creates a new UTC offset. 051 * @param millis the offset in milliseconds 052 */ 053 public UtcOffset(int millis) { 054 hour = millis / 1000 / 60 / 60; 055 millis -= hour * 1000 * 60 * 60; 056 minute = Math.abs(millis / 1000 / 60); 057 } 058 059 /** 060 * Parses a UTC offset from a string. 061 * @param text the text to parse (e.g. "-0500") 062 * @return the parsed UTC offset 063 * @throws IllegalArgumentException if the text cannot be parsed 064 */ 065 public static UtcOffset parse(String text) { 066 Pattern timeZoneRegex = Pattern.compile("^([-\\+])?(\\d{1,2})(:?(\\d{2}))?(:?(\\d{2}))?$"); 067 Matcher m = timeZoneRegex.matcher(text); 068 069 if (!m.find()) { 070 throw new IllegalArgumentException("Offset string is not in ISO8610 format: " + text); 071 } 072 073 String sign = m.group(1); 074 boolean positive; 075 if ("-".equals(sign)) { 076 positive = false; 077 } else { 078 positive = true; 079 } 080 081 String hourStr = m.group(2); 082 int hourOffset = Integer.parseInt(hourStr); 083 if (!positive) { 084 hourOffset *= -1; 085 } 086 087 String minuteStr = m.group(4); 088 int minuteOffset = (minuteStr == null) ? 0 : Integer.parseInt(minuteStr); 089 090 return new UtcOffset(hourOffset, minuteOffset); 091 } 092 093 /** 094 * Gets the hour component. 095 * @return the hour component 096 */ 097 public int getHour() { 098 return hour; 099 } 100 101 /** 102 * Gets the minute component. 103 * @return the minute component 104 */ 105 public int getMinute() { 106 return minute; 107 } 108 109 /** 110 * Converts this offset to its ISO string representation using "basic" 111 * format. 112 * @return the ISO string representation (e.g. "-0500") 113 */ 114 @Override 115 public String toString() { 116 return toString(false); 117 } 118 119 /** 120 * Converts this offset to its ISO string representation. 121 * @param extended true to use extended format (e.g. "-05:00"), false to use 122 * basic format (e.g. "-0500") 123 * @return the ISO string representation 124 */ 125 public String toString(boolean extended) { 126 StringBuilder sb = new StringBuilder(); 127 128 boolean positive = hour >= 0; 129 sb.append(positive ? '+' : '-'); 130 131 int hour = Math.abs(this.hour); 132 if (hour < 10) { 133 sb.append('0'); 134 } 135 sb.append(hour); 136 137 if (extended) { 138 sb.append(':'); 139 } 140 141 if (minute < 10) { 142 sb.append('0'); 143 } 144 sb.append(minute); 145 146 return sb.toString(); 147 } 148 149 /** 150 * Converts the offset to milliseconds. 151 * @return the offset in milliseconds 152 */ 153 public int toMillis() { 154 int millis = Math.abs(hour) * 60 * 60 * 1000; 155 millis += minute * 60 * 1000; 156 return millis * ((hour < 0) ? -1 : 1); 157 } 158 159 @Override 160 public int hashCode() { 161 final int prime = 31; 162 int result = 1; 163 result = prime * result + hour; 164 result = prime * result + minute; 165 return result; 166 } 167 168 @Override 169 public boolean equals(Object obj) { 170 if (this == obj) 171 return true; 172 if (obj == null) 173 return false; 174 if (getClass() != obj.getClass()) 175 return false; 176 UtcOffset other = (UtcOffset) obj; 177 if (hour != other.hour) 178 return false; 179 if (minute != other.minute) 180 return false; 181 return true; 182 } 183}