Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/make/src/classes/build/tools/tzdb/ZoneOffset.java
32287 views
/*1* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26* This file is available under and governed by the GNU General Public27* License version 2 only, as published by the Free Software Foundation.28* However, the following notice accompanied the original version of this29* file:30*31* Copyright (c) 2007-2012, Stephen Colebourne & Michael Nascimento Santos32*33* All rights reserved.34*35* Redistribution and use in source and binary forms, with or without36* modification, are permitted provided that the following conditions are met:37*38* * Redistributions of source code must retain the above copyright notice,39* this list of conditions and the following disclaimer.40*41* * Redistributions in binary form must reproduce the above copyright notice,42* this list of conditions and the following disclaimer in the documentation43* and/or other materials provided with the distribution.44*45* * Neither the name of JSR-310 nor the names of its contributors46* may be used to endorse or promote products derived from this software47* without specific prior written permission.48*49* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS50* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT51* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR52* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR53* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,54* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,55* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR56* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF57* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING58* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS59* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.60*/61package build.tools.tzdb;6263import java.util.Objects;64import java.util.concurrent.ConcurrentHashMap;65import java.util.concurrent.ConcurrentMap;6667/**68* A time-zone offset from Greenwich/UTC, such as {@code +02:00}.69* <p>70* A time-zone offset is the period of time that a time-zone differs from Greenwich/UTC.71* This is usually a fixed number of hours and minutes.72*73* @since 1.874*/75final class ZoneOffset implements Comparable<ZoneOffset> {7677/** Cache of time-zone offset by offset in seconds. */78private static final ConcurrentMap<Integer, ZoneOffset> SECONDS_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4);79/** Cache of time-zone offset by ID. */80private static final ConcurrentMap<String, ZoneOffset> ID_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4);8182/**83* The number of seconds per hour.84*/85private static final int SECONDS_PER_HOUR = 60 * 60;86/**87* The number of seconds per minute.88*/89private static final int SECONDS_PER_MINUTE = 60;90/**91* The number of minutes per hour.92*/93private static final int MINUTES_PER_HOUR = 60;94/**95* The abs maximum seconds.96*/97private static final int MAX_SECONDS = 18 * SECONDS_PER_HOUR;98/**99* Serialization version.100*/101private static final long serialVersionUID = 2357656521762053153L;102103/**104* The time-zone offset for UTC, with an ID of 'Z'.105*/106public static final ZoneOffset UTC = ZoneOffset.ofTotalSeconds(0);107/**108* Constant for the maximum supported offset.109*/110public static final ZoneOffset MIN = ZoneOffset.ofTotalSeconds(-MAX_SECONDS);111/**112* Constant for the maximum supported offset.113*/114public static final ZoneOffset MAX = ZoneOffset.ofTotalSeconds(MAX_SECONDS);115116/**117* The total offset in seconds.118*/119private final int totalSeconds;120/**121* The string form of the time-zone offset.122*/123private final transient String id;124125//-----------------------------------------------------------------------126/**127* Obtains an instance of {@code ZoneOffset} using the ID.128* <p>129* This method parses the string ID of a {@code ZoneOffset} to130* return an instance. The parsing accepts all the formats generated by131* {@link #getId()}, plus some additional formats:132* <p><ul>133* <li>{@code Z} - for UTC134* <li>{@code +h}135* <li>{@code +hh}136* <li>{@code +hh:mm}137* <li>{@code -hh:mm}138* <li>{@code +hhmm}139* <li>{@code -hhmm}140* <li>{@code +hh:mm:ss}141* <li>{@code -hh:mm:ss}142* <li>{@code +hhmmss}143* <li>{@code -hhmmss}144* </ul><p>145* Note that ± means either the plus or minus symbol.146* <p>147* The ID of the returned offset will be normalized to one of the formats148* described by {@link #getId()}.149* <p>150* The maximum supported range is from +18:00 to -18:00 inclusive.151*152* @param offsetId the offset ID, not null153* @return the zone-offset, not null154* @throws DateTimeException if the offset ID is invalid155*/156@SuppressWarnings("fallthrough")157public static ZoneOffset of(String offsetId) {158Objects.requireNonNull(offsetId, "offsetId");159// "Z" is always in the cache160ZoneOffset offset = ID_CACHE.get(offsetId);161if (offset != null) {162return offset;163}164165// parse - +h, +hh, +hhmm, +hh:mm, +hhmmss, +hh:mm:ss166final int hours, minutes, seconds;167switch (offsetId.length()) {168case 2:169offsetId = offsetId.charAt(0) + "0" + offsetId.charAt(1); // fallthru170case 3:171hours = parseNumber(offsetId, 1, false);172minutes = 0;173seconds = 0;174break;175case 5:176hours = parseNumber(offsetId, 1, false);177minutes = parseNumber(offsetId, 3, false);178seconds = 0;179break;180case 6:181hours = parseNumber(offsetId, 1, false);182minutes = parseNumber(offsetId, 4, true);183seconds = 0;184break;185case 7:186hours = parseNumber(offsetId, 1, false);187minutes = parseNumber(offsetId, 3, false);188seconds = parseNumber(offsetId, 5, false);189break;190case 9:191hours = parseNumber(offsetId, 1, false);192minutes = parseNumber(offsetId, 4, true);193seconds = parseNumber(offsetId, 7, true);194break;195default:196throw new DateTimeException("Zone offset ID '" + offsetId + "' is invalid");197}198char first = offsetId.charAt(0);199if (first != '+' && first != '-') {200throw new DateTimeException("Zone offset ID '" + offsetId + "' is invalid: Plus/minus not found when expected");201}202if (first == '-') {203return ofHoursMinutesSeconds(-hours, -minutes, -seconds);204} else {205return ofHoursMinutesSeconds(hours, minutes, seconds);206}207}208209/**210* Parse a two digit zero-prefixed number.211*212* @param offsetId the offset ID, not null213* @param pos the position to parse, valid214* @param precededByColon should this number be prefixed by a precededByColon215* @return the parsed number, from 0 to 99216*/217private static int parseNumber(CharSequence offsetId, int pos, boolean precededByColon) {218if (precededByColon && offsetId.charAt(pos - 1) != ':') {219throw new DateTimeException("Zone offset ID '" + offsetId + "' is invalid: Colon not found when expected");220}221char ch1 = offsetId.charAt(pos);222char ch2 = offsetId.charAt(pos + 1);223if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') {224throw new DateTimeException("Zone offset ID '" + offsetId + "' is invalid: Non numeric characters found");225}226return (ch1 - 48) * 10 + (ch2 - 48);227}228229//-----------------------------------------------------------------------230/**231* Obtains an instance of {@code ZoneOffset} using an offset in hours.232*233* @param hours the time-zone offset in hours, from -18 to +18234* @return the zone-offset, not null235* @throws DateTimeException if the offset is not in the required range236*/237public static ZoneOffset ofHours(int hours) {238return ofHoursMinutesSeconds(hours, 0, 0);239}240241/**242* Obtains an instance of {@code ZoneOffset} using an offset in243* hours and minutes.244* <p>245* The sign of the hours and minutes components must match.246* Thus, if the hours is negative, the minutes must be negative or zero.247* If the hours is zero, the minutes may be positive, negative or zero.248*249* @param hours the time-zone offset in hours, from -18 to +18250* @param minutes the time-zone offset in minutes, from 0 to ±59, sign matches hours251* @return the zone-offset, not null252* @throws DateTimeException if the offset is not in the required range253*/254public static ZoneOffset ofHoursMinutes(int hours, int minutes) {255return ofHoursMinutesSeconds(hours, minutes, 0);256}257258/**259* Obtains an instance of {@code ZoneOffset} using an offset in260* hours, minutes and seconds.261* <p>262* The sign of the hours, minutes and seconds components must match.263* Thus, if the hours is negative, the minutes and seconds must be negative or zero.264*265* @param hours the time-zone offset in hours, from -18 to +18266* @param minutes the time-zone offset in minutes, from 0 to ±59, sign matches hours and seconds267* @param seconds the time-zone offset in seconds, from 0 to ±59, sign matches hours and minutes268* @return the zone-offset, not null269* @throws DateTimeException if the offset is not in the required range270*/271public static ZoneOffset ofHoursMinutesSeconds(int hours, int minutes, int seconds) {272validate(hours, minutes, seconds);273int totalSeconds = totalSeconds(hours, minutes, seconds);274return ofTotalSeconds(totalSeconds);275}276277/**278* Validates the offset fields.279*280* @param hours the time-zone offset in hours, from -18 to +18281* @param minutes the time-zone offset in minutes, from 0 to ±59282* @param seconds the time-zone offset in seconds, from 0 to ±59283* @throws DateTimeException if the offset is not in the required range284*/285private static void validate(int hours, int minutes, int seconds) {286if (hours < -18 || hours > 18) {287throw new DateTimeException("Zone offset hours not in valid range: value " + hours +288" is not in the range -18 to 18");289}290if (hours > 0) {291if (minutes < 0 || seconds < 0) {292throw new DateTimeException("Zone offset minutes and seconds must be positive because hours is positive");293}294} else if (hours < 0) {295if (minutes > 0 || seconds > 0) {296throw new DateTimeException("Zone offset minutes and seconds must be negative because hours is negative");297}298} else if ((minutes > 0 && seconds < 0) || (minutes < 0 && seconds > 0)) {299throw new DateTimeException("Zone offset minutes and seconds must have the same sign");300}301if (Math.abs(minutes) > 59) {302throw new DateTimeException("Zone offset minutes not in valid range: abs(value) " +303Math.abs(minutes) + " is not in the range 0 to 59");304}305if (Math.abs(seconds) > 59) {306throw new DateTimeException("Zone offset seconds not in valid range: abs(value) " +307Math.abs(seconds) + " is not in the range 0 to 59");308}309if (Math.abs(hours) == 18 && (Math.abs(minutes) > 0 || Math.abs(seconds) > 0)) {310throw new DateTimeException("Zone offset not in valid range: -18:00 to +18:00");311}312}313314/**315* Calculates the total offset in seconds.316*317* @param hours the time-zone offset in hours, from -18 to +18318* @param minutes the time-zone offset in minutes, from 0 to ±59, sign matches hours and seconds319* @param seconds the time-zone offset in seconds, from 0 to ±59, sign matches hours and minutes320* @return the total in seconds321*/322private static int totalSeconds(int hours, int minutes, int seconds) {323return hours * SECONDS_PER_HOUR + minutes * SECONDS_PER_MINUTE + seconds;324}325326//-----------------------------------------------------------------------327/**328* Obtains an instance of {@code ZoneOffset} specifying the total offset in seconds329* <p>330* The offset must be in the range {@code -18:00} to {@code +18:00}, which corresponds to -64800 to +64800.331*332* @param totalSeconds the total time-zone offset in seconds, from -64800 to +64800333* @return the ZoneOffset, not null334* @throws DateTimeException if the offset is not in the required range335*/336public static ZoneOffset ofTotalSeconds(int totalSeconds) {337if (Math.abs(totalSeconds) > MAX_SECONDS) {338throw new DateTimeException("Zone offset not in valid range: -18:00 to +18:00");339}340if (totalSeconds % (15 * SECONDS_PER_MINUTE) == 0) {341Integer totalSecs = totalSeconds;342ZoneOffset result = SECONDS_CACHE.get(totalSecs);343if (result == null) {344result = new ZoneOffset(totalSeconds);345SECONDS_CACHE.putIfAbsent(totalSecs, result);346result = SECONDS_CACHE.get(totalSecs);347ID_CACHE.putIfAbsent(result.getId(), result);348}349return result;350} else {351return new ZoneOffset(totalSeconds);352}353}354355/**356* Constructor.357*358* @param totalSeconds the total time-zone offset in seconds, from -64800 to +64800359*/360private ZoneOffset(int totalSeconds) {361super();362this.totalSeconds = totalSeconds;363id = buildId(totalSeconds);364}365366private static String buildId(int totalSeconds) {367if (totalSeconds == 0) {368return "Z";369} else {370int absTotalSeconds = Math.abs(totalSeconds);371StringBuilder buf = new StringBuilder();372int absHours = absTotalSeconds / SECONDS_PER_HOUR;373int absMinutes = (absTotalSeconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR;374buf.append(totalSeconds < 0 ? "-" : "+")375.append(absHours < 10 ? "0" : "").append(absHours)376.append(absMinutes < 10 ? ":0" : ":").append(absMinutes);377int absSeconds = absTotalSeconds % SECONDS_PER_MINUTE;378if (absSeconds != 0) {379buf.append(absSeconds < 10 ? ":0" : ":").append(absSeconds);380}381return buf.toString();382}383}384385/**386* Gets the total zone offset in seconds.387* <p>388* This is the primary way to access the offset amount.389* It returns the total of the hours, minutes and seconds fields as a390* single offset that can be added to a time.391*392* @return the total zone offset amount in seconds393*/394public int getTotalSeconds() {395return totalSeconds;396}397398/**399* Gets the normalized zone offset ID.400* <p>401* The ID is minor variation to the standard ISO-8601 formatted string402* for the offset. There are three formats:403* <p><ul>404* <li>{@code Z} - for UTC (ISO-8601)405* <li>{@code +hh:mm} or {@code -hh:mm} - if the seconds are zero (ISO-8601)406* <li>{@code +hh:mm:ss} or {@code -hh:mm:ss} - if the seconds are non-zero (not ISO-8601)407* </ul><p>408*409* @return the zone offset ID, not null410*/411public String getId() {412return id;413}414415/**416* Compares this offset to another offset in descending order.417* <p>418* The offsets are compared in the order that they occur for the same time419* of day around the world. Thus, an offset of {@code +10:00} comes before an420* offset of {@code +09:00} and so on down to {@code -18:00}.421* <p>422* The comparison is "consistent with equals", as defined by {@link Comparable}.423*424* @param other the other date to compare to, not null425* @return the comparator value, negative if less, postive if greater426* @throws NullPointerException if {@code other} is null427*/428@Override429public int compareTo(ZoneOffset other) {430return other.totalSeconds - totalSeconds;431}432433/**434* Checks if this offset is equal to another offset.435* <p>436* The comparison is based on the amount of the offset in seconds.437* This is equivalent to a comparison by ID.438*439* @param obj the object to check, null returns false440* @return true if this is equal to the other offset441*/442@Override443public boolean equals(Object obj) {444if (this == obj) {445return true;446}447if (obj instanceof ZoneOffset) {448return totalSeconds == ((ZoneOffset) obj).totalSeconds;449}450return false;451}452453/**454* A hash code for this offset.455*456* @return a suitable hash code457*/458@Override459public int hashCode() {460return totalSeconds;461}462463/**464* Outputs this offset as a {@code String}, using the normalized ID.465*466* @return a string representation of this offset, not null467*/468@Override469public String toString() {470return id;471}472473}474475476