8042369: Remove duplicated java.time classes in build.tools.tzdb
Removed duplicated classes in builder Reviewed-by: rriggs
This commit is contained in:
parent
61ad72b44a
commit
565ab8b571
jdk/make/src/classes/build/tools/tzdb
@ -1,180 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of JSR-310 nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package build.tools.tzdb;
|
||||
|
||||
/**
|
||||
* A standard set of date/time fields.
|
||||
*
|
||||
* @since 1.8
|
||||
*/
|
||||
enum ChronoField {
|
||||
|
||||
/**
|
||||
* The second-of-minute.
|
||||
* <p>
|
||||
* This counts the second within the minute, from 0 to 59.
|
||||
* This field has the same meaning for all calendar systems.
|
||||
*/
|
||||
SECOND_OF_MINUTE("SecondOfMinute", 0, 59),
|
||||
|
||||
/**
|
||||
* The second-of-day.
|
||||
* <p>
|
||||
* This counts the second within the day, from 0 to (24 * 60 * 60) - 1.
|
||||
* This field has the same meaning for all calendar systems.
|
||||
*/
|
||||
SECOND_OF_DAY("SecondOfDay", 0, 86400 - 1),
|
||||
|
||||
/**
|
||||
* The minute-of-hour.
|
||||
* <p>
|
||||
* This counts the minute within the hour, from 0 to 59.
|
||||
* This field has the same meaning for all calendar systems.
|
||||
*/
|
||||
MINUTE_OF_HOUR("MinuteOfHour", 0, 59),
|
||||
|
||||
/**
|
||||
* The hour-of-day.
|
||||
* <p>
|
||||
* This counts the hour within the day, from 0 to 23.
|
||||
* This is the hour that would be observed on a standard 24-hour digital clock.
|
||||
* This field has the same meaning for all calendar systems.
|
||||
*/
|
||||
HOUR_OF_DAY("HourOfDay", 0, 23),
|
||||
|
||||
|
||||
/**
|
||||
* The day-of-month.
|
||||
* <p>
|
||||
* This represents the concept of the day within the month.
|
||||
* In the default ISO calendar system, this has values from 1 to 31 in most months.
|
||||
* April, June, September, November have days from 1 to 30, while February has days
|
||||
* from 1 to 28, or 29 in a leap year.
|
||||
* <p>
|
||||
* Non-ISO calendar systems should implement this field using the most recognized
|
||||
* day-of-month values for users of the calendar system.
|
||||
* Normally, this is a count of days from 1 to the length of the month.
|
||||
*/
|
||||
DAY_OF_MONTH("DayOfMonth", 1, 31),
|
||||
|
||||
/**
|
||||
* The month-of-year, such as March.
|
||||
* <p>
|
||||
* This represents the concept of the month within the year.
|
||||
* In the default ISO calendar system, this has values from January (1) to December (12).
|
||||
* <p>
|
||||
* Non-ISO calendar systems should implement this field using the most recognized
|
||||
* month-of-year values for users of the calendar system.
|
||||
* Normally, this is a count of months starting from 1.
|
||||
*/
|
||||
MONTH_OF_YEAR("MonthOfYear", 1, 12),
|
||||
|
||||
/**
|
||||
* The proleptic year, such as 2012.
|
||||
* <p>
|
||||
* This represents the concept of the year, counting sequentially and using negative numbers.
|
||||
* The proleptic year is not interpreted in terms of the era.
|
||||
* See {@link #YEAR_OF_ERA} for an example showing the mapping from proleptic year to year-of-era.
|
||||
* <p>
|
||||
* The standard mental model for a date is based on three concepts - year, month and day.
|
||||
* These map onto the {@code YEAR}, {@code MONTH_OF_YEAR} and {@code DAY_OF_MONTH} fields.
|
||||
* Note that there is no reference to eras.
|
||||
* The full model for a date requires four concepts - era, year, month and day. These map onto
|
||||
* the {@code ERA}, {@code YEAR_OF_ERA}, {@code MONTH_OF_YEAR} and {@code DAY_OF_MONTH} fields.
|
||||
* Whether this field or {@code YEAR_OF_ERA} is used depends on which mental model is being used.
|
||||
* See {@link ChronoLocalDate} for more discussion on this topic.
|
||||
* <p>
|
||||
* Non-ISO calendar systems should implement this field as follows.
|
||||
* If the calendar system has only two eras, before and after a fixed date, then the
|
||||
* proleptic-year value must be the same as the year-of-era value for the later era,
|
||||
* and increasingly negative for the earlier era.
|
||||
* If the calendar system has more than two eras, then the proleptic-year value may be
|
||||
* defined with any appropriate value, although defining it to be the same as ISO may be
|
||||
* the best option.
|
||||
*/
|
||||
YEAR("Year", -999_999_999, 999_999_999);
|
||||
|
||||
private final String name;
|
||||
private final int min;
|
||||
private final int max;
|
||||
|
||||
private ChronoField(String name, int min, int max) {
|
||||
this.name = name;
|
||||
this.min= min;
|
||||
this.max= max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified value is valid for this field.
|
||||
* <p>
|
||||
*
|
||||
* @param value the value to check
|
||||
* @return the value that was passed in
|
||||
*/
|
||||
public int checkValidValue(int value) {
|
||||
if (value >= min && value <= max) {
|
||||
return value;
|
||||
}
|
||||
throw new DateTimeException("Invalid value for " + name + " value: " + value);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is available under and governed by the GNU General Public
|
||||
* License version 2 only, as published by the Free Software Foundation.
|
||||
* However, the following notice accompanied the original version of this
|
||||
* file:
|
||||
*
|
||||
* Copyright (c) 2008-2012, Stephen Colebourne & Michael Nascimento Santos
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of JSR-310 nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package build.tools.tzdb;
|
||||
|
||||
/**
|
||||
* Exception used to indicate a problem while calculating a date-time.
|
||||
* <p>
|
||||
* This exception is used to indicate problems with creating, querying
|
||||
* and manipulating date-time objects.
|
||||
*
|
||||
* @since 1.8
|
||||
*/
|
||||
class DateTimeException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Serialization version.
|
||||
*/
|
||||
private static final long serialVersionUID = -1632418723876261839L;
|
||||
|
||||
/**
|
||||
* Constructs a new date-time exception with the specified message.
|
||||
*
|
||||
* @param message the message to use for this exception, may be null
|
||||
*/
|
||||
public DateTimeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new date-time exception with the specified message and cause.
|
||||
*
|
||||
* @param message the message to use for this exception, may be null
|
||||
* @param cause the cause of the exception, may be null
|
||||
*/
|
||||
public DateTimeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
@ -1,363 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is available under and governed by the GNU General Public
|
||||
* License version 2 only, as published by the Free Software Foundation.
|
||||
* However, the following notice accompanied the original version of this
|
||||
* file:
|
||||
*
|
||||
* Copyright (c) 2007-2012, Stephen Colebourne & Michael Nascimento Santos
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of JSR-310 nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package build.tools.tzdb;
|
||||
|
||||
import static build.tools.tzdb.Utils.*;
|
||||
import static build.tools.tzdb.LocalTime.SECONDS_PER_DAY;
|
||||
import static build.tools.tzdb.ChronoField.DAY_OF_MONTH;
|
||||
import static build.tools.tzdb.ChronoField.MONTH_OF_YEAR;
|
||||
import static build.tools.tzdb.ChronoField.YEAR;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A date without a time-zone in the ISO-8601 calendar system,
|
||||
* such as {@code 2007-12-03}.
|
||||
*
|
||||
* @since 1.8
|
||||
*/
|
||||
final class LocalDate {
|
||||
|
||||
/**
|
||||
* The minimum supported {@code LocalDate}, '-999999999-01-01'.
|
||||
* This could be used by an application as a "far past" date.
|
||||
*/
|
||||
public static final LocalDate MIN = new LocalDate(YEAR_MIN_VALUE, 1, 1);
|
||||
/**
|
||||
* The maximum supported {@code LocalDate}, '+999999999-12-31'.
|
||||
* This could be used by an application as a "far future" date.
|
||||
*/
|
||||
public static final LocalDate MAX = new LocalDate(YEAR_MAX_VALUE, 12, 31);
|
||||
|
||||
/**
|
||||
* The number of days in a 400 year cycle.
|
||||
*/
|
||||
private static final int DAYS_PER_CYCLE = 146097;
|
||||
/**
|
||||
* The number of days from year zero to year 1970.
|
||||
* There are five 400 year cycles from year zero to 2000.
|
||||
* There are 7 leap years from 1970 to 2000.
|
||||
*/
|
||||
static final long DAYS_0000_TO_1970 = (DAYS_PER_CYCLE * 5L) - (30L * 365L + 7L);
|
||||
|
||||
/**
|
||||
* The year.
|
||||
*/
|
||||
private final int year;
|
||||
/**
|
||||
* The month-of-year.
|
||||
*/
|
||||
private final short month;
|
||||
/**
|
||||
* The day-of-month.
|
||||
*/
|
||||
private final short day;
|
||||
|
||||
/**
|
||||
* Obtains an instance of {@code LocalDate} from a year, month and day.
|
||||
* <p>
|
||||
* The day must be valid for the year and month, otherwise an exception will be thrown.
|
||||
*
|
||||
* @param year the year to represent, from MIN_YEAR to MAX_YEAR
|
||||
* @param month the month-of-year to represent, from 1 (January) to 12 (December)
|
||||
* @param dayOfMonth the day-of-month to represent, from 1 to 31
|
||||
* @return the local date, not null
|
||||
* @throws DateTimeException if the value of any field is out of range
|
||||
* @throws DateTimeException if the day-of-month is invalid for the month-year
|
||||
*/
|
||||
public static LocalDate of(int year, int month, int dayOfMonth) {
|
||||
YEAR.checkValidValue(year);
|
||||
MONTH_OF_YEAR.checkValidValue(month);
|
||||
DAY_OF_MONTH.checkValidValue(dayOfMonth);
|
||||
if (dayOfMonth > 28 && dayOfMonth > lengthOfMonth(month, isLeapYear(year))) {
|
||||
if (dayOfMonth == 29) {
|
||||
throw new DateTimeException("Invalid date 'February 29' as '" + year + "' is not a leap year");
|
||||
} else {
|
||||
throw new DateTimeException("Invalid date '" + month + " " + dayOfMonth + "'");
|
||||
}
|
||||
}
|
||||
return new LocalDate(year, month, dayOfMonth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor, previously validated.
|
||||
*
|
||||
* @param year the year to represent, from MIN_YEAR to MAX_YEAR
|
||||
* @param month the month-of-year to represent, not null
|
||||
* @param dayOfMonth the day-of-month to represent, valid for year-month, from 1 to 31
|
||||
*/
|
||||
private LocalDate(int year, int month, int dayOfMonth) {
|
||||
this.year = year;
|
||||
this.month = (short) month;
|
||||
this.day = (short) dayOfMonth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the year field.
|
||||
* <p>
|
||||
* This method returns the primitive {@code int} value for the year.
|
||||
* <p>
|
||||
* The year returned by this method is proleptic as per {@code get(YEAR)}.
|
||||
* To obtain the year-of-era, use {@code get(YEAR_OF_ERA}.
|
||||
*
|
||||
* @return the year, from MIN_YEAR to MAX_YEAR
|
||||
*/
|
||||
public int getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the month-of-year field as an int from 1 to 12.
|
||||
*
|
||||
* @return the month-of-year
|
||||
*/
|
||||
public int getMonth() {
|
||||
return month;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the day-of-month field.
|
||||
* <p>
|
||||
* This method returns the primitive {@code int} value for the day-of-month.
|
||||
*
|
||||
* @return the day-of-month, from 1 to 31
|
||||
*/
|
||||
public int getDayOfMonth() {
|
||||
return day;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the day-of-week field, which is an int from 1 to 7.
|
||||
*
|
||||
* @return the day-of-week
|
||||
*/
|
||||
public int getDayOfWeek() {
|
||||
return (int)floorMod(toEpochDay() + 3, 7) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this {@code LocalDate} with the specified number of days added.
|
||||
* <p>
|
||||
* This method adds the specified amount to the days field incrementing the
|
||||
* month and year fields as necessary to ensure the result remains valid.
|
||||
* The result is only invalid if the maximum/minimum year is exceeded.
|
||||
* <p>
|
||||
* For example, 2008-12-31 plus one day would result in 2009-01-01.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @param daysToAdd the days to add, may be negative
|
||||
* @return a {@code LocalDate} based on this date with the days added, not null
|
||||
* @throws DateTimeException if the result exceeds the supported date range
|
||||
*/
|
||||
public LocalDate plusDays(long daysToAdd) {
|
||||
if (daysToAdd == 0) {
|
||||
return this;
|
||||
}
|
||||
long mjDay = addExact(toEpochDay(), daysToAdd);
|
||||
return LocalDate.ofEpochDay(mjDay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this {@code LocalDate} with the specified number of days subtracted.
|
||||
* <p>
|
||||
* This method subtracts the specified amount from the days field decrementing the
|
||||
* month and year fields as necessary to ensure the result remains valid.
|
||||
* The result is only invalid if the maximum/minimum year is exceeded.
|
||||
* <p>
|
||||
* For example, 2009-01-01 minus one day would result in 2008-12-31.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @param daysToSubtract the days to subtract, may be negative
|
||||
* @return a {@code LocalDate} based on this date with the days subtracted, not null
|
||||
* @throws DateTimeException if the result exceeds the supported date range
|
||||
*/
|
||||
public LocalDate minusDays(long daysToSubtract) {
|
||||
return (daysToSubtract == Long.MIN_VALUE ? plusDays(Long.MAX_VALUE).plusDays(1) : plusDays(-daysToSubtract));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an instance of {@code LocalDate} from the epoch day count.
|
||||
* <p>
|
||||
* The Epoch Day count is a simple incrementing count of days
|
||||
* where day 0 is 1970-01-01. Negative numbers represent earlier days.
|
||||
*
|
||||
* @param epochDay the Epoch Day to convert, based on the epoch 1970-01-01
|
||||
* @return the local date, not null
|
||||
* @throws DateTimeException if the epoch days exceeds the supported date range
|
||||
*/
|
||||
public static LocalDate ofEpochDay(long epochDay) {
|
||||
long zeroDay = epochDay + DAYS_0000_TO_1970;
|
||||
// find the march-based year
|
||||
zeroDay -= 60; // adjust to 0000-03-01 so leap day is at end of four year cycle
|
||||
long adjust = 0;
|
||||
if (zeroDay < 0) {
|
||||
// adjust negative years to positive for calculation
|
||||
long adjustCycles = (zeroDay + 1) / DAYS_PER_CYCLE - 1;
|
||||
adjust = adjustCycles * 400;
|
||||
zeroDay += -adjustCycles * DAYS_PER_CYCLE;
|
||||
}
|
||||
long yearEst = (400 * zeroDay + 591) / DAYS_PER_CYCLE;
|
||||
long doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
|
||||
if (doyEst < 0) {
|
||||
// fix estimate
|
||||
yearEst--;
|
||||
doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
|
||||
}
|
||||
yearEst += adjust; // reset any negative year
|
||||
int marchDoy0 = (int) doyEst;
|
||||
|
||||
// convert march-based values back to january-based
|
||||
int marchMonth0 = (marchDoy0 * 5 + 2) / 153;
|
||||
int month = (marchMonth0 + 2) % 12 + 1;
|
||||
int dom = marchDoy0 - (marchMonth0 * 306 + 5) / 10 + 1;
|
||||
yearEst += marchMonth0 / 10;
|
||||
|
||||
// check year now we are certain it is correct
|
||||
int year = YEAR.checkValidValue((int)yearEst);
|
||||
return new LocalDate(year, month, dom);
|
||||
}
|
||||
|
||||
public long toEpochDay() {
|
||||
long y = year;
|
||||
long m = month;
|
||||
long total = 0;
|
||||
total += 365 * y;
|
||||
if (y >= 0) {
|
||||
total += (y + 3) / 4 - (y + 99) / 100 + (y + 399) / 400;
|
||||
} else {
|
||||
total -= y / -4 - y / -100 + y / -400;
|
||||
}
|
||||
total += ((367 * m - 362) / 12);
|
||||
total += day - 1;
|
||||
if (m > 2) {
|
||||
total--;
|
||||
if (isLeapYear(year) == false) {
|
||||
total--;
|
||||
}
|
||||
}
|
||||
return total - DAYS_0000_TO_1970;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this date to another date.
|
||||
* <p>
|
||||
* The comparison is primarily based on the date, from earliest to latest.
|
||||
* It is "consistent with equals", as defined by {@link Comparable}.
|
||||
* <p>
|
||||
* If all the dates being compared are instances of {@code LocalDate},
|
||||
* then the comparison will be entirely based on the date.
|
||||
* If some dates being compared are in different chronologies, then the
|
||||
* chronology is also considered, see {@link java.time.temporal.ChronoLocalDate#compareTo}.
|
||||
*
|
||||
* @param other the other date to compare to, not null
|
||||
* @return the comparator value, negative if less, positive if greater
|
||||
*/
|
||||
public int compareTo(LocalDate otherDate) {
|
||||
int cmp = (year - otherDate.year);
|
||||
if (cmp == 0) {
|
||||
cmp = (month - otherDate.month);
|
||||
if (cmp == 0) {
|
||||
cmp = (day - otherDate.day);
|
||||
}
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this date is equal to another date.
|
||||
* <p>
|
||||
* Compares this {@code LocalDate} with another ensuring that the date is the same.
|
||||
* <p>
|
||||
* Only objects of type {@code LocalDate} are compared, other types return false.
|
||||
* To compare the dates of two {@code TemporalAccessor} instances, including dates
|
||||
* in two different chronologies, use {@link ChronoField#EPOCH_DAY} as a comparator.
|
||||
*
|
||||
* @param obj the object to check, null returns false
|
||||
* @return true if this is equal to the other date
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof LocalDate) {
|
||||
return compareTo((LocalDate) obj) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* A hash code for this date.
|
||||
*
|
||||
* @return a suitable hash code
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int yearValue = year;
|
||||
int monthValue = month;
|
||||
int dayValue = day;
|
||||
return (yearValue & 0xFFFFF800) ^ ((yearValue << 11) + (monthValue << 6) + (dayValue));
|
||||
}
|
||||
|
||||
}
|
@ -1,427 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is available under and governed by the GNU General Public
|
||||
* License version 2 only, as published by the Free Software Foundation.
|
||||
* However, the following notice accompanied the original version of this
|
||||
* file:
|
||||
*
|
||||
* Copyright (c) 2007-2012, Stephen Colebourne & Michael Nascimento Santos
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of JSR-310 nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package build.tools.tzdb;
|
||||
|
||||
import static build.tools.tzdb.Utils.*;
|
||||
import static build.tools.tzdb.LocalTime.HOURS_PER_DAY;
|
||||
import static build.tools.tzdb.LocalTime.MICROS_PER_DAY;
|
||||
import static build.tools.tzdb.LocalTime.MILLIS_PER_DAY;
|
||||
import static build.tools.tzdb.LocalTime.MINUTES_PER_DAY;
|
||||
import static build.tools.tzdb.LocalTime.SECONDS_PER_DAY;
|
||||
import static build.tools.tzdb.LocalTime.SECONDS_PER_MINUTE;
|
||||
import static build.tools.tzdb.LocalTime.SECONDS_PER_HOUR;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A date-time without a time-zone in the ISO-8601 calendar system,
|
||||
* such as {@code 2007-12-03T10:15:30}.
|
||||
*
|
||||
* @since 1.8
|
||||
*/
|
||||
final class LocalDateTime {
|
||||
|
||||
/**
|
||||
* The minimum supported {@code LocalDateTime}, '-999999999-01-01T00:00:00'.
|
||||
* This is the local date-time of midnight at the start of the minimum date.
|
||||
* This combines {@link LocalDate#MIN} and {@link LocalTime#MIN}.
|
||||
* This could be used by an application as a "far past" date-time.
|
||||
*/
|
||||
public static final LocalDateTime MIN = LocalDateTime.of(LocalDate.MIN, LocalTime.MIN);
|
||||
/**
|
||||
* The maximum supported {@code LocalDateTime}, '+999999999-12-31T23:59:59.999999999'.
|
||||
* This is the local date-time just before midnight at the end of the maximum date.
|
||||
* This combines {@link LocalDate#MAX} and {@link LocalTime#MAX}.
|
||||
* This could be used by an application as a "far future" date-time.
|
||||
*/
|
||||
public static final LocalDateTime MAX = LocalDateTime.of(LocalDate.MAX, LocalTime.MAX);
|
||||
|
||||
/**
|
||||
* The date part.
|
||||
*/
|
||||
private final LocalDate date;
|
||||
/**
|
||||
* The time part.
|
||||
*/
|
||||
private final LocalTime time;
|
||||
|
||||
/**
|
||||
* Obtains an instance of {@code LocalDateTime} from year, month,
|
||||
* day, hour and minute, setting the second and nanosecond to zero.
|
||||
* <p>
|
||||
* The day must be valid for the year and month, otherwise an exception will be thrown.
|
||||
* The second and nanosecond fields will be set to zero.
|
||||
*
|
||||
* @param year the year to represent, from MIN_YEAR to MAX_YEAR
|
||||
* @param month the month-of-year to represent, from 1 (January) to 12 (December)
|
||||
* @param dayOfMonth the day-of-month to represent, from 1 to 31
|
||||
* @param hour the hour-of-day to represent, from 0 to 23
|
||||
* @param minute the minute-of-hour to represent, from 0 to 59
|
||||
* @return the local date-time, not null
|
||||
* @throws DateTimeException if the value of any field is out of range
|
||||
* @throws DateTimeException if the day-of-month is invalid for the month-year
|
||||
*/
|
||||
public static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute) {
|
||||
LocalDate date = LocalDate.of(year, month, dayOfMonth);
|
||||
LocalTime time = LocalTime.of(hour, minute);
|
||||
return new LocalDateTime(date, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an instance of {@code LocalDateTime} from a date and time.
|
||||
*
|
||||
* @param date the local date, not null
|
||||
* @param time the local time, not null
|
||||
* @return the local date-time, not null
|
||||
*/
|
||||
public static LocalDateTime of(LocalDate date, LocalTime time) {
|
||||
Objects.requireNonNull(date, "date");
|
||||
Objects.requireNonNull(time, "time");
|
||||
return new LocalDateTime(date, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an instance of {@code LocalDateTime} using seconds from the
|
||||
* epoch of 1970-01-01T00:00:00Z.
|
||||
* <p>
|
||||
* This allows the {@link ChronoField#INSTANT_SECONDS epoch-second} field
|
||||
* to be converted to a local date-time. This is primarily intended for
|
||||
* low-level conversions rather than general application usage.
|
||||
*
|
||||
* @param epochSecond the number of seconds from the epoch of 1970-01-01T00:00:00Z
|
||||
* @param nanoOfSecond the nanosecond within the second, from 0 to 999,999,999
|
||||
* @param offset the zone offset, not null
|
||||
* @return the local date-time, not null
|
||||
* @throws DateTimeException if the result exceeds the supported range
|
||||
*/
|
||||
public static LocalDateTime ofEpochSecond(long epochSecond, int nanoOfSecond, ZoneOffset offset) {
|
||||
Objects.requireNonNull(offset, "offset");
|
||||
long localSecond = epochSecond + offset.getTotalSeconds(); // overflow caught later
|
||||
long localEpochDay = floorDiv(localSecond, SECONDS_PER_DAY);
|
||||
int secsOfDay = (int)floorMod(localSecond, SECONDS_PER_DAY);
|
||||
LocalDate date = LocalDate.ofEpochDay(localEpochDay);
|
||||
LocalTime time = LocalTime.ofSecondOfDay(secsOfDay); // ignore nano
|
||||
return new LocalDateTime(date, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param date the date part of the date-time, validated not null
|
||||
* @param time the time part of the date-time, validated not null
|
||||
*/
|
||||
private LocalDateTime(LocalDate date, LocalTime time) {
|
||||
this.date = date;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this date-time with the new date and time, checking
|
||||
* to see if a new object is in fact required.
|
||||
*
|
||||
* @param newDate the date of the new date-time, not null
|
||||
* @param newTime the time of the new date-time, not null
|
||||
* @return the date-time, not null
|
||||
*/
|
||||
private LocalDateTime with(LocalDate newDate, LocalTime newTime) {
|
||||
if (date == newDate && time == newTime) {
|
||||
return this;
|
||||
}
|
||||
return new LocalDateTime(newDate, newTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@code LocalDate} part of this date-time.
|
||||
* <p>
|
||||
* This returns a {@code LocalDate} with the same year, month and day
|
||||
* as this date-time.
|
||||
*
|
||||
* @return the date part of this date-time, not null
|
||||
*/
|
||||
public LocalDate getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the year field.
|
||||
* <p>
|
||||
* This method returns the primitive {@code int} value for the year.
|
||||
* <p>
|
||||
* The year returned by this method is proleptic as per {@code get(YEAR)}.
|
||||
* To obtain the year-of-era, use {@code get(YEAR_OF_ERA}.
|
||||
*
|
||||
* @return the year, from MIN_YEAR to MAX_YEAR
|
||||
*/
|
||||
public int getYear() {
|
||||
return date.getYear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the month-of-year field as an int from 1 to 12.
|
||||
*
|
||||
* @return the month-of-year
|
||||
*/
|
||||
public int getMonth() {
|
||||
return date.getMonth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the day-of-month field.
|
||||
* <p>
|
||||
* This method returns the primitive {@code int} value for the day-of-month.
|
||||
*
|
||||
* @return the day-of-month, from 1 to 31
|
||||
*/
|
||||
public int getDayOfMonth() {
|
||||
return date.getDayOfMonth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the day-of-week field, which is an integer from 1 to 7.
|
||||
*
|
||||
* @return the day-of-week, from 1 to 7
|
||||
*/
|
||||
public int getDayOfWeek() {
|
||||
return date.getDayOfWeek();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@code LocalTime} part of this date-time.
|
||||
* <p>
|
||||
* This returns a {@code LocalTime} with the same hour, minute, second and
|
||||
* nanosecond as this date-time.
|
||||
*
|
||||
* @return the time part of this date-time, not null
|
||||
*/
|
||||
public LocalTime getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hour-of-day field.
|
||||
*
|
||||
* @return the hour-of-day, from 0 to 23
|
||||
*/
|
||||
public int getHour() {
|
||||
return time.getHour();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minute-of-hour field.
|
||||
*
|
||||
* @return the minute-of-hour, from 0 to 59
|
||||
*/
|
||||
public int getMinute() {
|
||||
return time.getMinute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the second-of-minute field.
|
||||
*
|
||||
* @return the second-of-minute, from 0 to 59
|
||||
*/
|
||||
public int getSecond() {
|
||||
return time.getSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this date-time to the number of seconds from the epoch
|
||||
* of 1970-01-01T00:00:00Z.
|
||||
* <p>
|
||||
* This combines this local date-time and the specified offset to calculate the
|
||||
* epoch-second value, which is the number of elapsed seconds from 1970-01-01T00:00:00Z.
|
||||
* Instants on the time-line after the epoch are positive, earlier are negative.
|
||||
* <p>
|
||||
* This default implementation calculates from the epoch-day of the date and the
|
||||
* second-of-day of the time.
|
||||
*
|
||||
* @param offset the offset to use for the conversion, not null
|
||||
* @return the number of seconds from the epoch of 1970-01-01T00:00:00Z
|
||||
*/
|
||||
public long toEpochSecond(ZoneOffset offset) {
|
||||
Objects.requireNonNull(offset, "offset");
|
||||
long epochDay = getDate().toEpochDay();
|
||||
long secs = epochDay * 86400 + getTime().toSecondOfDay();
|
||||
secs -= offset.getTotalSeconds();
|
||||
return secs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this {@code LocalDateTime} with the specified period in days added.
|
||||
* <p>
|
||||
* This method adds the specified amount to the days field incrementing the
|
||||
* month and year fields as necessary to ensure the result remains valid.
|
||||
* The result is only invalid if the maximum/minimum year is exceeded.
|
||||
* <p>
|
||||
* For example, 2008-12-31 plus one day would result in 2009-01-01.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @param days the days to add, may be negative
|
||||
* @return a {@code LocalDateTime} based on this date-time with the days added, not null
|
||||
* @throws DateTimeException if the result exceeds the supported date range
|
||||
*/
|
||||
public LocalDateTime plusDays(long days) {
|
||||
LocalDate newDate = date.plusDays(days);
|
||||
return with(newDate, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this {@code LocalDateTime} with the specified period in seconds added.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @param seconds the seconds to add, may be negative
|
||||
* @return a {@code LocalDateTime} based on this date-time with the seconds added, not null
|
||||
* @throws DateTimeException if the result exceeds the supported date range
|
||||
*/
|
||||
public LocalDateTime plusSeconds(long seconds) {
|
||||
return plusWithOverflow(date, 0, 0, seconds, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this {@code LocalDateTime} with the specified period added.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @param newDate the new date to base the calculation on, not null
|
||||
* @param hours the hours to add, may be negative
|
||||
* @param minutes the minutes to add, may be negative
|
||||
* @param seconds the seconds to add, may be negative
|
||||
* @param nanos the nanos to add, may be negative
|
||||
* @param sign the sign to determine add or subtract
|
||||
* @return the combined result, not null
|
||||
*/
|
||||
private LocalDateTime plusWithOverflow(LocalDate newDate, long hours, long minutes, long seconds, int sign) {
|
||||
if ((hours | minutes | seconds) == 0) {
|
||||
return with(newDate, time);
|
||||
}
|
||||
long totDays = seconds / SECONDS_PER_DAY + // max/24*60*60
|
||||
minutes / MINUTES_PER_DAY + // max/24*60
|
||||
hours / HOURS_PER_DAY; // max/24
|
||||
totDays *= sign; // total max*0.4237...
|
||||
long totSecs = (seconds % SECONDS_PER_DAY) +
|
||||
(minutes % MINUTES_PER_DAY) * SECONDS_PER_MINUTE +
|
||||
(hours % HOURS_PER_DAY) * SECONDS_PER_HOUR;
|
||||
long curSoD = time.toSecondOfDay();
|
||||
totSecs = totSecs * sign + curSoD; // total 432000000000000
|
||||
totDays += floorDiv(totSecs, SECONDS_PER_DAY);
|
||||
|
||||
int newSoD = (int)floorMod(totSecs, SECONDS_PER_DAY);
|
||||
LocalTime newTime = (newSoD == curSoD ? time : LocalTime.ofSecondOfDay(newSoD));
|
||||
return with(newDate.plusDays(totDays), newTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this date-time to another date-time.
|
||||
* <p>
|
||||
* The comparison is primarily based on the date-time, from earliest to latest.
|
||||
* It is "consistent with equals", as defined by {@link Comparable}.
|
||||
* <p>
|
||||
* If all the date-times being compared are instances of {@code LocalDateTime},
|
||||
* then the comparison will be entirely based on the date-time.
|
||||
* If some dates being compared are in different chronologies, then the
|
||||
* chronology is also considered, see {@link ChronoLocalDateTime#compareTo}.
|
||||
*
|
||||
* @param other the other date-time to compare to, not null
|
||||
* @return the comparator value, negative if less, positive if greater
|
||||
*/
|
||||
public int compareTo(LocalDateTime other) {
|
||||
int cmp = date.compareTo(other.getDate());
|
||||
if (cmp == 0) {
|
||||
cmp = time.compareTo(other.getTime());
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this date-time is equal to another date-time.
|
||||
* <p>
|
||||
* Compares this {@code LocalDateTime} with another ensuring that the date-time is the same.
|
||||
* Only objects of type {@code LocalDateTime} are compared, other types return false.
|
||||
*
|
||||
* @param obj the object to check, null returns false
|
||||
* @return true if this is equal to the other date-time
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof LocalDateTime) {
|
||||
LocalDateTime other = (LocalDateTime) obj;
|
||||
return date.equals(other.date) && time.equals(other.time);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* A hash code for this date-time.
|
||||
*
|
||||
* @return a suitable hash code
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return date.hashCode() ^ time.hashCode();
|
||||
}
|
||||
|
||||
}
|
@ -1,388 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is available under and governed by the GNU General Public
|
||||
* License version 2 only, as published by the Free Software Foundation.
|
||||
* However, the following notice accompanied the original version of this
|
||||
* file:
|
||||
*
|
||||
* Copyright (c) 2007-2012, Stephen Colebourne & Michael Nascimento Santos
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of JSR-310 nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package build.tools.tzdb;
|
||||
|
||||
import static build.tools.tzdb.ChronoField.HOUR_OF_DAY;
|
||||
import static build.tools.tzdb.ChronoField.MINUTE_OF_HOUR;
|
||||
import static build.tools.tzdb.ChronoField.SECOND_OF_MINUTE;
|
||||
import static build.tools.tzdb.ChronoField.SECOND_OF_DAY;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A time without time-zone in the ISO-8601 calendar system,
|
||||
* such as {@code 10:15:30}.
|
||||
*
|
||||
*/
|
||||
final class LocalTime {
|
||||
|
||||
/**
|
||||
* The minimum supported {@code LocalTime}, '00:00'.
|
||||
* This is the time of midnight at the start of the day.
|
||||
*/
|
||||
public static final LocalTime MIN;
|
||||
/**
|
||||
* The minimum supported {@code LocalTime}, '23:59:59.999999999'.
|
||||
* This is the time just before midnight at the end of the day.
|
||||
*/
|
||||
public static final LocalTime MAX;
|
||||
/**
|
||||
* The time of midnight at the start of the day, '00:00'.
|
||||
*/
|
||||
public static final LocalTime MIDNIGHT;
|
||||
/**
|
||||
* The time of noon in the middle of the day, '12:00'.
|
||||
*/
|
||||
public static final LocalTime NOON;
|
||||
/**
|
||||
* Constants for the local time of each hour.
|
||||
*/
|
||||
private static final LocalTime[] HOURS = new LocalTime[24];
|
||||
static {
|
||||
for (int i = 0; i < HOURS.length; i++) {
|
||||
HOURS[i] = new LocalTime(i, 0, 0);
|
||||
}
|
||||
MIDNIGHT = HOURS[0];
|
||||
NOON = HOURS[12];
|
||||
MIN = HOURS[0];
|
||||
MAX = new LocalTime(23, 59, 59);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hours per day.
|
||||
*/
|
||||
static final int HOURS_PER_DAY = 24;
|
||||
/**
|
||||
* Minutes per hour.
|
||||
*/
|
||||
static final int MINUTES_PER_HOUR = 60;
|
||||
/**
|
||||
* Minutes per day.
|
||||
*/
|
||||
static final int MINUTES_PER_DAY = MINUTES_PER_HOUR * HOURS_PER_DAY;
|
||||
/**
|
||||
* Seconds per minute.
|
||||
*/
|
||||
static final int SECONDS_PER_MINUTE = 60;
|
||||
/**
|
||||
* Seconds per hour.
|
||||
*/
|
||||
static final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
|
||||
/**
|
||||
* Seconds per day.
|
||||
*/
|
||||
static final int SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY;
|
||||
/**
|
||||
* Milliseconds per day.
|
||||
*/
|
||||
static final long MILLIS_PER_DAY = SECONDS_PER_DAY * 1000L;
|
||||
/**
|
||||
* Microseconds per day.
|
||||
*/
|
||||
static final long MICROS_PER_DAY = SECONDS_PER_DAY * 1000_000L;
|
||||
|
||||
/**
|
||||
* The hour.
|
||||
*/
|
||||
private final byte hour;
|
||||
/**
|
||||
* The minute.
|
||||
*/
|
||||
private final byte minute;
|
||||
/**
|
||||
* The second.
|
||||
*/
|
||||
private final byte second;
|
||||
|
||||
/**
|
||||
* Obtains an instance of {@code LocalTime} from an hour and minute.
|
||||
* <p>
|
||||
* The second and nanosecond fields will be set to zero by this factory method.
|
||||
* <p>
|
||||
* This factory may return a cached value, but applications must not rely on this.
|
||||
*
|
||||
* @param hour the hour-of-day to represent, from 0 to 23
|
||||
* @param minute the minute-of-hour to represent, from 0 to 59
|
||||
* @return the local time, not null
|
||||
* @throws DateTimeException if the value of any field is out of range
|
||||
*/
|
||||
public static LocalTime of(int hour, int minute) {
|
||||
HOUR_OF_DAY.checkValidValue(hour);
|
||||
if (minute == 0) {
|
||||
return HOURS[hour]; // for performance
|
||||
}
|
||||
MINUTE_OF_HOUR.checkValidValue(minute);
|
||||
return new LocalTime(hour, minute, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an instance of {@code LocalTime} from an hour, minute and second.
|
||||
* <p>
|
||||
* The nanosecond field will be set to zero by this factory method.
|
||||
* <p>
|
||||
* This factory may return a cached value, but applications must not rely on this.
|
||||
*
|
||||
* @param hour the hour-of-day to represent, from 0 to 23
|
||||
* @param minute the minute-of-hour to represent, from 0 to 59
|
||||
* @param second the second-of-minute to represent, from 0 to 59
|
||||
* @return the local time, not null
|
||||
* @throws DateTimeException if the value of any field is out of range
|
||||
*/
|
||||
public static LocalTime of(int hour, int minute, int second) {
|
||||
HOUR_OF_DAY.checkValidValue(hour);
|
||||
if ((minute | second) == 0) {
|
||||
return HOURS[hour]; // for performance
|
||||
}
|
||||
MINUTE_OF_HOUR.checkValidValue(minute);
|
||||
SECOND_OF_MINUTE.checkValidValue(second);
|
||||
return new LocalTime(hour, minute, second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an instance of {@code LocalTime} from a second-of-day value.
|
||||
* <p>
|
||||
* This factory may return a cached value, but applications must not rely on this.
|
||||
*
|
||||
* @param secondOfDay the second-of-day, from {@code 0} to {@code 24 * 60 * 60 - 1}
|
||||
* @return the local time, not null
|
||||
* @throws DateTimeException if the second-of-day value is invalid
|
||||
*/
|
||||
public static LocalTime ofSecondOfDay(int secondOfDay) {
|
||||
SECOND_OF_DAY.checkValidValue(secondOfDay);
|
||||
int hours = secondOfDay / SECONDS_PER_HOUR;
|
||||
secondOfDay -= hours * SECONDS_PER_HOUR;
|
||||
int minutes = secondOfDay / SECONDS_PER_MINUTE;
|
||||
secondOfDay -= minutes * SECONDS_PER_MINUTE;
|
||||
return create(hours, minutes, secondOfDay);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a local time from the hour, minute, second and nanosecond fields.
|
||||
* <p>
|
||||
* This factory may return a cached value, but applications must not rely on this.
|
||||
*
|
||||
* @param hour the hour-of-day to represent, validated from 0 to 23
|
||||
* @param minute the minute-of-hour to represent, validated from 0 to 59
|
||||
* @param second the second-of-minute to represent, validated from 0 to 59
|
||||
* @return the local time, not null
|
||||
*/
|
||||
private static LocalTime create(int hour, int minute, int second) {
|
||||
if ((minute | second) == 0) {
|
||||
return HOURS[hour];
|
||||
}
|
||||
return new LocalTime(hour, minute, second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor, previously validated.
|
||||
*
|
||||
* @param hour the hour-of-day to represent, validated from 0 to 23
|
||||
* @param minute the minute-of-hour to represent, validated from 0 to 59
|
||||
* @param second the second-of-minute to represent, validated from 0 to 59
|
||||
*/
|
||||
private LocalTime(int hour, int minute, int second) {
|
||||
this.hour = (byte) hour;
|
||||
this.minute = (byte) minute;
|
||||
this.second = (byte) second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hour-of-day field.
|
||||
*
|
||||
* @return the hour-of-day, from 0 to 23
|
||||
*/
|
||||
public int getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minute-of-hour field.
|
||||
*
|
||||
* @return the minute-of-hour, from 0 to 59
|
||||
*/
|
||||
public int getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the second-of-minute field.
|
||||
*
|
||||
* @return the second-of-minute, from 0 to 59
|
||||
*/
|
||||
public int getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this {@code LocalTime} with the specified period in seconds added.
|
||||
* <p>
|
||||
* This adds the specified number of seconds to this time, returning a new time.
|
||||
* The calculation wraps around midnight.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @param secondstoAdd the seconds to add, may be negative
|
||||
* @return a {@code LocalTime} based on this time with the seconds added, not null
|
||||
*/
|
||||
public LocalTime plusSeconds(long secondstoAdd) {
|
||||
if (secondstoAdd == 0) {
|
||||
return this;
|
||||
}
|
||||
int sofd = hour * SECONDS_PER_HOUR +
|
||||
minute * SECONDS_PER_MINUTE + second;
|
||||
int newSofd = ((int) (secondstoAdd % SECONDS_PER_DAY) + sofd + SECONDS_PER_DAY) % SECONDS_PER_DAY;
|
||||
if (sofd == newSofd) {
|
||||
return this;
|
||||
}
|
||||
int newHour = newSofd / SECONDS_PER_HOUR;
|
||||
int newMinute = (newSofd / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR;
|
||||
int newSecond = newSofd % SECONDS_PER_MINUTE;
|
||||
return create(newHour, newMinute, newSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this {@code LocalTime} with the specified period in seconds subtracted.
|
||||
* <p>
|
||||
* This subtracts the specified number of seconds from this time, returning a new time.
|
||||
* The calculation wraps around midnight.
|
||||
* <p>
|
||||
* This instance is immutable and unaffected by this method call.
|
||||
*
|
||||
* @param secondsToSubtract the seconds to subtract, may be negative
|
||||
* @return a {@code LocalTime} based on this time with the seconds subtracted, not null
|
||||
*/
|
||||
public LocalTime minusSeconds(long secondsToSubtract) {
|
||||
return plusSeconds(-(secondsToSubtract % SECONDS_PER_DAY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the time as seconds of day,
|
||||
* from {@code 0} to {@code 24 * 60 * 60 - 1}.
|
||||
*
|
||||
* @return the second-of-day equivalent to this time
|
||||
*/
|
||||
public int toSecondOfDay() {
|
||||
int total = hour * SECONDS_PER_HOUR;
|
||||
total += minute * SECONDS_PER_MINUTE;
|
||||
total += second;
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this {@code LocalTime} to another time.
|
||||
* <p>
|
||||
* The comparison is based on the time-line position of the local times within a day.
|
||||
* It is "consistent with equals", as defined by {@link Comparable}.
|
||||
*
|
||||
* @param other the other time to compare to, not null
|
||||
* @return the comparator value, negative if less, positive if greater
|
||||
* @throws NullPointerException if {@code other} is null
|
||||
*/
|
||||
public int compareTo(LocalTime other) {
|
||||
int cmp = Integer.compare(hour, other.hour);
|
||||
if (cmp == 0) {
|
||||
cmp = Integer.compare(minute, other.minute);
|
||||
if (cmp == 0) {
|
||||
cmp = Integer.compare(second, other.second);
|
||||
}
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this time is equal to another time.
|
||||
* <p>
|
||||
* The comparison is based on the time-line position of the time within a day.
|
||||
* <p>
|
||||
* Only objects of type {@code LocalTime} are compared, other types return false.
|
||||
* To compare the date of two {@code TemporalAccessor} instances, use
|
||||
* {@link ChronoField#NANO_OF_DAY} as a comparator.
|
||||
*
|
||||
* @param obj the object to check, null returns false
|
||||
* @return true if this is equal to the other time
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof LocalTime) {
|
||||
LocalTime other = (LocalTime) obj;
|
||||
return hour == other.hour && minute == other.minute &&
|
||||
second == other.second;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* A hash code for this time.
|
||||
*
|
||||
* @return a suitable hash code
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
long sod = toSecondOfDay();
|
||||
return (int) (sod ^ (sod >>> 32));
|
||||
}
|
||||
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is available under and governed by the GNU General Public
|
||||
* License version 2 only, as published by the Free Software Foundation.
|
||||
* However, the following notice accompanied the original version of this
|
||||
* file:
|
||||
*
|
||||
* Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of JSR-310 nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package build.tools.tzdb;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A definition of the way a local time can be converted to the actual
|
||||
* transition date-time.
|
||||
* <p>
|
||||
* Time zone rules are expressed in one of three ways:
|
||||
* <p><ul>
|
||||
* <li>Relative to UTC</li>
|
||||
* <li>Relative to the standard offset in force</li>
|
||||
* <li>Relative to the wall offset (what you would see on a clock on the wall)</li>
|
||||
* </ul><p>
|
||||
*/
|
||||
public enum TimeDefinition {
|
||||
/** The local date-time is expressed in terms of the UTC offset. */
|
||||
UTC,
|
||||
/** The local date-time is expressed in terms of the wall offset. */
|
||||
WALL,
|
||||
/** The local date-time is expressed in terms of the standard offset. */
|
||||
STANDARD;
|
||||
|
||||
/**
|
||||
* Converts the specified local date-time to the local date-time actually
|
||||
* seen on a wall clock.
|
||||
* <p>
|
||||
* This method converts using the type of this enum.
|
||||
* The output is defined relative to the 'before' offset of the transition.
|
||||
* <p>
|
||||
* The UTC type uses the UTC offset.
|
||||
* The STANDARD type uses the standard offset.
|
||||
* The WALL type returns the input date-time.
|
||||
* The result is intended for use with the wall-offset.
|
||||
*
|
||||
* @param dateTime the local date-time, not null
|
||||
* @param standardOffset the standard offset, not null
|
||||
* @param wallOffset the wall offset, not null
|
||||
* @return the date-time relative to the wall/before offset, not null
|
||||
*/
|
||||
public LocalDateTime createDateTime(LocalDateTime dateTime, ZoneOffset standardOffset, ZoneOffset wallOffset) {
|
||||
switch (this) {
|
||||
case UTC: {
|
||||
int difference = wallOffset.getTotalSeconds() - ZoneOffset.UTC.getTotalSeconds();
|
||||
return dateTime.plusSeconds(difference);
|
||||
}
|
||||
case STANDARD: {
|
||||
int difference = wallOffset.getTotalSeconds() - standardOffset.getTotalSeconds();
|
||||
return dateTime.plusSeconds(difference);
|
||||
}
|
||||
default: // WALL
|
||||
return dateTime;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -56,8 +56,6 @@
|
||||
*/
|
||||
package build.tools.tzdb;
|
||||
|
||||
import static build.tools.tzdb.Utils.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@ -179,15 +177,41 @@ public final class TzdbZoneRulesCompiler {
|
||||
System.exit(1);
|
||||
System.err.println("Source directory does not contain file: VERSION");
|
||||
}
|
||||
|
||||
// load source files
|
||||
printVerbose("Compiling TZDB version " + version);
|
||||
// parse source files
|
||||
for (Path file : srcFiles) {
|
||||
printVerbose("Parsing file: " + file);
|
||||
parseFile(file);
|
||||
}
|
||||
TzdbZoneRulesProvider provider = new TzdbZoneRulesProvider(srcFiles);
|
||||
|
||||
// build zone rules
|
||||
printVerbose("Building rules");
|
||||
buildZoneRules();
|
||||
|
||||
// Build the rules, zones and links into real zones.
|
||||
SortedMap<String, ZoneRules> builtZones = new TreeMap<>();
|
||||
|
||||
// build zones
|
||||
for (String zoneId : provider.getZoneIds()) {
|
||||
printVerbose("Building zone " + zoneId);
|
||||
builtZones.put(zoneId, provider.getZoneRules(zoneId));
|
||||
}
|
||||
|
||||
// build aliases
|
||||
Map<String, String> links = provider.getAliasMap();
|
||||
for (String aliasId : links.keySet()) {
|
||||
String realId = links.get(aliasId);
|
||||
printVerbose("Linking alias " + aliasId + " to " + realId);
|
||||
ZoneRules realRules = builtZones.get(realId);
|
||||
if (realRules == null) {
|
||||
realId = links.get(realId); // try again (handle alias liked to alias)
|
||||
printVerbose("Relinking alias " + aliasId + " to " + realId);
|
||||
realRules = builtZones.get(realId);
|
||||
if (realRules == null) {
|
||||
throw new IllegalArgumentException("Alias '" + aliasId + "' links to invalid zone '" + realId);
|
||||
}
|
||||
links.put(aliasId, realId);
|
||||
}
|
||||
builtZones.put(aliasId, realRules);
|
||||
}
|
||||
|
||||
// output to file
|
||||
printVerbose("Outputting tzdb file: " + dstFile);
|
||||
outputFile(dstFile, version, builtZones, links);
|
||||
@ -269,361 +293,13 @@ public final class TzdbZoneRulesCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
private static final Pattern YEAR = Pattern.compile("(?i)(?<min>min)|(?<max>max)|(?<only>only)|(?<year>[0-9]+)");
|
||||
private static final Pattern MONTH = Pattern.compile("(?i)(jan)|(feb)|(mar)|(apr)|(may)|(jun)|(jul)|(aug)|(sep)|(oct)|(nov)|(dec)");
|
||||
private static final Matcher DOW = Pattern.compile("(?i)(mon)|(tue)|(wed)|(thu)|(fri)|(sat)|(sun)").matcher("");
|
||||
private static final Matcher TIME = Pattern.compile("(?<neg>-)?+(?<hour>[0-9]{1,2})(:(?<minute>[0-5][0-9]))?+(:(?<second>[0-5][0-9]))?+").matcher("");
|
||||
|
||||
/** The TZDB rules. */
|
||||
private final Map<String, List<TZDBRule>> rules = new HashMap<>();
|
||||
|
||||
/** The TZDB zones. */
|
||||
private final Map<String, List<TZDBZone>> zones = new HashMap<>();
|
||||
|
||||
/** The TZDB links. */
|
||||
private final Map<String, String> links = new HashMap<>();
|
||||
|
||||
/** The built zones. */
|
||||
private final SortedMap<String, ZoneRules> builtZones = new TreeMap<>();
|
||||
|
||||
/** Whether to output verbose messages. */
|
||||
private boolean verbose;
|
||||
|
||||
/**
|
||||
* private contructor
|
||||
*/
|
||||
private TzdbZoneRulesCompiler() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a source file.
|
||||
*
|
||||
* @param file the file being read, not null
|
||||
* @throws Exception if an error occurs
|
||||
*/
|
||||
private void parseFile(Path file) throws Exception {
|
||||
int lineNumber = 1;
|
||||
String line = null;
|
||||
try {
|
||||
List<String> lines = Files.readAllLines(file, StandardCharsets.ISO_8859_1);
|
||||
List<TZDBZone> openZone = null;
|
||||
for (; lineNumber < lines.size(); lineNumber++) {
|
||||
line = lines.get(lineNumber);
|
||||
int index = line.indexOf('#'); // remove comments (doesn't handle # in quotes)
|
||||
if (index >= 0) {
|
||||
line = line.substring(0, index);
|
||||
}
|
||||
if (line.trim().length() == 0) { // ignore blank lines
|
||||
continue;
|
||||
}
|
||||
Scanner s = new Scanner(line);
|
||||
if (openZone != null && Character.isWhitespace(line.charAt(0)) && s.hasNext()) {
|
||||
if (parseZoneLine(s, openZone)) {
|
||||
openZone = null;
|
||||
}
|
||||
} else {
|
||||
if (s.hasNext()) {
|
||||
String first = s.next();
|
||||
if (first.equals("Zone")) {
|
||||
openZone = new ArrayList<>();
|
||||
try {
|
||||
zones.put(s.next(), openZone);
|
||||
if (parseZoneLine(s, openZone)) {
|
||||
openZone = null;
|
||||
}
|
||||
} catch (NoSuchElementException x) {
|
||||
printVerbose("Invalid Zone line in file: " + file + ", line: " + line);
|
||||
throw new IllegalArgumentException("Invalid Zone line");
|
||||
}
|
||||
} else {
|
||||
openZone = null;
|
||||
if (first.equals("Rule")) {
|
||||
try {
|
||||
parseRuleLine(s);
|
||||
} catch (NoSuchElementException x) {
|
||||
printVerbose("Invalid Rule line in file: " + file + ", line: " + line);
|
||||
throw new IllegalArgumentException("Invalid Rule line");
|
||||
}
|
||||
} else if (first.equals("Link")) {
|
||||
try {
|
||||
String realId = s.next();
|
||||
String aliasId = s.next();
|
||||
links.put(aliasId, realId);
|
||||
} catch (NoSuchElementException x) {
|
||||
printVerbose("Invalid Link line in file: " + file + ", line: " + line);
|
||||
throw new IllegalArgumentException("Invalid Link line");
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown line");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new Exception("Failed while parsing file '" + file + "' on line " + lineNumber + " '" + line + "'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Rule line.
|
||||
*
|
||||
* @param s the line scanner, not null
|
||||
*/
|
||||
private void parseRuleLine(Scanner s) {
|
||||
TZDBRule rule = new TZDBRule();
|
||||
String name = s.next();
|
||||
if (rules.containsKey(name) == false) {
|
||||
rules.put(name, new ArrayList<TZDBRule>());
|
||||
}
|
||||
rules.get(name).add(rule);
|
||||
rule.startYear = parseYear(s, 0);
|
||||
rule.endYear = parseYear(s, rule.startYear);
|
||||
if (rule.startYear > rule.endYear) {
|
||||
throw new IllegalArgumentException("Year order invalid: " + rule.startYear + " > " + rule.endYear);
|
||||
}
|
||||
parseOptional(s.next()); // type is unused
|
||||
parseMonthDayTime(s, rule);
|
||||
rule.savingsAmount = parsePeriod(s.next());
|
||||
rule.text = parseOptional(s.next());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Zone line.
|
||||
*
|
||||
* @param s the line scanner, not null
|
||||
* @return true if the zone is complete
|
||||
*/
|
||||
private boolean parseZoneLine(Scanner s, List<TZDBZone> zoneList) {
|
||||
TZDBZone zone = new TZDBZone();
|
||||
zoneList.add(zone);
|
||||
zone.standardOffset = parseOffset(s.next());
|
||||
String savingsRule = parseOptional(s.next());
|
||||
if (savingsRule == null) {
|
||||
zone.fixedSavingsSecs = 0;
|
||||
zone.savingsRule = null;
|
||||
} else {
|
||||
try {
|
||||
zone.fixedSavingsSecs = parsePeriod(savingsRule);
|
||||
zone.savingsRule = null;
|
||||
} catch (Exception ex) {
|
||||
zone.fixedSavingsSecs = null;
|
||||
zone.savingsRule = savingsRule;
|
||||
}
|
||||
}
|
||||
zone.text = s.next();
|
||||
if (s.hasNext()) {
|
||||
zone.year = Integer.parseInt(s.next());
|
||||
if (s.hasNext()) {
|
||||
parseMonthDayTime(s, zone);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Rule line.
|
||||
*
|
||||
* @param s the line scanner, not null
|
||||
* @param mdt the object to parse into, not null
|
||||
*/
|
||||
private void parseMonthDayTime(Scanner s, TZDBMonthDayTime mdt) {
|
||||
mdt.month = parseMonth(s);
|
||||
if (s.hasNext()) {
|
||||
String dayRule = s.next();
|
||||
if (dayRule.startsWith("last")) {
|
||||
mdt.dayOfMonth = -1;
|
||||
mdt.dayOfWeek = parseDayOfWeek(dayRule.substring(4));
|
||||
mdt.adjustForwards = false;
|
||||
} else {
|
||||
int index = dayRule.indexOf(">=");
|
||||
if (index > 0) {
|
||||
mdt.dayOfWeek = parseDayOfWeek(dayRule.substring(0, index));
|
||||
dayRule = dayRule.substring(index + 2);
|
||||
} else {
|
||||
index = dayRule.indexOf("<=");
|
||||
if (index > 0) {
|
||||
mdt.dayOfWeek = parseDayOfWeek(dayRule.substring(0, index));
|
||||
mdt.adjustForwards = false;
|
||||
dayRule = dayRule.substring(index + 2);
|
||||
}
|
||||
}
|
||||
mdt.dayOfMonth = Integer.parseInt(dayRule);
|
||||
}
|
||||
if (s.hasNext()) {
|
||||
String timeStr = s.next();
|
||||
int secsOfDay = parseSecs(timeStr);
|
||||
if (secsOfDay == 86400) {
|
||||
mdt.endOfDay = true;
|
||||
secsOfDay = 0;
|
||||
}
|
||||
LocalTime time = LocalTime.ofSecondOfDay(secsOfDay);
|
||||
mdt.time = time;
|
||||
mdt.timeDefinition = parseTimeDefinition(timeStr.charAt(timeStr.length() - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int parseYear(Scanner s, int defaultYear) {
|
||||
if (s.hasNext(YEAR)) {
|
||||
s.next(YEAR);
|
||||
MatchResult mr = s.match();
|
||||
if (mr.group(1) != null) {
|
||||
return 1900; // systemv has min
|
||||
} else if (mr.group(2) != null) {
|
||||
return YEAR_MAX_VALUE;
|
||||
} else if (mr.group(3) != null) {
|
||||
return defaultYear;
|
||||
}
|
||||
return Integer.parseInt(mr.group(4));
|
||||
/*
|
||||
if (mr.group("min") != null) {
|
||||
//return YEAR_MIN_VALUE;
|
||||
return 1900; // systemv has min
|
||||
} else if (mr.group("max") != null) {
|
||||
return YEAR_MAX_VALUE;
|
||||
} else if (mr.group("only") != null) {
|
||||
return defaultYear;
|
||||
}
|
||||
return Integer.parseInt(mr.group("year"));
|
||||
*/
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown year: " + s.next());
|
||||
}
|
||||
|
||||
private int parseMonth(Scanner s) {
|
||||
if (s.hasNext(MONTH)) {
|
||||
s.next(MONTH);
|
||||
for (int moy = 1; moy < 13; moy++) {
|
||||
if (s.match().group(moy) != null) {
|
||||
return moy;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown month: " + s.next());
|
||||
}
|
||||
|
||||
private int parseDayOfWeek(String str) {
|
||||
if (DOW.reset(str).matches()) {
|
||||
for (int dow = 1; dow < 8; dow++) {
|
||||
if (DOW.group(dow) != null) {
|
||||
return dow;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown day-of-week: " + str);
|
||||
}
|
||||
|
||||
private String parseOptional(String str) {
|
||||
return str.equals("-") ? null : str;
|
||||
}
|
||||
|
||||
private int parseSecs(String str) {
|
||||
if (str.equals("-")) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
if (TIME.reset(str).find()) {
|
||||
int secs = Integer.parseInt(TIME.group("hour")) * 60 * 60;
|
||||
if (TIME.group("minute") != null) {
|
||||
secs += Integer.parseInt(TIME.group("minute")) * 60;
|
||||
}
|
||||
if (TIME.group("second") != null) {
|
||||
secs += Integer.parseInt(TIME.group("second"));
|
||||
}
|
||||
if (TIME.group("neg") != null) {
|
||||
secs = -secs;
|
||||
}
|
||||
return secs;
|
||||
}
|
||||
} catch (NumberFormatException x) {}
|
||||
throw new IllegalArgumentException(str);
|
||||
}
|
||||
|
||||
private ZoneOffset parseOffset(String str) {
|
||||
int secs = parseSecs(str);
|
||||
return ZoneOffset.ofTotalSeconds(secs);
|
||||
}
|
||||
|
||||
private int parsePeriod(String str) {
|
||||
return parseSecs(str);
|
||||
}
|
||||
|
||||
private TimeDefinition parseTimeDefinition(char c) {
|
||||
switch (c) {
|
||||
case 's':
|
||||
case 'S':
|
||||
// standard time
|
||||
return TimeDefinition.STANDARD;
|
||||
case 'u':
|
||||
case 'U':
|
||||
case 'g':
|
||||
case 'G':
|
||||
case 'z':
|
||||
case 'Z':
|
||||
// UTC
|
||||
return TimeDefinition.UTC;
|
||||
case 'w':
|
||||
case 'W':
|
||||
default:
|
||||
// wall time
|
||||
return TimeDefinition.WALL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the rules, zones and links into real zones.
|
||||
*
|
||||
* @throws Exception if an error occurs
|
||||
*/
|
||||
private void buildZoneRules() throws Exception {
|
||||
// build zones
|
||||
for (String zoneId : zones.keySet()) {
|
||||
printVerbose("Building zone " + zoneId);
|
||||
List<TZDBZone> tzdbZones = zones.get(zoneId);
|
||||
ZoneRulesBuilder bld = new ZoneRulesBuilder();
|
||||
for (TZDBZone tzdbZone : tzdbZones) {
|
||||
bld = tzdbZone.addToBuilder(bld, rules);
|
||||
}
|
||||
builtZones.put(zoneId, bld.toRules(zoneId));
|
||||
}
|
||||
|
||||
// build aliases
|
||||
for (String aliasId : links.keySet()) {
|
||||
String realId = links.get(aliasId);
|
||||
printVerbose("Linking alias " + aliasId + " to " + realId);
|
||||
ZoneRules realRules = builtZones.get(realId);
|
||||
if (realRules == null) {
|
||||
realId = links.get(realId); // try again (handle alias liked to alias)
|
||||
printVerbose("Relinking alias " + aliasId + " to " + realId);
|
||||
realRules = builtZones.get(realId);
|
||||
if (realRules == null) {
|
||||
throw new IllegalArgumentException("Alias '" + aliasId + "' links to invalid zone '" + realId);
|
||||
}
|
||||
links.put(aliasId, realId);
|
||||
}
|
||||
builtZones.put(aliasId, realRules);
|
||||
}
|
||||
// remove UTC and GMT
|
||||
// builtZones.remove("UTC");
|
||||
// builtZones.remove("GMT");
|
||||
// builtZones.remove("GMT0");
|
||||
builtZones.remove("GMT+0");
|
||||
builtZones.remove("GMT-0");
|
||||
links.remove("GMT+0");
|
||||
links.remove("GMT-0");
|
||||
// remove ROC, which is not supported in j.u.tz
|
||||
builtZones.remove("ROC");
|
||||
links.remove("ROC");
|
||||
// remove EST, HST and MST. They are supported via
|
||||
// the short-id mapping
|
||||
builtZones.remove("EST");
|
||||
builtZones.remove("HST");
|
||||
builtZones.remove("MST");
|
||||
}
|
||||
private TzdbZoneRulesCompiler() {}
|
||||
|
||||
/**
|
||||
* Prints a verbose message.
|
||||
@ -635,109 +311,4 @@ public final class TzdbZoneRulesCompiler {
|
||||
System.out.println(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a month-day-time in the TZDB file.
|
||||
*/
|
||||
abstract class TZDBMonthDayTime {
|
||||
/** The month of the cutover. */
|
||||
int month = 1;
|
||||
/** The day-of-month of the cutover. */
|
||||
int dayOfMonth = 1;
|
||||
/** Whether to adjust forwards. */
|
||||
boolean adjustForwards = true;
|
||||
/** The day-of-week of the cutover. */
|
||||
int dayOfWeek = -1;
|
||||
/** The time of the cutover. */
|
||||
LocalTime time = LocalTime.MIDNIGHT;
|
||||
/** Whether this is midnight end of day. */
|
||||
boolean endOfDay;
|
||||
/** The time of the cutover. */
|
||||
TimeDefinition timeDefinition = TimeDefinition.WALL;
|
||||
void adjustToFowards(int year) {
|
||||
if (adjustForwards == false && dayOfMonth > 0) {
|
||||
LocalDate adjustedDate = LocalDate.of(year, month, dayOfMonth).minusDays(6);
|
||||
dayOfMonth = adjustedDate.getDayOfMonth();
|
||||
month = adjustedDate.getMonth();
|
||||
adjustForwards = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a rule line in the TZDB file.
|
||||
*/
|
||||
final class TZDBRule extends TZDBMonthDayTime {
|
||||
/** The start year. */
|
||||
int startYear;
|
||||
/** The end year. */
|
||||
int endYear;
|
||||
/** The amount of savings. */
|
||||
int savingsAmount;
|
||||
/** The text name of the zone. */
|
||||
String text;
|
||||
|
||||
void addToBuilder(ZoneRulesBuilder bld) {
|
||||
adjustToFowards(2004); // irrelevant, treat as leap year
|
||||
bld.addRuleToWindow(startYear, endYear, month, dayOfMonth, dayOfWeek, time, endOfDay, timeDefinition, savingsAmount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a linked set of zone lines in the TZDB file.
|
||||
*/
|
||||
final class TZDBZone extends TZDBMonthDayTime {
|
||||
/** The standard offset. */
|
||||
ZoneOffset standardOffset;
|
||||
/** The fixed savings amount. */
|
||||
Integer fixedSavingsSecs;
|
||||
/** The savings rule. */
|
||||
String savingsRule;
|
||||
/** The text name of the zone. */
|
||||
String text;
|
||||
/** The year of the cutover. */
|
||||
int year = YEAR_MAX_VALUE;
|
||||
|
||||
ZoneRulesBuilder addToBuilder(ZoneRulesBuilder bld, Map<String, List<TZDBRule>> rules) {
|
||||
if (year != YEAR_MAX_VALUE) {
|
||||
bld.addWindow(standardOffset, toDateTime(year), timeDefinition);
|
||||
} else {
|
||||
bld.addWindowForever(standardOffset);
|
||||
}
|
||||
if (fixedSavingsSecs != null) {
|
||||
bld.setFixedSavingsToWindow(fixedSavingsSecs);
|
||||
} else {
|
||||
List<TZDBRule> tzdbRules = rules.get(savingsRule);
|
||||
if (tzdbRules == null) {
|
||||
throw new IllegalArgumentException("Rule not found: " + savingsRule);
|
||||
}
|
||||
for (TZDBRule tzdbRule : tzdbRules) {
|
||||
tzdbRule.addToBuilder(bld);
|
||||
}
|
||||
}
|
||||
return bld;
|
||||
}
|
||||
|
||||
private LocalDateTime toDateTime(int year) {
|
||||
adjustToFowards(year);
|
||||
LocalDate date;
|
||||
if (dayOfMonth == -1) {
|
||||
dayOfMonth = lengthOfMonth(month, isLeapYear(year));
|
||||
date = LocalDate.of(year, month, dayOfMonth);
|
||||
if (dayOfWeek != -1) {
|
||||
date = previousOrSame(date, dayOfWeek);
|
||||
}
|
||||
} else {
|
||||
date = LocalDate.of(year, month, dayOfMonth);
|
||||
if (dayOfWeek != -1) {
|
||||
date = nextOrSame(date, dayOfWeek);
|
||||
}
|
||||
}
|
||||
LocalDateTime ldt = LocalDateTime.of(date, time);
|
||||
if (endOfDay) {
|
||||
ldt = ldt.plusDays(1);
|
||||
}
|
||||
return ldt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
843
jdk/make/src/classes/build/tools/tzdb/TzdbZoneRulesProvider.java
Normal file
843
jdk/make/src/classes/build/tools/tzdb/TzdbZoneRulesProvider.java
Normal file
@ -0,0 +1,843 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
|
||||
package build.tools.tzdb;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.time.*;
|
||||
import java.time.Year;
|
||||
import java.time.chrono.IsoChronology;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.time.zone.ZoneOffsetTransition;
|
||||
import java.time.zone.ZoneOffsetTransitionRule;
|
||||
import java.time.zone.ZoneOffsetTransitionRule.TimeDefinition;
|
||||
import java.time.zone.ZoneRulesException;
|
||||
|
||||
/**
|
||||
* Compile and build time-zone rules from IANA timezone data
|
||||
*
|
||||
* @author Xueming Shen
|
||||
* @author Stephen Colebourne
|
||||
* @author Michael Nascimento Santos
|
||||
*
|
||||
* @since 1.9
|
||||
*/
|
||||
|
||||
class TzdbZoneRulesProvider {
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @throws ZoneRulesException if unable to load
|
||||
*/
|
||||
public TzdbZoneRulesProvider(List<Path> files) {
|
||||
try {
|
||||
load(files);
|
||||
} catch (Exception ex) {
|
||||
throw new ZoneRulesException("Unable to load TZDB time-zone rules", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getZoneIds() {
|
||||
return new TreeSet(regionIds);
|
||||
}
|
||||
|
||||
public Map<String, String> getAliasMap() {
|
||||
return links;
|
||||
}
|
||||
|
||||
public ZoneRules getZoneRules(String zoneId) {
|
||||
Object obj = zones.get(zoneId);
|
||||
if (obj == null) {
|
||||
String zoneId0 = zoneId;
|
||||
if (links.containsKey(zoneId)) {
|
||||
zoneId = links.get(zoneId);
|
||||
obj = zones.get(zoneId);
|
||||
}
|
||||
if (obj == null) {
|
||||
throw new ZoneRulesException("Unknown time-zone ID: " + zoneId0);
|
||||
}
|
||||
}
|
||||
if (obj instanceof ZoneRules) {
|
||||
return (ZoneRules)obj;
|
||||
}
|
||||
try {
|
||||
ZoneRules zrules = buildRules(zoneId, (List<ZoneLine>)obj);
|
||||
zones.put(zoneId, zrules);
|
||||
return zrules;
|
||||
} catch (Exception ex) {
|
||||
throw new ZoneRulesException(
|
||||
"Invalid binary time-zone data: TZDB:" + zoneId, ex);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* All the regions that are available.
|
||||
*/
|
||||
private List<String> regionIds = new ArrayList<>(600);
|
||||
|
||||
/**
|
||||
* Zone region to rules mapping
|
||||
*/
|
||||
private final Map<String, Object> zones = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* compatibility list
|
||||
*/
|
||||
private static HashSet<String> excludedZones;
|
||||
static {
|
||||
// (1) exclude EST, HST and MST. They are supported
|
||||
// via the short-id mapping
|
||||
// (2) remove UTC and GMT
|
||||
// (3) remove ROC, which is not supported in j.u.tz
|
||||
excludedZones = new HashSet<>(10);
|
||||
excludedZones.add("EST");
|
||||
excludedZones.add("HST");
|
||||
excludedZones.add("MST");
|
||||
excludedZones.add("GMT+0");
|
||||
excludedZones.add("GMT-0");
|
||||
excludedZones.add("ROC");
|
||||
}
|
||||
|
||||
private Map<String, String> links = new HashMap<>(150);
|
||||
private Map<String, List<RuleLine>> rules = new HashMap<>(500);
|
||||
|
||||
private void load(List<Path> files) throws IOException {
|
||||
|
||||
for (Path file : files) {
|
||||
List<ZoneLine> openZone = null;
|
||||
try {
|
||||
for (String line : Files.readAllLines(file, StandardCharsets.ISO_8859_1)) {
|
||||
if (line.length() == 0 || line.charAt(0) == '#') {
|
||||
continue;
|
||||
}
|
||||
//StringIterator itr = new StringIterator(line);
|
||||
String[] tokens = split(line);
|
||||
if (openZone != null && // continuing zone line
|
||||
Character.isWhitespace(line.charAt(0)) &&
|
||||
tokens.length > 0) {
|
||||
ZoneLine zLine = new ZoneLine();
|
||||
openZone.add(zLine);
|
||||
if (zLine.parse(tokens, 0)) {
|
||||
openZone = null;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("Zone")) { // parse Zone line
|
||||
String name = tokens[1];
|
||||
if (excludedZones.contains(name)){
|
||||
continue;
|
||||
}
|
||||
if (zones.containsKey(name)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Duplicated zone name in file: " + name +
|
||||
", line: [" + line + "]");
|
||||
}
|
||||
openZone = new ArrayList<>(10);
|
||||
zones.put(name, openZone);
|
||||
regionIds.add(name);
|
||||
ZoneLine zLine = new ZoneLine();
|
||||
openZone.add(zLine);
|
||||
if (zLine.parse(tokens, 2)) {
|
||||
openZone = null;
|
||||
}
|
||||
} else if (line.startsWith("Rule")) { // parse Rule line
|
||||
String name = tokens[1];
|
||||
if (!rules.containsKey(name)) {
|
||||
rules.put(name, new ArrayList<RuleLine>(10));
|
||||
}
|
||||
rules.get(name).add(new RuleLine().parse(tokens));
|
||||
} else if (line.startsWith("Link")) { // parse link line
|
||||
if (tokens.length >= 3) {
|
||||
String realId = tokens[1];
|
||||
String aliasId = tokens[2];
|
||||
if (excludedZones.contains(aliasId)){
|
||||
continue;
|
||||
}
|
||||
links.put(aliasId, realId);
|
||||
regionIds.add(aliasId);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid Link line in file" +
|
||||
file + ", line: [" + line + "]");
|
||||
}
|
||||
} else {
|
||||
// skip unknown line
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("Failed while processing file [" + file +
|
||||
"]", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String[] split(String str) {
|
||||
int off = 0;
|
||||
int end = str.length();
|
||||
ArrayList<String> list = new ArrayList<>(10);
|
||||
while (off < end) {
|
||||
char c = str.charAt(off);
|
||||
if (c == '\t' || c == ' ') {
|
||||
off++;
|
||||
continue;
|
||||
}
|
||||
if (c == '#') { // comment
|
||||
break;
|
||||
}
|
||||
int start = off;
|
||||
while (off < end) {
|
||||
c = str.charAt(off);
|
||||
if (c == ' ' || c == '\t') {
|
||||
break;
|
||||
}
|
||||
off++;
|
||||
}
|
||||
if (start != off) {
|
||||
list.add(str.substring(start, off));
|
||||
}
|
||||
}
|
||||
return list.toArray(new String[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a month-day-time in the TZDB file.
|
||||
*/
|
||||
private static abstract class MonthDayTime {
|
||||
/** The month of the cutover. */
|
||||
Month month = Month.JANUARY;
|
||||
|
||||
/** The day-of-month of the cutover. */
|
||||
int dayOfMonth = 1;
|
||||
|
||||
/** Whether to adjust forwards. */
|
||||
boolean adjustForwards = true;
|
||||
|
||||
/** The day-of-week of the cutover. */
|
||||
DayOfWeek dayOfWeek;
|
||||
|
||||
/** The time of the cutover, in second of day */
|
||||
int secsOfDay = 0;
|
||||
|
||||
/** Whether this is midnight end of day. */
|
||||
boolean endOfDay;
|
||||
/** The time of the cutover. */
|
||||
|
||||
TimeDefinition timeDefinition = TimeDefinition.WALL;
|
||||
|
||||
void adjustToForwards(int year) {
|
||||
if (adjustForwards == false && dayOfMonth > 0) {
|
||||
// weekDay<=monthDay case, don't have it in tzdb data for now
|
||||
LocalDate adjustedDate = LocalDate.of(year, month, dayOfMonth).minusDays(6);
|
||||
dayOfMonth = adjustedDate.getDayOfMonth();
|
||||
month = adjustedDate.getMonth();
|
||||
adjustForwards = true;
|
||||
}
|
||||
}
|
||||
|
||||
LocalDateTime toDateTime(int year) {
|
||||
LocalDate date;
|
||||
if (dayOfMonth < 0) {
|
||||
int monthLen = month.length(IsoChronology.INSTANCE.isLeapYear(year));
|
||||
date = LocalDate.of(year, month, monthLen + 1 + dayOfMonth);
|
||||
if (dayOfWeek != null) {
|
||||
date = date.with(TemporalAdjusters.previousOrSame(dayOfWeek));
|
||||
}
|
||||
} else {
|
||||
date = LocalDate.of(year, month, dayOfMonth);
|
||||
if (dayOfWeek != null) {
|
||||
date = date.with(TemporalAdjusters.nextOrSame(dayOfWeek));
|
||||
}
|
||||
}
|
||||
if (endOfDay) {
|
||||
date = date.plusDays(1);
|
||||
}
|
||||
return LocalDateTime.of(date, LocalTime.ofSecondOfDay(secsOfDay));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the MonthDaytime segment of a tzdb line.
|
||||
*/
|
||||
private void parse(String[] tokens, int off) {
|
||||
month = parseMonth(tokens[off++]);
|
||||
if (off < tokens.length) {
|
||||
String dayRule = tokens[off++];
|
||||
if (dayRule.startsWith("last")) {
|
||||
dayOfMonth = -1;
|
||||
dayOfWeek = parseDayOfWeek(dayRule.substring(4));
|
||||
adjustForwards = false;
|
||||
} else {
|
||||
int index = dayRule.indexOf(">=");
|
||||
if (index > 0) {
|
||||
dayOfWeek = parseDayOfWeek(dayRule.substring(0, index));
|
||||
dayRule = dayRule.substring(index + 2);
|
||||
} else {
|
||||
index = dayRule.indexOf("<=");
|
||||
if (index > 0) {
|
||||
dayOfWeek = parseDayOfWeek(dayRule.substring(0, index));
|
||||
adjustForwards = false;
|
||||
dayRule = dayRule.substring(index + 2);
|
||||
}
|
||||
}
|
||||
dayOfMonth = Integer.parseInt(dayRule);
|
||||
if (dayOfMonth < -28 || dayOfMonth > 31 || dayOfMonth == 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Day of month indicator must be between -28 and 31 inclusive excluding zero");
|
||||
}
|
||||
}
|
||||
if (off < tokens.length) {
|
||||
String timeStr = tokens[off++];
|
||||
secsOfDay = parseSecs(timeStr);
|
||||
if (secsOfDay == 86400) {
|
||||
// time must be midnight when end of day flag is true
|
||||
endOfDay = true;
|
||||
secsOfDay = 0;
|
||||
}
|
||||
timeDefinition = parseTimeDefinition(timeStr.charAt(timeStr.length() - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int parseYear(String year, int defaultYear) {
|
||||
switch (year.toLowerCase()) {
|
||||
case "min": return 1900;
|
||||
case "max": return Year.MAX_VALUE;
|
||||
case "only": return defaultYear;
|
||||
}
|
||||
return Integer.parseInt(year);
|
||||
}
|
||||
|
||||
Month parseMonth(String mon) {
|
||||
switch (mon) {
|
||||
case "Jan": return Month.JANUARY;
|
||||
case "Feb": return Month.FEBRUARY;
|
||||
case "Mar": return Month.MARCH;
|
||||
case "Apr": return Month.APRIL;
|
||||
case "May": return Month.MAY;
|
||||
case "Jun": return Month.JUNE;
|
||||
case "Jul": return Month.JULY;
|
||||
case "Aug": return Month.AUGUST;
|
||||
case "Sep": return Month.SEPTEMBER;
|
||||
case "Oct": return Month.OCTOBER;
|
||||
case "Nov": return Month.NOVEMBER;
|
||||
case "Dec": return Month.DECEMBER;
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown month: " + mon);
|
||||
}
|
||||
|
||||
DayOfWeek parseDayOfWeek(String dow) {
|
||||
switch (dow) {
|
||||
case "Mon": return DayOfWeek.MONDAY;
|
||||
case "Tue": return DayOfWeek.TUESDAY;
|
||||
case "Wed": return DayOfWeek.WEDNESDAY;
|
||||
case "Thu": return DayOfWeek.THURSDAY;
|
||||
case "Fri": return DayOfWeek.FRIDAY;
|
||||
case "Sat": return DayOfWeek.SATURDAY;
|
||||
case "Sun": return DayOfWeek.SUNDAY;
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown day-of-week: " + dow);
|
||||
}
|
||||
|
||||
String parseOptional(String str) {
|
||||
return str.equals("-") ? null : str;
|
||||
}
|
||||
|
||||
static final boolean isDigit(char c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
private int parseSecs(String time) {
|
||||
if (time.equals("-")) {
|
||||
return 0;
|
||||
}
|
||||
// faster hack
|
||||
int secs = 0;
|
||||
int sign = 1;
|
||||
int off = 0;
|
||||
int len = time.length();
|
||||
if (off < len && time.charAt(off) == '-') {
|
||||
sign = -1;
|
||||
off++;
|
||||
}
|
||||
char c0, c1;
|
||||
if (off < len && isDigit(c0 = time.charAt(off++))) {
|
||||
int hour = c0 - '0';
|
||||
if (off < len && isDigit(c1 = time.charAt(off))) {
|
||||
hour = hour * 10 + c1 - '0';
|
||||
off++;
|
||||
}
|
||||
secs = hour * 60 * 60;
|
||||
if (off < len && time.charAt(off++) == ':') {
|
||||
if (off + 1 < len &&
|
||||
isDigit(c0 = time.charAt(off++)) &&
|
||||
isDigit(c1 = time.charAt(off++))) {
|
||||
// minutes
|
||||
secs += ((c0 - '0') * 10 + c1 - '0') * 60;
|
||||
if (off < len && time.charAt(off++) == ':') {
|
||||
if (off + 1 < len &&
|
||||
isDigit(c0 = time.charAt(off++)) &&
|
||||
isDigit(c1 = time.charAt(off++))) {
|
||||
// seconds
|
||||
secs += ((c0 - '0') * 10 + c1 - '0');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return secs * sign;
|
||||
}
|
||||
throw new IllegalArgumentException("[" + time + "]");
|
||||
}
|
||||
|
||||
int parseOffset(String str) {
|
||||
int secs = parseSecs(str);
|
||||
if (Math.abs(secs) > 18 * 60 * 60) {
|
||||
throw new IllegalArgumentException(
|
||||
"Zone offset not in valid range: -18:00 to +18:00");
|
||||
}
|
||||
return secs;
|
||||
}
|
||||
|
||||
int parsePeriod(String str) {
|
||||
return parseSecs(str);
|
||||
}
|
||||
|
||||
TimeDefinition parseTimeDefinition(char c) {
|
||||
switch (c) {
|
||||
case 's':
|
||||
case 'S':
|
||||
// standard time
|
||||
return TimeDefinition.STANDARD;
|
||||
case 'u':
|
||||
case 'U':
|
||||
case 'g':
|
||||
case 'G':
|
||||
case 'z':
|
||||
case 'Z':
|
||||
// UTC
|
||||
return TimeDefinition.UTC;
|
||||
case 'w':
|
||||
case 'W':
|
||||
default:
|
||||
// wall time
|
||||
return TimeDefinition.WALL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a rule line in the TZDB file.
|
||||
*/
|
||||
private static class RuleLine extends MonthDayTime {
|
||||
/** The start year. */
|
||||
int startYear;
|
||||
|
||||
/** The end year. */
|
||||
int endYear;
|
||||
|
||||
/** The amount of savings, in seconds. */
|
||||
int savingsAmount;
|
||||
|
||||
/** The text name of the zone. */
|
||||
String text;
|
||||
|
||||
/**
|
||||
* Converts this to a transition rule.
|
||||
*
|
||||
* @param standardOffset the active standard offset, not null
|
||||
* @param savingsBeforeSecs the active savings before the transition in seconds
|
||||
* @return the transition, not null
|
||||
*/
|
||||
ZoneOffsetTransitionRule toTransitionRule(ZoneOffset stdOffset, int savingsBefore) {
|
||||
// rule shared by different zones, so don't change it
|
||||
Month month = this.month;
|
||||
int dayOfMonth = this.dayOfMonth;
|
||||
DayOfWeek dayOfWeek = this.dayOfWeek;
|
||||
boolean endOfDay = this.endOfDay;
|
||||
|
||||
// optimize stored format
|
||||
if (dayOfMonth < 0) {
|
||||
if (month != Month.FEBRUARY) { // not Month.FEBRUARY
|
||||
dayOfMonth = month.maxLength() - 6;
|
||||
}
|
||||
}
|
||||
if (endOfDay && dayOfMonth > 0 &&
|
||||
(dayOfMonth == 28 && month == Month.FEBRUARY) == false) {
|
||||
LocalDate date = LocalDate.of(2004, month, dayOfMonth).plusDays(1); // leap-year
|
||||
month = date.getMonth();
|
||||
dayOfMonth = date.getDayOfMonth();
|
||||
if (dayOfWeek != null) {
|
||||
dayOfWeek = dayOfWeek.plus(1);
|
||||
}
|
||||
endOfDay = false;
|
||||
}
|
||||
// build rule
|
||||
return ZoneOffsetTransitionRule.of(
|
||||
//month, dayOfMonth, dayOfWeek, time, endOfDay, timeDefinition,
|
||||
month, dayOfMonth, dayOfWeek,
|
||||
LocalTime.ofSecondOfDay(secsOfDay), endOfDay, timeDefinition,
|
||||
stdOffset,
|
||||
ZoneOffset.ofTotalSeconds(stdOffset.getTotalSeconds() + savingsBefore),
|
||||
ZoneOffset.ofTotalSeconds(stdOffset.getTotalSeconds() + savingsAmount));
|
||||
}
|
||||
|
||||
RuleLine parse(String[] tokens) {
|
||||
startYear = parseYear(tokens[2], 0);
|
||||
endYear = parseYear(tokens[3], startYear);
|
||||
if (startYear > endYear) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid <Rule> line/Year order invalid:" + startYear + " > " + endYear);
|
||||
}
|
||||
//parseOptional(s.next()); // type is unused
|
||||
super.parse(tokens, 5); // monthdaytime parsing
|
||||
savingsAmount = parsePeriod(tokens[8]);
|
||||
//rule.text = parseOptional(s.next());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a linked set of zone lines in the TZDB file.
|
||||
*/
|
||||
private static class ZoneLine extends MonthDayTime {
|
||||
/** The standard offset. */
|
||||
int stdOffsetSecs;
|
||||
|
||||
/** The fixed savings amount. */
|
||||
int fixedSavingsSecs = 0;
|
||||
|
||||
/** The savings rule. */
|
||||
String savingsRule;
|
||||
|
||||
/** The text name of the zone. */
|
||||
String text;
|
||||
|
||||
/** The cutover year */
|
||||
int year = Year.MAX_VALUE;
|
||||
|
||||
/** The cutover date time */
|
||||
LocalDateTime ldt;
|
||||
|
||||
/** The cutover date/time in epoch seconds/UTC */
|
||||
long ldtSecs = Long.MIN_VALUE;
|
||||
|
||||
LocalDateTime toDateTime() {
|
||||
if (ldt == null) {
|
||||
ldt = toDateTime(year);
|
||||
}
|
||||
return ldt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the date-time epoch second in the wall offset for the local
|
||||
* date-time at the end of the window.
|
||||
*
|
||||
* @param savingsSecs the amount of savings in use in seconds
|
||||
* @return the created date-time epoch second in the wall offset, not null
|
||||
*/
|
||||
long toDateTimeEpochSecond(int savingsSecs) {
|
||||
if (ldtSecs == Long.MIN_VALUE) {
|
||||
ldtSecs = toDateTime().toEpochSecond(ZoneOffset.UTC);
|
||||
}
|
||||
switch(timeDefinition) {
|
||||
case UTC: return ldtSecs;
|
||||
case STANDARD: return ldtSecs - stdOffsetSecs;
|
||||
default: return ldtSecs - (stdOffsetSecs + savingsSecs); // WALL
|
||||
}
|
||||
}
|
||||
|
||||
boolean parse(String[] tokens, int off) {
|
||||
stdOffsetSecs = parseOffset(tokens[off++]);
|
||||
savingsRule = parseOptional(tokens[off++]);
|
||||
if (savingsRule != null && savingsRule.length() > 0 &&
|
||||
(savingsRule.charAt(0) == '-' || isDigit(savingsRule.charAt(0)))) {
|
||||
try {
|
||||
fixedSavingsSecs = parsePeriod(savingsRule);
|
||||
savingsRule = null;
|
||||
} catch (Exception ex) {
|
||||
fixedSavingsSecs = 0;
|
||||
}
|
||||
}
|
||||
text = tokens[off++];
|
||||
if (off < tokens.length) {
|
||||
year = Integer.parseInt(tokens[off++]);
|
||||
if (off < tokens.length) {
|
||||
super.parse(tokens, off); // MonthDayTime
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a rule line in the TZDB file for a particular year.
|
||||
*/
|
||||
private static class TransRule implements Comparable<TransRule>
|
||||
{
|
||||
private int year;
|
||||
private RuleLine rule;
|
||||
|
||||
/** The trans date/time */
|
||||
private LocalDateTime ldt;
|
||||
|
||||
/** The trans date/time in epoch seconds (assume UTC) */
|
||||
long ldtSecs;
|
||||
|
||||
TransRule(int year, RuleLine rule) {
|
||||
this.year = year;
|
||||
this.rule = rule;
|
||||
this.ldt = rule.toDateTime(year);
|
||||
this.ldtSecs = ldt.toEpochSecond(ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
ZoneOffsetTransition toTransition(ZoneOffset standardOffset, int savingsBeforeSecs) {
|
||||
// copy of code in ZoneOffsetTransitionRule to avoid infinite loop
|
||||
ZoneOffset wallOffset = ZoneOffset.ofTotalSeconds(
|
||||
standardOffset.getTotalSeconds() + savingsBeforeSecs);
|
||||
ZoneOffset offsetAfter = ZoneOffset.ofTotalSeconds(
|
||||
standardOffset.getTotalSeconds() + rule.savingsAmount);
|
||||
LocalDateTime dt = rule.timeDefinition
|
||||
.createDateTime(ldt, standardOffset, wallOffset);
|
||||
return ZoneOffsetTransition.of(dt, wallOffset, offsetAfter);
|
||||
}
|
||||
|
||||
long toEpochSecond(ZoneOffset stdOffset, int savingsBeforeSecs) {
|
||||
switch(rule.timeDefinition) {
|
||||
case UTC: return ldtSecs;
|
||||
case STANDARD: return ldtSecs - stdOffset.getTotalSeconds();
|
||||
default: return ldtSecs - (stdOffset.getTotalSeconds() + savingsBeforeSecs); // WALL
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if this a real transition with the active savings in seconds
|
||||
*
|
||||
* @param savingsBefore the active savings in seconds
|
||||
* @return true, if savings changes
|
||||
*/
|
||||
boolean isTransition(int savingsBefore) {
|
||||
return rule.savingsAmount != savingsBefore;
|
||||
}
|
||||
|
||||
public int compareTo(TransRule other) {
|
||||
return (ldtSecs < other.ldtSecs)? -1 : ((ldtSecs == other.ldtSecs) ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
private ZoneRules buildRules(String zoneId, List<ZoneLine> zones) {
|
||||
if (zones.isEmpty()) {
|
||||
throw new IllegalStateException("No available zone window");
|
||||
}
|
||||
final List<ZoneOffsetTransition> standardTransitionList = new ArrayList<>(4);
|
||||
final List<ZoneOffsetTransition> transitionList = new ArrayList<>(256);
|
||||
final List<ZoneOffsetTransitionRule> lastTransitionRuleList = new ArrayList<>(2);
|
||||
|
||||
final ZoneLine zone0 = zones.get(0);
|
||||
// initialize the standard offset, wallOffset and savings for loop
|
||||
|
||||
//ZoneOffset stdOffset = zone0.standardOffset;
|
||||
ZoneOffset stdOffset = ZoneOffset.ofTotalSeconds(zone0.stdOffsetSecs);
|
||||
|
||||
int savings = zone0.fixedSavingsSecs;
|
||||
ZoneOffset wallOffset = ZoneOffset.ofTotalSeconds(stdOffset.getTotalSeconds() + savings);
|
||||
|
||||
// start ldt of each zone window
|
||||
LocalDateTime zoneStart = LocalDateTime.MIN;
|
||||
|
||||
// first stanard offset
|
||||
ZoneOffset firstStdOffset = stdOffset;
|
||||
// first wall offset
|
||||
ZoneOffset firstWallOffset = wallOffset;
|
||||
|
||||
for (ZoneLine zone : zones) {
|
||||
// check if standard offset changed, update it if yes
|
||||
ZoneOffset stdOffsetPrev = stdOffset; // for effectiveSavings check
|
||||
if (zone.stdOffsetSecs != stdOffset.getTotalSeconds()) {
|
||||
ZoneOffset stdOffsetNew = ZoneOffset.ofTotalSeconds(zone.stdOffsetSecs);
|
||||
standardTransitionList.add(
|
||||
ZoneOffsetTransition.of(
|
||||
LocalDateTime.ofEpochSecond(zoneStart.toEpochSecond(wallOffset),
|
||||
0,
|
||||
stdOffset),
|
||||
stdOffset,
|
||||
stdOffsetNew));
|
||||
stdOffset = stdOffsetNew;
|
||||
}
|
||||
|
||||
LocalDateTime zoneEnd;
|
||||
if (zone.year == Year.MAX_VALUE) {
|
||||
zoneEnd = LocalDateTime.MAX;
|
||||
} else {
|
||||
zoneEnd = zone.toDateTime();
|
||||
}
|
||||
if (zoneEnd.compareTo(zoneStart) < 0) {
|
||||
throw new IllegalStateException("Windows must be in date-time order: " +
|
||||
zoneEnd + " < " + zoneStart);
|
||||
}
|
||||
// calculate effective savings at the start of the window
|
||||
List<TransRule> trules = null;
|
||||
List<TransRule> lastRules = null;
|
||||
|
||||
int effectiveSavings = zone.fixedSavingsSecs;
|
||||
if (zone.savingsRule != null) {
|
||||
List<RuleLine> tzdbRules = rules.get(zone.savingsRule);
|
||||
if (tzdbRules == null) {
|
||||
throw new IllegalArgumentException("<Rule> not found: " +
|
||||
zone.savingsRule);
|
||||
}
|
||||
trules = new ArrayList<>(256);
|
||||
lastRules = new ArrayList<>(2);
|
||||
int lastRulesStartYear = Year.MIN_VALUE;
|
||||
|
||||
// merge the rules to transitions
|
||||
for (RuleLine rule : tzdbRules) {
|
||||
if (rule.startYear > zoneEnd.getYear()) {
|
||||
// rules will not be used for this zone entry
|
||||
continue;
|
||||
}
|
||||
rule.adjustToForwards(2004); // irrelevant, treat as leap year
|
||||
|
||||
int startYear = rule.startYear;
|
||||
int endYear = rule.endYear;
|
||||
if (zoneEnd.equals(LocalDateTime.MAX)) {
|
||||
if (endYear == Year.MAX_VALUE) {
|
||||
endYear = startYear;
|
||||
lastRules.add(new TransRule(endYear, rule));
|
||||
lastRulesStartYear = Math.max(startYear, lastRulesStartYear);
|
||||
}
|
||||
} else {
|
||||
if (endYear == Year.MAX_VALUE) {
|
||||
//endYear = zoneEnd.getYear();
|
||||
endYear = zone.year;
|
||||
}
|
||||
}
|
||||
int year = startYear;
|
||||
while (year <= endYear) {
|
||||
trules.add(new TransRule(year, rule));
|
||||
year++;
|
||||
}
|
||||
}
|
||||
|
||||
// last rules, fill the gap years between different last rules
|
||||
if (zoneEnd.equals(LocalDateTime.MAX)) {
|
||||
lastRulesStartYear = Math.max(lastRulesStartYear, zoneStart.getYear()) + 1;
|
||||
for (TransRule rule : lastRules) {
|
||||
if (rule.year <= lastRulesStartYear) {
|
||||
int year = rule.year;
|
||||
while (year <= lastRulesStartYear) {
|
||||
trules.add(new TransRule(year, rule.rule));
|
||||
year++;
|
||||
}
|
||||
rule.year = lastRulesStartYear;
|
||||
rule.ldt = rule.rule.toDateTime(year);
|
||||
rule.ldtSecs = rule.ldt.toEpochSecond(ZoneOffset.UTC);
|
||||
}
|
||||
}
|
||||
Collections.sort(lastRules);
|
||||
}
|
||||
// sort the merged rules
|
||||
Collections.sort(trules);
|
||||
|
||||
effectiveSavings = 0;
|
||||
for (TransRule rule : trules) {
|
||||
if (rule.toEpochSecond(stdOffsetPrev, savings) >
|
||||
zoneStart.toEpochSecond(wallOffset)) {
|
||||
// previous savings amount found, which could be the
|
||||
// savings amount at the instant that the window starts
|
||||
// (hence isAfter)
|
||||
break;
|
||||
}
|
||||
effectiveSavings = rule.rule.savingsAmount;
|
||||
}
|
||||
}
|
||||
// check if the start of the window represents a transition
|
||||
ZoneOffset effectiveWallOffset =
|
||||
ZoneOffset.ofTotalSeconds(stdOffset.getTotalSeconds() + effectiveSavings);
|
||||
|
||||
if (!wallOffset.equals(effectiveWallOffset)) {
|
||||
transitionList.add(ZoneOffsetTransition.of(zoneStart,
|
||||
wallOffset,
|
||||
effectiveWallOffset));
|
||||
}
|
||||
savings = effectiveSavings;
|
||||
// apply rules within the window
|
||||
if (trules != null) {
|
||||
long zoneStartEpochSecs = zoneStart.toEpochSecond(wallOffset);
|
||||
for (TransRule trule : trules) {
|
||||
if (trule.isTransition(savings)) {
|
||||
long epochSecs = trule.toEpochSecond(stdOffset, savings);
|
||||
if (epochSecs < zoneStartEpochSecs ||
|
||||
epochSecs >= zone.toDateTimeEpochSecond(savings)) {
|
||||
continue;
|
||||
}
|
||||
transitionList.add(trule.toTransition(stdOffset, savings));
|
||||
savings = trule.rule.savingsAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastRules != null) {
|
||||
for (TransRule trule : lastRules) {
|
||||
lastTransitionRuleList.add(trule.rule.toTransitionRule(stdOffset, savings));
|
||||
savings = trule.rule.savingsAmount;
|
||||
}
|
||||
}
|
||||
|
||||
// finally we can calculate the true end of the window, passing it to the next window
|
||||
wallOffset = ZoneOffset.ofTotalSeconds(stdOffset.getTotalSeconds() + savings);
|
||||
zoneStart = LocalDateTime.ofEpochSecond(zone.toDateTimeEpochSecond(savings),
|
||||
0,
|
||||
wallOffset);
|
||||
}
|
||||
return new ZoneRules(firstStdOffset,
|
||||
firstWallOffset,
|
||||
standardTransitionList,
|
||||
transitionList,
|
||||
lastTransitionRuleList);
|
||||
}
|
||||
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
/*
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of JSR-310 nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package build.tools.tzdb;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
class Utils {
|
||||
|
||||
// Returns the largest (closest to positive infinity)
|
||||
public static long floorDiv(long x, long y) {
|
||||
long r = x / y;
|
||||
// if the signs are different and modulo not zero, round down
|
||||
if ((x ^ y) < 0 && (r * y != x)) {
|
||||
r--;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Returns the floor modulus of the {@code long} arguments.
|
||||
public static long floorMod(long x, long y) {
|
||||
return x - floorDiv(x, y) * y;
|
||||
}
|
||||
|
||||
// Returns the sum of its arguments,
|
||||
public static long addExact(long x, long y) {
|
||||
long r = x + y;
|
||||
// HD 2-12 Overflow iff both arguments have the opposite sign of the result
|
||||
if (((x ^ r) & (y ^ r)) < 0) {
|
||||
throw new ArithmeticException("long overflow");
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Year
|
||||
|
||||
// Returns true if the specified year is a leap year.
|
||||
public static boolean isLeapYear(int year) {
|
||||
return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
|
||||
}
|
||||
|
||||
// The minimum supported year, '-999,999,999'.
|
||||
public static final int YEAR_MIN_VALUE = -999_999_999;
|
||||
|
||||
// The maximum supported year, '+999,999,999'.
|
||||
public static final int YEAR_MAX_VALUE = 999_999_999;
|
||||
|
||||
|
||||
// Gets the length of the specified month in days.
|
||||
public static int lengthOfMonth(int month, boolean leapYear) {
|
||||
switch (month) {
|
||||
case 2: //FEBRUARY:
|
||||
return (leapYear ? 29 : 28);
|
||||
case 4: //APRIL:
|
||||
case 6: //JUNE:
|
||||
case 9: //SEPTEMBER:
|
||||
case 11: //NOVEMBER:
|
||||
return 30;
|
||||
default:
|
||||
return 31;
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the maximum length of the specified month in days.
|
||||
public static int maxLengthOfMonth(int month) {
|
||||
switch (month) {
|
||||
case 2: //FEBRUARY:
|
||||
return 29;
|
||||
case 4: //APRIL:
|
||||
case 6: //JUNE:
|
||||
case 9: //SEPTEMBER:
|
||||
case 11: //NOVEMBER:
|
||||
return 30;
|
||||
default:
|
||||
return 31;
|
||||
}
|
||||
}
|
||||
|
||||
// DayOfWeek
|
||||
|
||||
// Returns the day-of-week that is the specified number of days after
|
||||
// this one, from 1 to 7 for Monday to Sunday.
|
||||
public static int plusDayOfWeek(int dow, long days) {
|
||||
int amount = (int) (days % 7);
|
||||
return (dow - 1 + (amount + 7)) % 7 + 1;
|
||||
}
|
||||
|
||||
// Returns the day-of-week that is the specified number of days before
|
||||
// this one, from 1 to 7 for Monday to Sunday.
|
||||
public static int minusDayOfWeek(int dow, long days) {
|
||||
return plusDayOfWeek(dow, -(days % 7));
|
||||
}
|
||||
|
||||
// Adjusts the date to the first occurrence of the specified day-of-week
|
||||
// before the date being adjusted unless it is already on that day in
|
||||
// which case the same object is returned.
|
||||
public static LocalDate previousOrSame(LocalDate date, int dayOfWeek) {
|
||||
return adjust(date, dayOfWeek, 1);
|
||||
}
|
||||
|
||||
// Adjusts the date to the first occurrence of the specified day-of-week
|
||||
// after the date being adjusted unless it is already on that day in
|
||||
// which case the same object is returned.
|
||||
public static LocalDate nextOrSame(LocalDate date, int dayOfWeek) {
|
||||
return adjust(date, dayOfWeek, 0);
|
||||
}
|
||||
|
||||
// Implementation of next, previous or current day-of-week.
|
||||
// @param relative whether the current date is a valid answer
|
||||
private static final LocalDate adjust(LocalDate date, int dow, int relative) {
|
||||
int calDow = date.getDayOfWeek();
|
||||
if (relative < 2 && calDow == dow) {
|
||||
return date;
|
||||
}
|
||||
if ((relative & 1) == 0) {
|
||||
int daysDiff = calDow - dow;
|
||||
return date.plusDays(daysDiff >= 0 ? 7 - daysDiff : -daysDiff);
|
||||
} else {
|
||||
int daysDiff = dow - calDow;
|
||||
return date.minusDays(daysDiff >= 0 ? 7 - daysDiff : -daysDiff);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,474 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is available under and governed by the GNU General Public
|
||||
* License version 2 only, as published by the Free Software Foundation.
|
||||
* However, the following notice accompanied the original version of this
|
||||
* file:
|
||||
*
|
||||
* Copyright (c) 2007-2012, Stephen Colebourne & Michael Nascimento Santos
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of JSR-310 nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package build.tools.tzdb;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* A time-zone offset from Greenwich/UTC, such as {@code +02:00}.
|
||||
* <p>
|
||||
* A time-zone offset is the period of time that a time-zone differs from Greenwich/UTC.
|
||||
* This is usually a fixed number of hours and minutes.
|
||||
*
|
||||
* @since 1.8
|
||||
*/
|
||||
final class ZoneOffset implements Comparable<ZoneOffset> {
|
||||
|
||||
/** Cache of time-zone offset by offset in seconds. */
|
||||
private static final ConcurrentMap<Integer, ZoneOffset> SECONDS_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4);
|
||||
/** Cache of time-zone offset by ID. */
|
||||
private static final ConcurrentMap<String, ZoneOffset> ID_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4);
|
||||
|
||||
/**
|
||||
* The number of seconds per hour.
|
||||
*/
|
||||
private static final int SECONDS_PER_HOUR = 60 * 60;
|
||||
/**
|
||||
* The number of seconds per minute.
|
||||
*/
|
||||
private static final int SECONDS_PER_MINUTE = 60;
|
||||
/**
|
||||
* The number of minutes per hour.
|
||||
*/
|
||||
private static final int MINUTES_PER_HOUR = 60;
|
||||
/**
|
||||
* The abs maximum seconds.
|
||||
*/
|
||||
private static final int MAX_SECONDS = 18 * SECONDS_PER_HOUR;
|
||||
/**
|
||||
* Serialization version.
|
||||
*/
|
||||
private static final long serialVersionUID = 2357656521762053153L;
|
||||
|
||||
/**
|
||||
* The time-zone offset for UTC, with an ID of 'Z'.
|
||||
*/
|
||||
public static final ZoneOffset UTC = ZoneOffset.ofTotalSeconds(0);
|
||||
/**
|
||||
* Constant for the maximum supported offset.
|
||||
*/
|
||||
public static final ZoneOffset MIN = ZoneOffset.ofTotalSeconds(-MAX_SECONDS);
|
||||
/**
|
||||
* Constant for the maximum supported offset.
|
||||
*/
|
||||
public static final ZoneOffset MAX = ZoneOffset.ofTotalSeconds(MAX_SECONDS);
|
||||
|
||||
/**
|
||||
* The total offset in seconds.
|
||||
*/
|
||||
private final int totalSeconds;
|
||||
/**
|
||||
* The string form of the time-zone offset.
|
||||
*/
|
||||
private final transient String id;
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Obtains an instance of {@code ZoneOffset} using the ID.
|
||||
* <p>
|
||||
* This method parses the string ID of a {@code ZoneOffset} to
|
||||
* return an instance. The parsing accepts all the formats generated by
|
||||
* {@link #getId()}, plus some additional formats:
|
||||
* <p><ul>
|
||||
* <li>{@code Z} - for UTC
|
||||
* <li>{@code +h}
|
||||
* <li>{@code +hh}
|
||||
* <li>{@code +hh:mm}
|
||||
* <li>{@code -hh:mm}
|
||||
* <li>{@code +hhmm}
|
||||
* <li>{@code -hhmm}
|
||||
* <li>{@code +hh:mm:ss}
|
||||
* <li>{@code -hh:mm:ss}
|
||||
* <li>{@code +hhmmss}
|
||||
* <li>{@code -hhmmss}
|
||||
* </ul><p>
|
||||
* Note that ± means either the plus or minus symbol.
|
||||
* <p>
|
||||
* The ID of the returned offset will be normalized to one of the formats
|
||||
* described by {@link #getId()}.
|
||||
* <p>
|
||||
* The maximum supported range is from +18:00 to -18:00 inclusive.
|
||||
*
|
||||
* @param offsetId the offset ID, not null
|
||||
* @return the zone-offset, not null
|
||||
* @throws DateTimeException if the offset ID is invalid
|
||||
*/
|
||||
@SuppressWarnings("fallthrough")
|
||||
public static ZoneOffset of(String offsetId) {
|
||||
Objects.requireNonNull(offsetId, "offsetId");
|
||||
// "Z" is always in the cache
|
||||
ZoneOffset offset = ID_CACHE.get(offsetId);
|
||||
if (offset != null) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
// parse - +h, +hh, +hhmm, +hh:mm, +hhmmss, +hh:mm:ss
|
||||
final int hours, minutes, seconds;
|
||||
switch (offsetId.length()) {
|
||||
case 2:
|
||||
offsetId = offsetId.charAt(0) + "0" + offsetId.charAt(1); // fallthru
|
||||
case 3:
|
||||
hours = parseNumber(offsetId, 1, false);
|
||||
minutes = 0;
|
||||
seconds = 0;
|
||||
break;
|
||||
case 5:
|
||||
hours = parseNumber(offsetId, 1, false);
|
||||
minutes = parseNumber(offsetId, 3, false);
|
||||
seconds = 0;
|
||||
break;
|
||||
case 6:
|
||||
hours = parseNumber(offsetId, 1, false);
|
||||
minutes = parseNumber(offsetId, 4, true);
|
||||
seconds = 0;
|
||||
break;
|
||||
case 7:
|
||||
hours = parseNumber(offsetId, 1, false);
|
||||
minutes = parseNumber(offsetId, 3, false);
|
||||
seconds = parseNumber(offsetId, 5, false);
|
||||
break;
|
||||
case 9:
|
||||
hours = parseNumber(offsetId, 1, false);
|
||||
minutes = parseNumber(offsetId, 4, true);
|
||||
seconds = parseNumber(offsetId, 7, true);
|
||||
break;
|
||||
default:
|
||||
throw new DateTimeException("Zone offset ID '" + offsetId + "' is invalid");
|
||||
}
|
||||
char first = offsetId.charAt(0);
|
||||
if (first != '+' && first != '-') {
|
||||
throw new DateTimeException("Zone offset ID '" + offsetId + "' is invalid: Plus/minus not found when expected");
|
||||
}
|
||||
if (first == '-') {
|
||||
return ofHoursMinutesSeconds(-hours, -minutes, -seconds);
|
||||
} else {
|
||||
return ofHoursMinutesSeconds(hours, minutes, seconds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a two digit zero-prefixed number.
|
||||
*
|
||||
* @param offsetId the offset ID, not null
|
||||
* @param pos the position to parse, valid
|
||||
* @param precededByColon should this number be prefixed by a precededByColon
|
||||
* @return the parsed number, from 0 to 99
|
||||
*/
|
||||
private static int parseNumber(CharSequence offsetId, int pos, boolean precededByColon) {
|
||||
if (precededByColon && offsetId.charAt(pos - 1) != ':') {
|
||||
throw new DateTimeException("Zone offset ID '" + offsetId + "' is invalid: Colon not found when expected");
|
||||
}
|
||||
char ch1 = offsetId.charAt(pos);
|
||||
char ch2 = offsetId.charAt(pos + 1);
|
||||
if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') {
|
||||
throw new DateTimeException("Zone offset ID '" + offsetId + "' is invalid: Non numeric characters found");
|
||||
}
|
||||
return (ch1 - 48) * 10 + (ch2 - 48);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Obtains an instance of {@code ZoneOffset} using an offset in hours.
|
||||
*
|
||||
* @param hours the time-zone offset in hours, from -18 to +18
|
||||
* @return the zone-offset, not null
|
||||
* @throws DateTimeException if the offset is not in the required range
|
||||
*/
|
||||
public static ZoneOffset ofHours(int hours) {
|
||||
return ofHoursMinutesSeconds(hours, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an instance of {@code ZoneOffset} using an offset in
|
||||
* hours and minutes.
|
||||
* <p>
|
||||
* The sign of the hours and minutes components must match.
|
||||
* Thus, if the hours is negative, the minutes must be negative or zero.
|
||||
* If the hours is zero, the minutes may be positive, negative or zero.
|
||||
*
|
||||
* @param hours the time-zone offset in hours, from -18 to +18
|
||||
* @param minutes the time-zone offset in minutes, from 0 to ±59, sign matches hours
|
||||
* @return the zone-offset, not null
|
||||
* @throws DateTimeException if the offset is not in the required range
|
||||
*/
|
||||
public static ZoneOffset ofHoursMinutes(int hours, int minutes) {
|
||||
return ofHoursMinutesSeconds(hours, minutes, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an instance of {@code ZoneOffset} using an offset in
|
||||
* hours, minutes and seconds.
|
||||
* <p>
|
||||
* The sign of the hours, minutes and seconds components must match.
|
||||
* Thus, if the hours is negative, the minutes and seconds must be negative or zero.
|
||||
*
|
||||
* @param hours the time-zone offset in hours, from -18 to +18
|
||||
* @param minutes the time-zone offset in minutes, from 0 to ±59, sign matches hours and seconds
|
||||
* @param seconds the time-zone offset in seconds, from 0 to ±59, sign matches hours and minutes
|
||||
* @return the zone-offset, not null
|
||||
* @throws DateTimeException if the offset is not in the required range
|
||||
*/
|
||||
public static ZoneOffset ofHoursMinutesSeconds(int hours, int minutes, int seconds) {
|
||||
validate(hours, minutes, seconds);
|
||||
int totalSeconds = totalSeconds(hours, minutes, seconds);
|
||||
return ofTotalSeconds(totalSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the offset fields.
|
||||
*
|
||||
* @param hours the time-zone offset in hours, from -18 to +18
|
||||
* @param minutes the time-zone offset in minutes, from 0 to ±59
|
||||
* @param seconds the time-zone offset in seconds, from 0 to ±59
|
||||
* @throws DateTimeException if the offset is not in the required range
|
||||
*/
|
||||
private static void validate(int hours, int minutes, int seconds) {
|
||||
if (hours < -18 || hours > 18) {
|
||||
throw new DateTimeException("Zone offset hours not in valid range: value " + hours +
|
||||
" is not in the range -18 to 18");
|
||||
}
|
||||
if (hours > 0) {
|
||||
if (minutes < 0 || seconds < 0) {
|
||||
throw new DateTimeException("Zone offset minutes and seconds must be positive because hours is positive");
|
||||
}
|
||||
} else if (hours < 0) {
|
||||
if (minutes > 0 || seconds > 0) {
|
||||
throw new DateTimeException("Zone offset minutes and seconds must be negative because hours is negative");
|
||||
}
|
||||
} else if ((minutes > 0 && seconds < 0) || (minutes < 0 && seconds > 0)) {
|
||||
throw new DateTimeException("Zone offset minutes and seconds must have the same sign");
|
||||
}
|
||||
if (Math.abs(minutes) > 59) {
|
||||
throw new DateTimeException("Zone offset minutes not in valid range: abs(value) " +
|
||||
Math.abs(minutes) + " is not in the range 0 to 59");
|
||||
}
|
||||
if (Math.abs(seconds) > 59) {
|
||||
throw new DateTimeException("Zone offset seconds not in valid range: abs(value) " +
|
||||
Math.abs(seconds) + " is not in the range 0 to 59");
|
||||
}
|
||||
if (Math.abs(hours) == 18 && (Math.abs(minutes) > 0 || Math.abs(seconds) > 0)) {
|
||||
throw new DateTimeException("Zone offset not in valid range: -18:00 to +18:00");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total offset in seconds.
|
||||
*
|
||||
* @param hours the time-zone offset in hours, from -18 to +18
|
||||
* @param minutes the time-zone offset in minutes, from 0 to ±59, sign matches hours and seconds
|
||||
* @param seconds the time-zone offset in seconds, from 0 to ±59, sign matches hours and minutes
|
||||
* @return the total in seconds
|
||||
*/
|
||||
private static int totalSeconds(int hours, int minutes, int seconds) {
|
||||
return hours * SECONDS_PER_HOUR + minutes * SECONDS_PER_MINUTE + seconds;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Obtains an instance of {@code ZoneOffset} specifying the total offset in seconds
|
||||
* <p>
|
||||
* The offset must be in the range {@code -18:00} to {@code +18:00}, which corresponds to -64800 to +64800.
|
||||
*
|
||||
* @param totalSeconds the total time-zone offset in seconds, from -64800 to +64800
|
||||
* @return the ZoneOffset, not null
|
||||
* @throws DateTimeException if the offset is not in the required range
|
||||
*/
|
||||
public static ZoneOffset ofTotalSeconds(int totalSeconds) {
|
||||
if (Math.abs(totalSeconds) > MAX_SECONDS) {
|
||||
throw new DateTimeException("Zone offset not in valid range: -18:00 to +18:00");
|
||||
}
|
||||
if (totalSeconds % (15 * SECONDS_PER_MINUTE) == 0) {
|
||||
Integer totalSecs = totalSeconds;
|
||||
ZoneOffset result = SECONDS_CACHE.get(totalSecs);
|
||||
if (result == null) {
|
||||
result = new ZoneOffset(totalSeconds);
|
||||
SECONDS_CACHE.putIfAbsent(totalSecs, result);
|
||||
result = SECONDS_CACHE.get(totalSecs);
|
||||
ID_CACHE.putIfAbsent(result.getId(), result);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return new ZoneOffset(totalSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param totalSeconds the total time-zone offset in seconds, from -64800 to +64800
|
||||
*/
|
||||
private ZoneOffset(int totalSeconds) {
|
||||
super();
|
||||
this.totalSeconds = totalSeconds;
|
||||
id = buildId(totalSeconds);
|
||||
}
|
||||
|
||||
private static String buildId(int totalSeconds) {
|
||||
if (totalSeconds == 0) {
|
||||
return "Z";
|
||||
} else {
|
||||
int absTotalSeconds = Math.abs(totalSeconds);
|
||||
StringBuilder buf = new StringBuilder();
|
||||
int absHours = absTotalSeconds / SECONDS_PER_HOUR;
|
||||
int absMinutes = (absTotalSeconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR;
|
||||
buf.append(totalSeconds < 0 ? "-" : "+")
|
||||
.append(absHours < 10 ? "0" : "").append(absHours)
|
||||
.append(absMinutes < 10 ? ":0" : ":").append(absMinutes);
|
||||
int absSeconds = absTotalSeconds % SECONDS_PER_MINUTE;
|
||||
if (absSeconds != 0) {
|
||||
buf.append(absSeconds < 10 ? ":0" : ":").append(absSeconds);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total zone offset in seconds.
|
||||
* <p>
|
||||
* This is the primary way to access the offset amount.
|
||||
* It returns the total of the hours, minutes and seconds fields as a
|
||||
* single offset that can be added to a time.
|
||||
*
|
||||
* @return the total zone offset amount in seconds
|
||||
*/
|
||||
public int getTotalSeconds() {
|
||||
return totalSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the normalized zone offset ID.
|
||||
* <p>
|
||||
* The ID is minor variation to the standard ISO-8601 formatted string
|
||||
* for the offset. There are three formats:
|
||||
* <p><ul>
|
||||
* <li>{@code Z} - for UTC (ISO-8601)
|
||||
* <li>{@code +hh:mm} or {@code -hh:mm} - if the seconds are zero (ISO-8601)
|
||||
* <li>{@code +hh:mm:ss} or {@code -hh:mm:ss} - if the seconds are non-zero (not ISO-8601)
|
||||
* </ul><p>
|
||||
*
|
||||
* @return the zone offset ID, not null
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this offset to another offset in descending order.
|
||||
* <p>
|
||||
* The offsets are compared in the order that they occur for the same time
|
||||
* of day around the world. Thus, an offset of {@code +10:00} comes before an
|
||||
* offset of {@code +09:00} and so on down to {@code -18:00}.
|
||||
* <p>
|
||||
* The comparison is "consistent with equals", as defined by {@link Comparable}.
|
||||
*
|
||||
* @param other the other date to compare to, not null
|
||||
* @return the comparator value, negative if less, postive if greater
|
||||
* @throws NullPointerException if {@code other} is null
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(ZoneOffset other) {
|
||||
return other.totalSeconds - totalSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this offset is equal to another offset.
|
||||
* <p>
|
||||
* The comparison is based on the amount of the offset in seconds.
|
||||
* This is equivalent to a comparison by ID.
|
||||
*
|
||||
* @param obj the object to check, null returns false
|
||||
* @return true if this is equal to the other offset
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof ZoneOffset) {
|
||||
return totalSeconds == ((ZoneOffset) obj).totalSeconds;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* A hash code for this offset.
|
||||
*
|
||||
* @return a suitable hash code
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return totalSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs this offset as a {@code String}, using the normalized ID.
|
||||
*
|
||||
* @return a string representation of this offset, not null
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return id;
|
||||
}
|
||||
|
||||
}
|
@ -1,290 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is available under and governed by the GNU General Public
|
||||
* License version 2 only, as published by the Free Software Foundation.
|
||||
* However, the following notice accompanied the original version of this
|
||||
* file:
|
||||
*
|
||||
* Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of JSR-310 nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package build.tools.tzdb;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A transition between two offsets caused by a discontinuity in the local time-line.
|
||||
*
|
||||
* @since 1.8
|
||||
*/
|
||||
final class ZoneOffsetTransition implements Comparable<ZoneOffsetTransition> {
|
||||
|
||||
/**
|
||||
* The local transition date-time at the transition.
|
||||
*/
|
||||
private final LocalDateTime transition;
|
||||
/**
|
||||
* The offset before transition.
|
||||
*/
|
||||
private final ZoneOffset offsetBefore;
|
||||
/**
|
||||
* The offset after transition.
|
||||
*/
|
||||
private final ZoneOffset offsetAfter;
|
||||
|
||||
/**
|
||||
* Creates an instance defining a transition between two offsets.
|
||||
*
|
||||
* @param transition the transition date-time with the offset before the transition, not null
|
||||
* @param offsetBefore the offset before the transition, not null
|
||||
* @param offsetAfter the offset at and after the transition, not null
|
||||
*/
|
||||
ZoneOffsetTransition(LocalDateTime transition, ZoneOffset offsetBefore, ZoneOffset offsetAfter) {
|
||||
Objects.requireNonNull(transition, "transition");
|
||||
Objects.requireNonNull(offsetBefore, "offsetBefore");
|
||||
Objects.requireNonNull(offsetAfter, "offsetAfter");
|
||||
if (offsetBefore.equals(offsetAfter)) {
|
||||
throw new IllegalArgumentException("Offsets must not be equal");
|
||||
}
|
||||
this.transition = transition;
|
||||
this.offsetBefore = offsetBefore;
|
||||
this.offsetAfter = offsetAfter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance from epoch-second and offsets.
|
||||
*
|
||||
* @param epochSecond the transition epoch-second
|
||||
* @param offsetBefore the offset before the transition, not null
|
||||
* @param offsetAfter the offset at and after the transition, not null
|
||||
*/
|
||||
ZoneOffsetTransition(long epochSecond, ZoneOffset offsetBefore, ZoneOffset offsetAfter) {
|
||||
this.transition = LocalDateTime.ofEpochSecond(epochSecond, 0, offsetBefore);
|
||||
this.offsetBefore = offsetBefore;
|
||||
this.offsetAfter = offsetAfter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the transition instant as an epoch second.
|
||||
*
|
||||
* @return the transition epoch second
|
||||
*/
|
||||
public long toEpochSecond() {
|
||||
return transition.toEpochSecond(offsetBefore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the local transition date-time, as would be expressed with the 'before' offset.
|
||||
* <p>
|
||||
* This is the date-time where the discontinuity begins expressed with the 'before' offset.
|
||||
* At this instant, the 'after' offset is actually used, therefore the combination of this
|
||||
* date-time and the 'before' offset will never occur.
|
||||
* <p>
|
||||
* The combination of the 'before' date-time and offset represents the same instant
|
||||
* as the 'after' date-time and offset.
|
||||
*
|
||||
* @return the transition date-time expressed with the before offset, not null
|
||||
*/
|
||||
public LocalDateTime getDateTimeBefore() {
|
||||
return transition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the local transition date-time, as would be expressed with the 'after' offset.
|
||||
* <p>
|
||||
* This is the first date-time after the discontinuity, when the new offset applies.
|
||||
* <p>
|
||||
* The combination of the 'before' date-time and offset represents the same instant
|
||||
* as the 'after' date-time and offset.
|
||||
*
|
||||
* @return the transition date-time expressed with the after offset, not null
|
||||
*/
|
||||
public LocalDateTime getDateTimeAfter() {
|
||||
return transition.plusSeconds(getDurationSeconds());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the offset before the transition.
|
||||
* <p>
|
||||
* This is the offset in use before the instant of the transition.
|
||||
*
|
||||
* @return the offset before the transition, not null
|
||||
*/
|
||||
public ZoneOffset getOffsetBefore() {
|
||||
return offsetBefore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the offset after the transition.
|
||||
* <p>
|
||||
* This is the offset in use on and after the instant of the transition.
|
||||
*
|
||||
* @return the offset after the transition, not null
|
||||
*/
|
||||
public ZoneOffset getOffsetAfter() {
|
||||
return offsetAfter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the duration of the transition in seconds.
|
||||
*
|
||||
* @return the duration in seconds
|
||||
*/
|
||||
private int getDurationSeconds() {
|
||||
return getOffsetAfter().getTotalSeconds() - getOffsetBefore().getTotalSeconds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this transition represent a gap in the local time-line.
|
||||
* <p>
|
||||
* Gaps occur where there are local date-times that simply do not not exist.
|
||||
* An example would be when the offset changes from {@code +01:00} to {@code +02:00}.
|
||||
* This might be described as 'the clocks will move forward one hour tonight at 1am'.
|
||||
*
|
||||
* @return true if this transition is a gap, false if it is an overlap
|
||||
*/
|
||||
public boolean isGap() {
|
||||
return getOffsetAfter().getTotalSeconds() > getOffsetBefore().getTotalSeconds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this transition represent a gap in the local time-line.
|
||||
* <p>
|
||||
* Overlaps occur where there are local date-times that exist twice.
|
||||
* An example would be when the offset changes from {@code +02:00} to {@code +01:00}.
|
||||
* This might be described as 'the clocks will move back one hour tonight at 2am'.
|
||||
*
|
||||
* @return true if this transition is an overlap, false if it is a gap
|
||||
*/
|
||||
public boolean isOverlap() {
|
||||
return getOffsetAfter().getTotalSeconds() < getOffsetBefore().getTotalSeconds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified offset is valid during this transition.
|
||||
* <p>
|
||||
* This checks to see if the given offset will be valid at some point in the transition.
|
||||
* A gap will always return false.
|
||||
* An overlap will return true if the offset is either the before or after offset.
|
||||
*
|
||||
* @param offset the offset to check, null returns false
|
||||
* @return true if the offset is valid during the transition
|
||||
*/
|
||||
public boolean isValidOffset(ZoneOffset offset) {
|
||||
return isGap() ? false : (getOffsetBefore().equals(offset) || getOffsetAfter().equals(offset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the valid offsets during this transition.
|
||||
* <p>
|
||||
* A gap will return an empty list, while an overlap will return both offsets.
|
||||
*
|
||||
* @return the list of valid offsets
|
||||
*/
|
||||
List<ZoneOffset> getValidOffsets() {
|
||||
if (isGap()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Arrays.asList(getOffsetBefore(), getOffsetAfter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this transition to another based on the transition instant.
|
||||
* <p>
|
||||
* This compares the instants of each transition.
|
||||
* The offsets are ignored, making this order inconsistent with equals.
|
||||
*
|
||||
* @param transition the transition to compare to, not null
|
||||
* @return the comparator value, negative if less, positive if greater
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(ZoneOffsetTransition transition) {
|
||||
return Long.compare(this.toEpochSecond(), transition.toEpochSecond());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this object equals another.
|
||||
* <p>
|
||||
* The entire state of the object is compared.
|
||||
*
|
||||
* @param other the other object to compare to, null returns false
|
||||
* @return true if equal
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) {
|
||||
return true;
|
||||
}
|
||||
if (other instanceof ZoneOffsetTransition) {
|
||||
ZoneOffsetTransition d = (ZoneOffsetTransition) other;
|
||||
return transition.equals(d.transition) &&
|
||||
offsetBefore.equals(d.offsetBefore) && offsetAfter.equals(d.offsetAfter);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a suitable hash code.
|
||||
*
|
||||
* @return the hash code
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return transition.hashCode() ^ offsetBefore.hashCode() ^ Integer.rotateLeft(offsetAfter.hashCode(), 16);
|
||||
}
|
||||
|
||||
}
|
@ -1,223 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is available under and governed by the GNU General Public
|
||||
* License version 2 only, as published by the Free Software Foundation.
|
||||
* However, the following notice accompanied the original version of this
|
||||
* file:
|
||||
*
|
||||
* Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of JSR-310 nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package build.tools.tzdb;
|
||||
|
||||
import static build.tools.tzdb.Utils.*;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A rule expressing how to create a transition.
|
||||
* <p>
|
||||
* This class allows rules for identifying future transitions to be expressed.
|
||||
* A rule might be written in many forms:
|
||||
* <p><ul>
|
||||
* <li>the 16th March
|
||||
* <li>the Sunday on or after the 16th March
|
||||
* <li>the Sunday on or before the 16th March
|
||||
* <li>the last Sunday in February
|
||||
* </ul><p>
|
||||
* These different rule types can be expressed and queried.
|
||||
*
|
||||
* <h3>Specification for implementors</h3>
|
||||
* This class is immutable and thread-safe.
|
||||
*
|
||||
* @since 1.8
|
||||
*/
|
||||
final class ZoneOffsetTransitionRule {
|
||||
|
||||
/**
|
||||
* The month of the month-day of the first day of the cutover week.
|
||||
* The actual date will be adjusted by the dowChange field.
|
||||
*/
|
||||
final int month;
|
||||
/**
|
||||
* The day-of-month of the month-day of the cutover week.
|
||||
* If positive, it is the start of the week where the cutover can occur.
|
||||
* If negative, it represents the end of the week where cutover can occur.
|
||||
* The value is the number of days from the end of the month, such that
|
||||
* {@code -1} is the last day of the month, {@code -2} is the second
|
||||
* to last day, and so on.
|
||||
*/
|
||||
final byte dom;
|
||||
/**
|
||||
* The cutover day-of-week, -1 to retain the day-of-month.
|
||||
*/
|
||||
final int dow;
|
||||
/**
|
||||
* The cutover time in the 'before' offset.
|
||||
*/
|
||||
final LocalTime time;
|
||||
/**
|
||||
* Whether the cutover time is midnight at the end of day.
|
||||
*/
|
||||
final boolean timeEndOfDay;
|
||||
/**
|
||||
* The definition of how the local time should be interpreted.
|
||||
*/
|
||||
final TimeDefinition timeDefinition;
|
||||
/**
|
||||
* The standard offset at the cutover.
|
||||
*/
|
||||
final ZoneOffset standardOffset;
|
||||
/**
|
||||
* The offset before the cutover.
|
||||
*/
|
||||
final ZoneOffset offsetBefore;
|
||||
/**
|
||||
* The offset after the cutover.
|
||||
*/
|
||||
final ZoneOffset offsetAfter;
|
||||
|
||||
/**
|
||||
* Creates an instance defining the yearly rule to create transitions between two offsets.
|
||||
*
|
||||
* @param month the month of the month-day of the first day of the cutover week, from 1 to 12
|
||||
* @param dayOfMonthIndicator the day of the month-day of the cutover week, positive if the week is that
|
||||
* day or later, negative if the week is that day or earlier, counting from the last day of the month,
|
||||
* from -28 to 31 excluding 0
|
||||
* @param dayOfWeek the required day-of-week, -1 if the month-day should not be changed
|
||||
* @param time the cutover time in the 'before' offset, not null
|
||||
* @param timeEndOfDay whether the time is midnight at the end of day
|
||||
* @param timeDefnition how to interpret the cutover
|
||||
* @param standardOffset the standard offset in force at the cutover, not null
|
||||
* @param offsetBefore the offset before the cutover, not null
|
||||
* @param offsetAfter the offset after the cutover, not null
|
||||
* @throws IllegalArgumentException if the day of month indicator is invalid
|
||||
* @throws IllegalArgumentException if the end of day flag is true when the time is not midnight
|
||||
*/
|
||||
ZoneOffsetTransitionRule(
|
||||
int month,
|
||||
int dayOfMonthIndicator,
|
||||
int dayOfWeek,
|
||||
LocalTime time,
|
||||
boolean timeEndOfDay,
|
||||
TimeDefinition timeDefnition,
|
||||
ZoneOffset standardOffset,
|
||||
ZoneOffset offsetBefore,
|
||||
ZoneOffset offsetAfter) {
|
||||
Objects.requireNonNull(time, "time");
|
||||
Objects.requireNonNull(timeDefnition, "timeDefnition");
|
||||
Objects.requireNonNull(standardOffset, "standardOffset");
|
||||
Objects.requireNonNull(offsetBefore, "offsetBefore");
|
||||
Objects.requireNonNull(offsetAfter, "offsetAfter");
|
||||
if (month < 1 || month > 12) {
|
||||
throw new IllegalArgumentException("month must be between 1 and 12");
|
||||
}
|
||||
if (dayOfMonthIndicator < -28 || dayOfMonthIndicator > 31 || dayOfMonthIndicator == 0) {
|
||||
throw new IllegalArgumentException("Day of month indicator must be between -28 and 31 inclusive excluding zero");
|
||||
}
|
||||
if (timeEndOfDay && time.equals(LocalTime.MIDNIGHT) == false) {
|
||||
throw new IllegalArgumentException("Time must be midnight when end of day flag is true");
|
||||
}
|
||||
this.month = month;
|
||||
this.dom = (byte) dayOfMonthIndicator;
|
||||
this.dow = dayOfWeek;
|
||||
this.time = time;
|
||||
this.timeEndOfDay = timeEndOfDay;
|
||||
this.timeDefinition = timeDefnition;
|
||||
this.standardOffset = standardOffset;
|
||||
this.offsetBefore = offsetBefore;
|
||||
this.offsetAfter = offsetAfter;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Checks if this object equals another.
|
||||
* <p>
|
||||
* The entire state of the object is compared.
|
||||
*
|
||||
* @param otherRule the other object to compare to, null returns false
|
||||
* @return true if equal
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object otherRule) {
|
||||
if (otherRule == this) {
|
||||
return true;
|
||||
}
|
||||
if (otherRule instanceof ZoneOffsetTransitionRule) {
|
||||
ZoneOffsetTransitionRule other = (ZoneOffsetTransitionRule) otherRule;
|
||||
return month == other.month && dom == other.dom && dow == other.dow &&
|
||||
timeDefinition == other.timeDefinition &&
|
||||
time.equals(other.time) &&
|
||||
timeEndOfDay == other.timeEndOfDay &&
|
||||
standardOffset.equals(other.standardOffset) &&
|
||||
offsetBefore.equals(other.offsetBefore) &&
|
||||
offsetAfter.equals(other.offsetAfter);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a suitable hash code.
|
||||
*
|
||||
* @return the hash code
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = ((time.toSecondOfDay() + (timeEndOfDay ? 1 : 0)) << 15) +
|
||||
(month << 11) + ((dom + 32) << 5) +
|
||||
((dow == -1 ? 8 : dow) << 2) + (timeDefinition.ordinal());
|
||||
return hash ^ standardOffset.hashCode() ^
|
||||
offsetBefore.hashCode() ^ offsetAfter.hashCode();
|
||||
}
|
||||
|
||||
}
|
@ -64,6 +64,12 @@ package build.tools.tzdb;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutput;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.zone.ZoneOffsetTransition;
|
||||
import java.time.zone.ZoneOffsetTransitionRule;
|
||||
import java.time.zone.ZoneOffsetTransitionRule.TimeDefinition;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -224,15 +230,15 @@ final class ZoneRules {
|
||||
* @throws IOException if an error occurs
|
||||
*/
|
||||
static void writeRule(ZoneOffsetTransitionRule rule, DataOutput out) throws IOException {
|
||||
int month = rule.month;
|
||||
byte dom = rule.dom;
|
||||
int dow = rule.dow;
|
||||
LocalTime time = rule.time;
|
||||
boolean timeEndOfDay = rule.timeEndOfDay;
|
||||
TimeDefinition timeDefinition = rule.timeDefinition;
|
||||
ZoneOffset standardOffset = rule.standardOffset;
|
||||
ZoneOffset offsetBefore = rule.offsetBefore;
|
||||
ZoneOffset offsetAfter = rule.offsetAfter;
|
||||
int month = rule.getMonth().getValue();
|
||||
byte dom = (byte)rule.getDayOfMonthIndicator();
|
||||
int dow = rule.getDayOfWeek().getValue();
|
||||
LocalTime time = rule.getLocalTime();
|
||||
boolean timeEndOfDay = rule.isMidnightEndOfDay();
|
||||
TimeDefinition timeDefinition = rule.getTimeDefinition();
|
||||
ZoneOffset standardOffset = rule.getStandardOffset();
|
||||
ZoneOffset offsetBefore = rule.getOffsetBefore();
|
||||
ZoneOffset offsetAfter = rule.getOffsetAfter();
|
||||
|
||||
int timeSecs = (timeEndOfDay ? 86400 : time.toSecondOfDay());
|
||||
int stdOffset = standardOffset.getTotalSeconds();
|
||||
|
@ -1,743 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is available under and governed by the GNU General Public
|
||||
* License version 2 only, as published by the Free Software Foundation.
|
||||
* However, the following notice accompanied the original version of this
|
||||
* file:
|
||||
*
|
||||
* Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of JSR-310 nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package build.tools.tzdb;
|
||||
|
||||
import static build.tools.tzdb.Utils.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A mutable builder used to create all the rules for a historic time-zone.
|
||||
* <p>
|
||||
* The rules of a time-zone describe how the offset changes over time.
|
||||
* The rules are created by building windows on the time-line within which
|
||||
* the different rules apply. The rules may be one of two kinds:
|
||||
* <p><ul>
|
||||
* <li>Fixed savings - A single fixed amount of savings from the standard offset will apply.</li>
|
||||
* <li>Rules - A set of one or more rules describe how daylight savings changes during the window.</li>
|
||||
* </ul><p>
|
||||
*
|
||||
* <h4>Implementation notes</h4>
|
||||
* This class is a mutable builder used to create zone instances.
|
||||
* It must only be used from a single thread.
|
||||
* The created instances are immutable and thread-safe.
|
||||
*
|
||||
* @since 1.8
|
||||
*/
|
||||
public class ZoneRulesBuilder {
|
||||
|
||||
/**
|
||||
* The list of windows.
|
||||
*/
|
||||
private List<TZWindow> windowList = new ArrayList<>();
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Constructs an instance of the builder that can be used to create zone rules.
|
||||
* <p>
|
||||
* The builder is used by adding one or more windows representing portions
|
||||
* of the time-line. The standard offset from UTC/Greenwich will be constant
|
||||
* within a window, although two adjacent windows can have the same standard offset.
|
||||
* <p>
|
||||
* Within each window, there can either be a
|
||||
* {@link #setFixedSavingsToWindow fixed savings amount} or a
|
||||
* {@link #addRuleToWindow list of rules}.
|
||||
*/
|
||||
public ZoneRulesBuilder() {
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Adds a window to the builder that can be used to filter a set of rules.
|
||||
* <p>
|
||||
* This method defines and adds a window to the zone where the standard offset is specified.
|
||||
* The window limits the effect of subsequent additions of transition rules
|
||||
* or fixed savings. If neither rules or fixed savings are added to the window
|
||||
* then the window will default to no savings.
|
||||
* <p>
|
||||
* Each window must be added sequentially, as the start instant of the window
|
||||
* is derived from the until instant of the previous window.
|
||||
*
|
||||
* @param standardOffset the standard offset, not null
|
||||
* @param until the date-time that the offset applies until, not null
|
||||
* @param untilDefinition the time type for the until date-time, not null
|
||||
* @return this, for chaining
|
||||
* @throws IllegalStateException if the window order is invalid
|
||||
*/
|
||||
public ZoneRulesBuilder addWindow(
|
||||
ZoneOffset standardOffset,
|
||||
LocalDateTime until,
|
||||
TimeDefinition untilDefinition) {
|
||||
Objects.requireNonNull(standardOffset, "standardOffset");
|
||||
Objects.requireNonNull(until, "until");
|
||||
Objects.requireNonNull(untilDefinition, "untilDefinition");
|
||||
TZWindow window = new TZWindow(standardOffset, until, untilDefinition);
|
||||
if (windowList.size() > 0) {
|
||||
TZWindow previous = windowList.get(windowList.size() - 1);
|
||||
window.validateWindowOrder(previous);
|
||||
}
|
||||
windowList.add(window);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a window that applies until the end of time to the builder that can be
|
||||
* used to filter a set of rules.
|
||||
* <p>
|
||||
* This method defines and adds a window to the zone where the standard offset is specified.
|
||||
* The window limits the effect of subsequent additions of transition rules
|
||||
* or fixed savings. If neither rules or fixed savings are added to the window
|
||||
* then the window will default to no savings.
|
||||
* <p>
|
||||
* This must be added after all other windows.
|
||||
* No more windows can be added after this one.
|
||||
*
|
||||
* @param standardOffset the standard offset, not null
|
||||
* @return this, for chaining
|
||||
* @throws IllegalStateException if a forever window has already been added
|
||||
*/
|
||||
public ZoneRulesBuilder addWindowForever(ZoneOffset standardOffset) {
|
||||
return addWindow(standardOffset, LocalDateTime.MAX, TimeDefinition.WALL);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Sets the previously added window to have fixed savings.
|
||||
* <p>
|
||||
* Setting a window to have fixed savings simply means that a single daylight
|
||||
* savings amount applies throughout the window. The window could be small,
|
||||
* such as a single summer, or large, such as a multi-year daylight savings.
|
||||
* <p>
|
||||
* A window can either have fixed savings or rules but not both.
|
||||
*
|
||||
* @param fixedSavingAmountSecs the amount of saving to use for the whole window, not null
|
||||
* @return this, for chaining
|
||||
* @throws IllegalStateException if no window has yet been added
|
||||
* @throws IllegalStateException if the window already has rules
|
||||
*/
|
||||
public ZoneRulesBuilder setFixedSavingsToWindow(int fixedSavingAmountSecs) {
|
||||
if (windowList.isEmpty()) {
|
||||
throw new IllegalStateException("Must add a window before setting the fixed savings");
|
||||
}
|
||||
TZWindow window = windowList.get(windowList.size() - 1);
|
||||
window.setFixedSavings(fixedSavingAmountSecs);
|
||||
return this;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Adds a single transition rule to the current window.
|
||||
* <p>
|
||||
* This adds a rule such that the offset, expressed as a daylight savings amount,
|
||||
* changes at the specified date-time.
|
||||
*
|
||||
* @param transitionDateTime the date-time that the transition occurs as defined by timeDefintion, not null
|
||||
* @param timeDefinition the definition of how to convert local to actual time, not null
|
||||
* @param savingAmountSecs the amount of saving from the standard offset after the transition in seconds
|
||||
* @return this, for chaining
|
||||
* @throws IllegalStateException if no window has yet been added
|
||||
* @throws IllegalStateException if the window already has fixed savings
|
||||
* @throws IllegalStateException if the window has reached the maximum capacity of 2000 rules
|
||||
*/
|
||||
public ZoneRulesBuilder addRuleToWindow(
|
||||
LocalDateTime transitionDateTime,
|
||||
TimeDefinition timeDefinition,
|
||||
int savingAmountSecs) {
|
||||
Objects.requireNonNull(transitionDateTime, "transitionDateTime");
|
||||
return addRuleToWindow(
|
||||
transitionDateTime.getYear(), transitionDateTime.getYear(),
|
||||
transitionDateTime.getMonth(), transitionDateTime.getDayOfMonth(),
|
||||
-1, transitionDateTime.getTime(), false, timeDefinition, savingAmountSecs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a single transition rule to the current window.
|
||||
* <p>
|
||||
* This adds a rule such that the offset, expressed as a daylight savings amount,
|
||||
* changes at the specified date-time.
|
||||
*
|
||||
* @param year the year of the transition, from MIN_YEAR to MAX_YEAR
|
||||
* @param month the month of the transition, not null
|
||||
* @param dayOfMonthIndicator the day-of-month of the transition, adjusted by dayOfWeek,
|
||||
* from 1 to 31 adjusted later, or -1 to -28 adjusted earlier from the last day of the month
|
||||
* @param time the time that the transition occurs as defined by timeDefintion, not null
|
||||
* @param timeEndOfDay whether midnight is at the end of day
|
||||
* @param timeDefinition the definition of how to convert local to actual time, not null
|
||||
* @param savingAmountSecs the amount of saving from the standard offset after the transition in seconds
|
||||
* @return this, for chaining
|
||||
* @throws DateTimeException if a date-time field is out of range
|
||||
* @throws IllegalStateException if no window has yet been added
|
||||
* @throws IllegalStateException if the window already has fixed savings
|
||||
* @throws IllegalStateException if the window has reached the maximum capacity of 2000 rules
|
||||
*/
|
||||
public ZoneRulesBuilder addRuleToWindow(
|
||||
int year,
|
||||
int month,
|
||||
int dayOfMonthIndicator,
|
||||
LocalTime time,
|
||||
boolean timeEndOfDay,
|
||||
TimeDefinition timeDefinition,
|
||||
int savingAmountSecs) {
|
||||
return addRuleToWindow(year, year, month, dayOfMonthIndicator, -1, time, timeEndOfDay, timeDefinition, savingAmountSecs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a multi-year transition rule to the current window.
|
||||
* <p>
|
||||
* This adds a rule such that the offset, expressed as a daylight savings amount,
|
||||
* changes at the specified date-time for each year in the range.
|
||||
*
|
||||
* @param startYear the start year of the rule, from MIN_YEAR to MAX_YEAR
|
||||
* @param endYear the end year of the rule, from MIN_YEAR to MAX_YEAR
|
||||
* @param month the month of the transition, from 1 to 12
|
||||
* @param dayOfMonthIndicator the day-of-month of the transition, adjusted by dayOfWeek,
|
||||
* from 1 to 31 adjusted later, or -1 to -28 adjusted earlier from the last day of the month
|
||||
* @param dayOfWeek the day-of-week to adjust to, -1 if day-of-month should not be adjusted
|
||||
* @param time the time that the transition occurs as defined by timeDefintion, not null
|
||||
* @param timeEndOfDay whether midnight is at the end of day
|
||||
* @param timeDefinition the definition of how to convert local to actual time, not null
|
||||
* @param savingAmountSecs the amount of saving from the standard offset after the transition in seconds
|
||||
* @return this, for chaining
|
||||
* @throws DateTimeException if a date-time field is out of range
|
||||
* @throws IllegalArgumentException if the day of month indicator is invalid
|
||||
* @throws IllegalArgumentException if the end of day midnight flag does not match the time
|
||||
* @throws IllegalStateException if no window has yet been added
|
||||
* @throws IllegalStateException if the window already has fixed savings
|
||||
* @throws IllegalStateException if the window has reached the maximum capacity of 2000 rules
|
||||
*/
|
||||
public ZoneRulesBuilder addRuleToWindow(
|
||||
int startYear,
|
||||
int endYear,
|
||||
int month,
|
||||
int dayOfMonthIndicator,
|
||||
int dayOfWeek,
|
||||
LocalTime time,
|
||||
boolean timeEndOfDay,
|
||||
TimeDefinition timeDefinition,
|
||||
int savingAmountSecs) {
|
||||
Objects.requireNonNull(time, "time");
|
||||
Objects.requireNonNull(timeDefinition, "timeDefinition");
|
||||
if (dayOfMonthIndicator < -28 || dayOfMonthIndicator > 31 || dayOfMonthIndicator == 0) {
|
||||
throw new IllegalArgumentException("Day of month indicator must be between -28 and 31 inclusive excluding zero");
|
||||
}
|
||||
if (timeEndOfDay && time.equals(LocalTime.MIDNIGHT) == false) {
|
||||
throw new IllegalArgumentException("Time must be midnight when end of day flag is true");
|
||||
}
|
||||
if (windowList.isEmpty()) {
|
||||
throw new IllegalStateException("Must add a window before adding a rule");
|
||||
}
|
||||
TZWindow window = windowList.get(windowList.size() - 1);
|
||||
window.addRule(startYear, endYear, month, dayOfMonthIndicator, dayOfWeek, time, timeEndOfDay, timeDefinition, savingAmountSecs);
|
||||
return this;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Completes the build converting the builder to a set of time-zone rules.
|
||||
* <p>
|
||||
* Calling this method alters the state of the builder.
|
||||
* Further rules should not be added to this builder once this method is called.
|
||||
*
|
||||
* @param zoneId the time-zone ID, not null
|
||||
* @return the zone rules, not null
|
||||
* @throws IllegalStateException if no windows have been added
|
||||
* @throws IllegalStateException if there is only one rule defined as being forever for any given window
|
||||
*/
|
||||
public ZoneRules toRules(String zoneId) {
|
||||
Objects.requireNonNull(zoneId, "zoneId");
|
||||
if (windowList.isEmpty()) {
|
||||
throw new IllegalStateException("No windows have been added to the builder");
|
||||
}
|
||||
|
||||
final List<ZoneOffsetTransition> standardTransitionList = new ArrayList<>(4);
|
||||
final List<ZoneOffsetTransition> transitionList = new ArrayList<>(256);
|
||||
final List<ZoneOffsetTransitionRule> lastTransitionRuleList = new ArrayList<>(2);
|
||||
|
||||
// initialize the standard offset calculation
|
||||
final TZWindow firstWindow = windowList.get(0);
|
||||
ZoneOffset loopStandardOffset = firstWindow.standardOffset;
|
||||
int loopSavings = 0;
|
||||
if (firstWindow.fixedSavingAmountSecs != null) {
|
||||
loopSavings = firstWindow.fixedSavingAmountSecs;
|
||||
}
|
||||
final ZoneOffset firstWallOffset = ZoneOffset.ofTotalSeconds(loopStandardOffset.getTotalSeconds() + loopSavings);
|
||||
LocalDateTime loopWindowStart = LocalDateTime.of(YEAR_MIN_VALUE, 1, 1, 0, 0);
|
||||
ZoneOffset loopWindowOffset = firstWallOffset;
|
||||
|
||||
// build the windows and rules to interesting data
|
||||
for (TZWindow window : windowList) {
|
||||
// tidy the state
|
||||
window.tidy(loopWindowStart.getYear());
|
||||
|
||||
// calculate effective savings at the start of the window
|
||||
Integer effectiveSavings = window.fixedSavingAmountSecs;
|
||||
if (effectiveSavings == null) {
|
||||
// apply rules from this window together with the standard offset and
|
||||
// savings from the last window to find the savings amount applicable
|
||||
// at start of this window
|
||||
effectiveSavings = 0;
|
||||
for (TZRule rule : window.ruleList) {
|
||||
if (rule.toEpochSecond(loopStandardOffset, loopSavings) > loopWindowStart.toEpochSecond(loopWindowOffset)) {
|
||||
// previous savings amount found, which could be the savings amount at
|
||||
// the instant that the window starts (hence isAfter)
|
||||
break;
|
||||
}
|
||||
effectiveSavings = rule.savingAmountSecs;
|
||||
}
|
||||
}
|
||||
|
||||
// check if standard offset changed, and update it
|
||||
if (loopStandardOffset.equals(window.standardOffset) == false) {
|
||||
standardTransitionList.add(
|
||||
new ZoneOffsetTransition(
|
||||
LocalDateTime.ofEpochSecond(loopWindowStart.toEpochSecond(loopWindowOffset), 0, loopStandardOffset),
|
||||
loopStandardOffset, window.standardOffset));
|
||||
loopStandardOffset = window.standardOffset;
|
||||
}
|
||||
|
||||
// check if the start of the window represents a transition
|
||||
ZoneOffset effectiveWallOffset = ZoneOffset.ofTotalSeconds(loopStandardOffset.getTotalSeconds() + effectiveSavings);
|
||||
if (loopWindowOffset.equals(effectiveWallOffset) == false) {
|
||||
transitionList.add(new ZoneOffsetTransition(loopWindowStart, loopWindowOffset, effectiveWallOffset));
|
||||
}
|
||||
loopSavings = effectiveSavings;
|
||||
|
||||
// apply rules within the window
|
||||
for (TZRule rule : window.ruleList) {
|
||||
if (rule.isTransition(loopSavings)) {
|
||||
ZoneOffsetTransition trans = rule.toTransition(loopStandardOffset, loopSavings);
|
||||
if (trans.toEpochSecond() < loopWindowStart.toEpochSecond(loopWindowOffset) == false &&
|
||||
trans.toEpochSecond() < window.createDateTimeEpochSecond(loopSavings)) {
|
||||
transitionList.add(trans);
|
||||
loopSavings = rule.savingAmountSecs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// calculate last rules
|
||||
for (TZRule lastRule : window.lastRuleList) {
|
||||
lastTransitionRuleList.add(lastRule.toTransitionRule(loopStandardOffset, loopSavings));
|
||||
loopSavings = lastRule.savingAmountSecs;
|
||||
}
|
||||
|
||||
// finally we can calculate the true end of the window, passing it to the next window
|
||||
loopWindowOffset = window.createWallOffset(loopSavings);
|
||||
loopWindowStart = LocalDateTime.ofEpochSecond(
|
||||
window.createDateTimeEpochSecond(loopSavings), 0, loopWindowOffset);
|
||||
}
|
||||
|
||||
return new ZoneRules(
|
||||
firstWindow.standardOffset, firstWallOffset, standardTransitionList,
|
||||
transitionList, lastTransitionRuleList);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* A definition of a window in the time-line.
|
||||
* The window will have one standard offset and will either have a
|
||||
* fixed DST savings or a set of rules.
|
||||
*/
|
||||
class TZWindow {
|
||||
/** The standard offset during the window, not null. */
|
||||
private final ZoneOffset standardOffset;
|
||||
/** The end local time, not null. */
|
||||
private final LocalDateTime windowEnd;
|
||||
/** The type of the end time, not null. */
|
||||
private final TimeDefinition timeDefinition;
|
||||
|
||||
/** The fixed amount of the saving to be applied during this window. */
|
||||
private Integer fixedSavingAmountSecs;
|
||||
/** The rules for the current window. */
|
||||
private List<TZRule> ruleList = new ArrayList<>();
|
||||
/** The latest year that the last year starts at. */
|
||||
private int maxLastRuleStartYear = YEAR_MIN_VALUE;
|
||||
/** The last rules. */
|
||||
private List<TZRule> lastRuleList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param standardOffset the standard offset applicable during the window, not null
|
||||
* @param windowEnd the end of the window, relative to the time definition, null if forever
|
||||
* @param timeDefinition the time definition for calculating the true end, not null
|
||||
*/
|
||||
TZWindow(
|
||||
ZoneOffset standardOffset,
|
||||
LocalDateTime windowEnd,
|
||||
TimeDefinition timeDefinition) {
|
||||
super();
|
||||
this.windowEnd = windowEnd;
|
||||
this.timeDefinition = timeDefinition;
|
||||
this.standardOffset = standardOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fixed savings amount for the window.
|
||||
*
|
||||
* @param fixedSavingAmount the amount of daylight saving to apply throughout the window, may be null
|
||||
* @throws IllegalStateException if the window already has rules
|
||||
*/
|
||||
void setFixedSavings(int fixedSavingAmount) {
|
||||
if (ruleList.size() > 0 || lastRuleList.size() > 0) {
|
||||
throw new IllegalStateException("Window has DST rules, so cannot have fixed savings");
|
||||
}
|
||||
this.fixedSavingAmountSecs = fixedSavingAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a rule to the current window.
|
||||
*
|
||||
* @param startYear the start year of the rule, from MIN_YEAR to MAX_YEAR
|
||||
* @param endYear the end year of the rule, from MIN_YEAR to MAX_YEAR
|
||||
* @param month the month of the transition, not null
|
||||
* @param dayOfMonthIndicator the day-of-month of the transition, adjusted by dayOfWeek,
|
||||
* from 1 to 31 adjusted later, or -1 to -28 adjusted earlier from the last day of the month
|
||||
* @param dayOfWeek the day-of-week to adjust to, null if day-of-month should not be adjusted
|
||||
* @param time the time that the transition occurs as defined by timeDefintion, not null
|
||||
* @param timeEndOfDay whether midnight is at the end of day
|
||||
* @param timeDefinition the definition of how to convert local to actual time, not null
|
||||
* @param savingAmountSecs the amount of saving from the standard offset in seconds
|
||||
* @throws IllegalStateException if the window already has fixed savings
|
||||
* @throws IllegalStateException if the window has reached the maximum capacity of 2000 rules
|
||||
*/
|
||||
void addRule(
|
||||
int startYear,
|
||||
int endYear,
|
||||
int month,
|
||||
int dayOfMonthIndicator,
|
||||
int dayOfWeek,
|
||||
LocalTime time,
|
||||
boolean timeEndOfDay,
|
||||
TimeDefinition timeDefinition,
|
||||
int savingAmountSecs) {
|
||||
|
||||
if (fixedSavingAmountSecs != null) {
|
||||
throw new IllegalStateException("Window has a fixed DST saving, so cannot have DST rules");
|
||||
}
|
||||
if (ruleList.size() >= 2000) {
|
||||
throw new IllegalStateException("Window has reached the maximum number of allowed rules");
|
||||
}
|
||||
boolean lastRule = false;
|
||||
if (endYear == YEAR_MAX_VALUE) {
|
||||
lastRule = true;
|
||||
endYear = startYear;
|
||||
}
|
||||
int year = startYear;
|
||||
while (year <= endYear) {
|
||||
TZRule rule = new TZRule(year, month, dayOfMonthIndicator, dayOfWeek, time, timeEndOfDay, timeDefinition, savingAmountSecs);
|
||||
if (lastRule) {
|
||||
lastRuleList.add(rule);
|
||||
maxLastRuleStartYear = Math.max(startYear, maxLastRuleStartYear);
|
||||
} else {
|
||||
ruleList.add(rule);
|
||||
}
|
||||
year++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that this window is after the previous one.
|
||||
*
|
||||
* @param previous the previous window, not null
|
||||
* @throws IllegalStateException if the window order is invalid
|
||||
*/
|
||||
void validateWindowOrder(TZWindow previous) {
|
||||
if (windowEnd.compareTo(previous.windowEnd) < 0) {
|
||||
throw new IllegalStateException("Windows must be added in date-time order: " +
|
||||
windowEnd + " < " + previous.windowEnd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds rules to make the last rules all start from the same year.
|
||||
* Also add one more year to avoid weird case where penultimate year has odd offset.
|
||||
*
|
||||
* @param windowStartYear the window start year
|
||||
* @throws IllegalStateException if there is only one rule defined as being forever
|
||||
*/
|
||||
void tidy(int windowStartYear) {
|
||||
if (lastRuleList.size() == 1) {
|
||||
throw new IllegalStateException("Cannot have only one rule defined as being forever");
|
||||
}
|
||||
|
||||
// handle last rules
|
||||
if (windowEnd.equals(LocalDateTime.MAX)) {
|
||||
// setup at least one real rule, which closes off other windows nicely
|
||||
maxLastRuleStartYear = Math.max(maxLastRuleStartYear, windowStartYear) + 1;
|
||||
for (TZRule lastRule : lastRuleList) {
|
||||
addRule(lastRule.year, maxLastRuleStartYear, lastRule.month, lastRule.dayOfMonthIndicator,
|
||||
lastRule.dayOfWeek, lastRule.time, lastRule.timeEndOfDay, lastRule.timeDefinition, lastRule.savingAmountSecs);
|
||||
lastRule.year = maxLastRuleStartYear + 1;
|
||||
}
|
||||
if (maxLastRuleStartYear == YEAR_MAX_VALUE) {
|
||||
lastRuleList.clear();
|
||||
} else {
|
||||
maxLastRuleStartYear++;
|
||||
}
|
||||
} else {
|
||||
// convert all within the endYear limit
|
||||
int endYear = windowEnd.getYear();
|
||||
for (TZRule lastRule : lastRuleList) {
|
||||
addRule(lastRule.year, endYear + 1, lastRule.month, lastRule.dayOfMonthIndicator,
|
||||
lastRule.dayOfWeek, lastRule.time, lastRule.timeEndOfDay, lastRule.timeDefinition, lastRule.savingAmountSecs);
|
||||
}
|
||||
lastRuleList.clear();
|
||||
maxLastRuleStartYear = YEAR_MAX_VALUE;
|
||||
}
|
||||
|
||||
// ensure lists are sorted
|
||||
Collections.sort(ruleList);
|
||||
Collections.sort(lastRuleList);
|
||||
|
||||
// default fixed savings to zero
|
||||
if (ruleList.size() == 0 && fixedSavingAmountSecs == null) {
|
||||
fixedSavingAmountSecs = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the window is empty.
|
||||
*
|
||||
* @return true if the window is only a standard offset
|
||||
*/
|
||||
boolean isSingleWindowStandardOffset() {
|
||||
return windowEnd.equals(LocalDateTime.MAX) && timeDefinition == TimeDefinition.WALL &&
|
||||
fixedSavingAmountSecs == null && lastRuleList.isEmpty() && ruleList.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the wall offset for the local date-time at the end of the window.
|
||||
*
|
||||
* @param savingsSecs the amount of savings in use in seconds
|
||||
* @return the created date-time epoch second in the wall offset, not null
|
||||
*/
|
||||
ZoneOffset createWallOffset(int savingsSecs) {
|
||||
return ZoneOffset.ofTotalSeconds(standardOffset.getTotalSeconds() + savingsSecs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the offset date-time for the local date-time at the end of the window.
|
||||
*
|
||||
* @param savingsSecs the amount of savings in use in seconds
|
||||
* @return the created date-time epoch second in the wall offset, not null
|
||||
*/
|
||||
long createDateTimeEpochSecond(int savingsSecs) {
|
||||
ZoneOffset wallOffset = createWallOffset(savingsSecs);
|
||||
LocalDateTime ldt = timeDefinition.createDateTime(windowEnd, standardOffset, wallOffset);
|
||||
return ldt.toEpochSecond(wallOffset);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* A definition of the way a local time can be converted to an offset time.
|
||||
*/
|
||||
class TZRule implements Comparable<TZRule> {
|
||||
private int year;
|
||||
private int month;
|
||||
private int dayOfMonthIndicator;
|
||||
private int dayOfWeek;
|
||||
private LocalTime time;
|
||||
private boolean timeEndOfDay; // Whether the local time is end of day.
|
||||
private TimeDefinition timeDefinition; // The type of the time.
|
||||
private int savingAmountSecs; // The amount of the saving to be applied after this point.
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param year the year
|
||||
* @param month the month, value from 1 to 12
|
||||
* @param dayOfMonthIndicator the day-of-month of the transition, adjusted by dayOfWeek,
|
||||
* from 1 to 31 adjusted later, or -1 to -28 adjusted earlier from the last day of the month
|
||||
* @param dayOfWeek the day-of-week, -1 if day-of-month is exact
|
||||
* @param time the time, not null
|
||||
* @param timeEndOfDay whether midnight is at the end of day
|
||||
* @param timeDefinition the time definition, not null
|
||||
* @param savingAfterSecs the savings amount in seconds
|
||||
*/
|
||||
TZRule(int year, int month, int dayOfMonthIndicator,
|
||||
int dayOfWeek, LocalTime time, boolean timeEndOfDay,
|
||||
TimeDefinition timeDefinition, int savingAfterSecs) {
|
||||
this.year = year;
|
||||
this.month = month;
|
||||
this.dayOfMonthIndicator = dayOfMonthIndicator;
|
||||
this.dayOfWeek = dayOfWeek;
|
||||
this.time = time;
|
||||
this.timeEndOfDay = timeEndOfDay;
|
||||
this.timeDefinition = timeDefinition;
|
||||
this.savingAmountSecs = savingAfterSecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this to a transition.
|
||||
*
|
||||
* @param standardOffset the active standard offset, not null
|
||||
* @param savingsBeforeSecs the active savings in seconds
|
||||
* @return the transition, not null
|
||||
*/
|
||||
ZoneOffsetTransition toTransition(ZoneOffset standardOffset, int savingsBeforeSecs) {
|
||||
// copy of code in ZoneOffsetTransitionRule to avoid infinite loop
|
||||
LocalDate date = toLocalDate();
|
||||
LocalDateTime ldt = LocalDateTime.of(date, time);
|
||||
ZoneOffset wallOffset = ZoneOffset.ofTotalSeconds(standardOffset.getTotalSeconds() + savingsBeforeSecs);
|
||||
LocalDateTime dt = timeDefinition.createDateTime(ldt, standardOffset, wallOffset);
|
||||
ZoneOffset offsetAfter = ZoneOffset.ofTotalSeconds(standardOffset.getTotalSeconds() + savingAmountSecs);
|
||||
return new ZoneOffsetTransition(dt, wallOffset, offsetAfter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the apoch second of this rules with the specified
|
||||
* active standard offset and active savings
|
||||
*
|
||||
* @param standardOffset the active standard offset, not null
|
||||
* @param savingsBeforeSecs the active savings in seconds
|
||||
* @return the transition epoch second
|
||||
*/
|
||||
long toEpochSecond(ZoneOffset standardOffset, int savingsBeforeSecs) {
|
||||
LocalDateTime ldt = LocalDateTime.of(toLocalDate(), time);
|
||||
ZoneOffset wallOffset = ZoneOffset.ofTotalSeconds(standardOffset.getTotalSeconds() + savingsBeforeSecs);
|
||||
return timeDefinition.createDateTime(ldt, standardOffset, wallOffset)
|
||||
.toEpochSecond(wallOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if this a real transition with the active savings in seconds
|
||||
*
|
||||
* @param savingsBeforeSecs the active savings in seconds
|
||||
* @return true, if savings in seconds changes
|
||||
*/
|
||||
boolean isTransition(int savingsBeforeSecs) {
|
||||
return savingAmountSecs != savingsBeforeSecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this to a transition rule.
|
||||
*
|
||||
* @param standardOffset the active standard offset, not null
|
||||
* @param savingsBeforeSecs the active savings before the transition in seconds
|
||||
* @return the transition, not null
|
||||
*/
|
||||
ZoneOffsetTransitionRule toTransitionRule(ZoneOffset standardOffset, int savingsBeforeSecs) {
|
||||
// optimize stored format
|
||||
if (dayOfMonthIndicator < 0) {
|
||||
if (month != 2) { // not Month.FEBRUARY
|
||||
dayOfMonthIndicator = maxLengthOfMonth(month) - 6;
|
||||
}
|
||||
}
|
||||
if (timeEndOfDay && dayOfMonthIndicator > 0 &&
|
||||
(dayOfMonthIndicator == 28 && month == 2) == false) {
|
||||
LocalDate date = LocalDate.of(2004, month, dayOfMonthIndicator).plusDays(1); // leap-year
|
||||
month = date.getMonth();
|
||||
dayOfMonthIndicator = date.getDayOfMonth();
|
||||
if (dayOfWeek != -1) {
|
||||
dayOfWeek = plusDayOfWeek(dayOfWeek, 1);
|
||||
}
|
||||
timeEndOfDay = false;
|
||||
}
|
||||
// build rule
|
||||
return new ZoneOffsetTransitionRule(
|
||||
month, dayOfMonthIndicator, dayOfWeek, time, timeEndOfDay, timeDefinition,
|
||||
standardOffset,
|
||||
ZoneOffset.ofTotalSeconds(standardOffset.getTotalSeconds() + savingsBeforeSecs),
|
||||
ZoneOffset.ofTotalSeconds(standardOffset.getTotalSeconds() + savingAmountSecs));
|
||||
}
|
||||
|
||||
public int compareTo(TZRule other) {
|
||||
int cmp = year - other.year;
|
||||
cmp = (cmp == 0 ? month - other.month : cmp);
|
||||
if (cmp == 0) {
|
||||
// convert to date to handle dow/domIndicator/timeEndOfDay
|
||||
LocalDate thisDate = toLocalDate();
|
||||
LocalDate otherDate = other.toLocalDate();
|
||||
cmp = thisDate.compareTo(otherDate);
|
||||
}
|
||||
cmp = (cmp == 0 ? time.compareTo(other.time) : cmp);
|
||||
return cmp;
|
||||
}
|
||||
|
||||
private LocalDate toLocalDate() {
|
||||
LocalDate date;
|
||||
if (dayOfMonthIndicator < 0) {
|
||||
int monthLen = lengthOfMonth(month, isLeapYear(year));
|
||||
date = LocalDate.of(year, month, monthLen + 1 + dayOfMonthIndicator);
|
||||
if (dayOfWeek != -1) {
|
||||
date = previousOrSame(date, dayOfWeek);
|
||||
}
|
||||
} else {
|
||||
date = LocalDate.of(year, month, dayOfMonthIndicator);
|
||||
if (dayOfWeek != -1) {
|
||||
date = nextOrSame(date, dayOfWeek);
|
||||
}
|
||||
}
|
||||
if (timeEndOfDay) {
|
||||
date = date.plusDays(1);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user