diff --git a/make/docs/CORE_PKGS.gmk b/make/docs/CORE_PKGS.gmk index 2c2b1774f3269689655c87eae04a513c9364531a..86aeae9501d02786601609620833d677a4185619 100644 --- a/make/docs/CORE_PKGS.gmk +++ b/make/docs/CORE_PKGS.gmk @@ -127,6 +127,11 @@ CORE_PKGS = \ java.sql \ java.text \ java.text.spi \ + java.time \ + java.time.temporal \ + java.time.calendar \ + java.time.format \ + java.time.zone \ java.util \ java.util.concurrent \ java.util.concurrent.atomic \ diff --git a/make/java/Makefile b/make/java/Makefile index 67f735fcb1d095e072e3abf67341f51c88c1cea8..80c97d27f5815839967c24d6dc591b025af6aaa7 100644 --- a/make/java/Makefile +++ b/make/java/Makefile @@ -39,7 +39,7 @@ SUBDIRS += version jvm redist verify fdlibm java sun_nio jli main zip # Others # Note: java_crw_demo java_hprof_demo are demos but must be delivered built in sdk -SUBDIRS += security math util text net nio jar +SUBDIRS += security math util text net nio jar time SUBDIRS_desktop = awt applet beans SUBDIRS_management = management diff --git a/make/java/time/Makefile b/make/java/time/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..0ff3698bbf31f65982c1007a122d83426c4ba85b --- /dev/null +++ b/make/java/time/Makefile @@ -0,0 +1,42 @@ +# +# 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. +# + +# +# Makefile for building jar utility. +# + +BUILDDIR = ../../ +PACKAGE = java.time +include $(BUILDDIR)/common/Defs.gmk + +# +# Files +# +AUTO_FILES_JAVA_DIRS = java/time + +# +# Rules +# +include $(BUILDDIR)/common/Classes.gmk diff --git a/make/jprt.properties b/make/jprt.properties index 4bd2d4b84c61657f9179a2f6c9fc1a17ba220ad7..a6d2ea1d0d4c7d8d94ef4f304afc6acd38e4d5f5 100644 --- a/make/jprt.properties +++ b/make/jprt.properties @@ -87,6 +87,7 @@ jprt.make.rule.core.test.targets= \ ${jprt.my.test.target.set:TESTNAME=jdk_text}, \ ${jprt.my.test.target.set:TESTNAME=jdk_tools}, \ ${jprt.my.test.target.set:TESTNAME=jdk_jfr}, \ + ${jprt.my.test.target.set:TESTNAME=jdk_time}, \ ${jprt.my.test.target.set:TESTNAME=jdk_other} # All vm test targets (testset=all) diff --git a/make/sun/Makefile b/make/sun/Makefile index 623923296114cf0c9bc1d5ac9b70feef4c129be4..3d26ee5e0b4d1887f256ade0de6e07189cb48a55 100644 --- a/make/sun/Makefile +++ b/make/sun/Makefile @@ -70,7 +70,7 @@ else endif # nio need to be compiled before awt to have all charsets ready -SUBDIRS = jar security javazic misc net nio text util launcher cldr +SUBDIRS = jar security javazic misc net nio text util launcher cldr tzdb ifdef BUILD_HEADLESS_ONLY DISPLAY_LIBS = awt $(HEADLESS_SUBDIR) diff --git a/make/sun/tzdb/Makefile b/make/sun/tzdb/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..d09a1251b1de074d79270fa483977ba3c18e1d5a --- /dev/null +++ b/make/sun/tzdb/Makefile @@ -0,0 +1,68 @@ +# +# 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. +# + +# +# Makefile for building tzdb compiler utility. +# + +BUILDDIR = ../.. +PACKAGE = sun.tzdb +PRODUCT = sun +include $(BUILDDIR)/common/Defs.gmk + +# This program must contain a manifest that defines the execution level +# needed to follow standard Vista User Access Control Guidelines +# This must be set before Program.gmk is included +# +BUILD_MANIFEST=true + +# +# Time zone data file creation +# +TZDATA_DIR := ../javazic/tzdata +TZDATA_VER := $(subst tzdata,,$(shell $(GREP) '^tzdata' $(TZDATA_DIR)/VERSION)) +TZFILE := africa antarctica asia australasia europe northamerica southamerica backward etcetera +TZFILES := $(addprefix $(TZDATA_DIR)/,$(TZFILE)) + +TZDB_JAR = tzdb.jar + +# +# Rules +# +include $(BUILDDIR)/common/Classes.gmk + +# +# Add to the build rule +# +build: $(LIBDIR)/$(TZDB_JAR) + +$(LIBDIR)/$(TZDB_JAR): $(TZFILES) + $(prep-target) + echo build tzdb from version $(TZDATA_VER) + $(BOOT_JAVA_CMD) -jar $(BUILDTOOLJARDIR)/tzdb.jar -verbose \ + -version $(TZDATA_VER) -srcdir $(TZDATA_DIR) -dstdir $(LIBDIR) $(TZFILE) + +clean clobber:: + $(RM) $(LIBDIR)/$(TZDB_JAR) diff --git a/make/tools/Makefile b/make/tools/Makefile index ca39dd6df67710ae5d7f10f43cc9ef554abfa8a5..586a169948658cda877e84acb2dff6982e11014d 100644 --- a/make/tools/Makefile +++ b/make/tools/Makefile @@ -53,6 +53,7 @@ SUBDIRS = \ makeclasslist \ strip_properties \ spp \ + tzdb \ CharsetMapping ifndef DISABLE_NIMBUS diff --git a/make/tools/src/build/tools/tzdb/ChronoField.java b/make/tools/src/build/tools/tzdb/ChronoField.java new file mode 100644 index 0000000000000000000000000000000000000000..7ce34bbfae262f02e427880ab762c0cd8edb604e --- /dev/null +++ b/make/tools/src/build/tools/tzdb/ChronoField.java @@ -0,0 +1,180 @@ +/* + * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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). + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * + * @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; + } + +} diff --git a/make/tools/src/build/tools/tzdb/DateTimeException.java b/make/tools/src/build/tools/tzdb/DateTimeException.java new file mode 100644 index 0000000000000000000000000000000000000000..2b7674df3ff44ddc6587b4bad7e9f6030bd16aa4 --- /dev/null +++ b/make/tools/src/build/tools/tzdb/DateTimeException.java @@ -0,0 +1,98 @@ +/* + * 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. + *

+ * 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); + } + +} diff --git a/make/tools/src/build/tools/tzdb/LocalDate.java b/make/tools/src/build/tools/tzdb/LocalDate.java new file mode 100644 index 0000000000000000000000000000000000000000..fedab6d31561e208565873c5e8c73fe702555f9e --- /dev/null +++ b/make/tools/src/build/tools/tzdb/LocalDate.java @@ -0,0 +1,363 @@ +/* + * 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. + *

+ * 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. + *

+ * This method returns the primitive {@code int} value for the year. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * For example, 2008-12-31 plus one day would result in 2009-01-01. + *

+ * 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. + *

+ * 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. + *

+ * For example, 2009-01-01 minus one day would result in 2008-12-31. + *

+ * 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. + *

+ * 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. + *

+ * The comparison is primarily based on the date, from earliest to latest. + * It is "consistent with equals", as defined by {@link Comparable}. + *

+ * 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. + *

+ * Compares this {@code LocalDate} with another ensuring that the date is the same. + *

+ * 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)); + } + +} diff --git a/make/tools/src/build/tools/tzdb/LocalDateTime.java b/make/tools/src/build/tools/tzdb/LocalDateTime.java new file mode 100644 index 0000000000000000000000000000000000000000..ced37b72613da44120d551067ccb317bd10f3cdb --- /dev/null +++ b/make/tools/src/build/tools/tzdb/LocalDateTime.java @@ -0,0 +1,427 @@ +/* + * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * This method returns the primitive {@code int} value for the year. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * For example, 2008-12-31 plus one day would result in 2009-01-01. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * The comparison is primarily based on the date-time, from earliest to latest. + * It is "consistent with equals", as defined by {@link Comparable}. + *

+ * 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. + *

+ * 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(); + } + +} diff --git a/make/tools/src/build/tools/tzdb/LocalTime.java b/make/tools/src/build/tools/tzdb/LocalTime.java new file mode 100644 index 0000000000000000000000000000000000000000..0fc318689446ae4141d12162ffd63489ffdffb50 --- /dev/null +++ b/make/tools/src/build/tools/tzdb/LocalTime.java @@ -0,0 +1,388 @@ +/* + * 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. + *

+ * The second and nanosecond fields will be set to zero by this factory method. + *

+ * 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. + *

+ * The nanosecond field will be set to zero by this factory method. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * This adds the specified number of seconds to this time, returning a new time. + * The calculation wraps around midnight. + *

+ * 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. + *

+ * This subtracts the specified number of seconds from this time, returning a new time. + * The calculation wraps around midnight. + *

+ * 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. + *

+ * 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. + *

+ * The comparison is based on the time-line position of the time within a day. + *

+ * 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)); + } + +} diff --git a/make/tools/src/build/tools/tzdb/TimeDefinition.java b/make/tools/src/build/tools/tzdb/TimeDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..8a5853db3d7d6db7451ef99520af486a2347f915 --- /dev/null +++ b/make/tools/src/build/tools/tzdb/TimeDefinition.java @@ -0,0 +1,117 @@ +/* + * 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. + *

+ * Time zone rules are expressed in one of three ways: + *

+ */ +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. + *

+ * This method converts using the type of this enum. + * The output is defined relative to the 'before' offset of the transition. + *

+ * 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; + } + } + +} diff --git a/make/tools/src/build/tools/tzdb/TzdbZoneRulesCompiler.java b/make/tools/src/build/tools/tzdb/TzdbZoneRulesCompiler.java new file mode 100644 index 0000000000000000000000000000000000000000..7b32ccf267a3a617d5cf087ad2262b4f6207d429 --- /dev/null +++ b/make/tools/src/build/tools/tzdb/TzdbZoneRulesCompiler.java @@ -0,0 +1,876 @@ +/* + * 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) 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.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.text.ParsePosition; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.StringTokenizer; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A builder that can read the TZDB time-zone files and build {@code ZoneRules} instances. + * + * @since 1.8 + */ +public final class TzdbZoneRulesCompiler { + + private static final Matcher YEAR = Pattern.compile("(?i)(?min)|(?max)|(?only)|(?[0-9]+)").matcher(""); + private static final Matcher MONTH = Pattern.compile("(?i)(jan)|(feb)|(mar)|(apr)|(may)|(jun)|(jul)|(aug)|(sep)|(oct)|(nov)|(dec)").matcher(""); + private static final Matcher DOW = Pattern.compile("(?i)(mon)|(tue)|(wed)|(thu)|(fri)|(sat)|(sun)").matcher(""); + private static final Matcher TIME = Pattern.compile("(?-)?+(?[0-9]{1,2})(:(?[0-5][0-9]))?+(:(?[0-5][0-9]))?+").matcher(""); + + /** + * Constant for MJD 1972-01-01. + */ + private static final long MJD_1972_01_01 = 41317L; + + /** + * Reads a set of TZDB files and builds a single combined data file. + * + * @param args the arguments + */ + public static void main(String[] args) { + if (args.length < 2) { + outputHelp(); + return; + } + + // parse args + String version = null; + File baseSrcDir = null; + File dstDir = null; + boolean verbose = false; + + // parse options + int i; + for (i = 0; i < args.length; i++) { + String arg = args[i]; + if (arg.startsWith("-") == false) { + break; + } + if ("-srcdir".equals(arg)) { + if (baseSrcDir == null && ++i < args.length) { + baseSrcDir = new File(args[i]); + continue; + } + } else if ("-dstdir".equals(arg)) { + if (dstDir == null && ++i < args.length) { + dstDir = new File(args[i]); + continue; + } + } else if ("-version".equals(arg)) { + if (version == null && ++i < args.length) { + version = args[i]; + continue; + } + } else if ("-verbose".equals(arg)) { + if (verbose == false) { + verbose = true; + continue; + } + } else if ("-help".equals(arg) == false) { + System.out.println("Unrecognised option: " + arg); + } + outputHelp(); + return; + } + + // check source directory + if (baseSrcDir == null) { + System.out.println("Source directory must be specified using -srcdir: " + baseSrcDir); + return; + } + if (baseSrcDir.isDirectory() == false) { + System.out.println("Source does not exist or is not a directory: " + baseSrcDir); + return; + } + dstDir = (dstDir != null ? dstDir : baseSrcDir); + + // parse source file names + List srcFileNames = Arrays.asList(Arrays.copyOfRange(args, i, args.length)); + if (srcFileNames.isEmpty()) { + System.out.println("Source filenames not specified, using default set"); + System.out.println("(africa antarctica asia australasia backward etcetera europe northamerica southamerica)"); + srcFileNames = Arrays.asList("africa", "antarctica", "asia", "australasia", "backward", + "etcetera", "europe", "northamerica", "southamerica"); + } + + // find source directories to process + List srcDirs = new ArrayList<>(); + if (version != null) { + // if the "version" specified, as in jdk repo, the "baseSrcDir" is + // the "srcDir" that contains the tzdb data. + srcDirs.add(baseSrcDir); + } else { + File[] dirs = baseSrcDir.listFiles(); + for (File dir : dirs) { + if (dir.isDirectory() && dir.getName().matches("[12][0-9]{3}[A-Za-z0-9._-]+")) { + srcDirs.add(dir); + } + } + } + if (srcDirs.isEmpty()) { + System.out.println("Source directory contains no valid source folders: " + baseSrcDir); + return; + } + // check destination directory + if (dstDir.exists() == false && dstDir.mkdirs() == false) { + System.out.println("Destination directory could not be created: " + dstDir); + return; + } + if (dstDir.isDirectory() == false) { + System.out.println("Destination is not a directory: " + dstDir); + return; + } + process(srcDirs, srcFileNames, dstDir, version, verbose); + System.exit(0); + } + + /** + * Output usage text for the command line. + */ + private static void outputHelp() { + System.out.println("Usage: TzdbZoneRulesCompiler "); + System.out.println("where options include:"); + System.out.println(" -srcdir Where to find source directories (required)"); + System.out.println(" -dstdir Where to output generated files (default srcdir)"); + System.out.println(" -version Specify the version, such as 2009a (optional)"); + System.out.println(" -help Print this usage message"); + System.out.println(" -verbose Output verbose information during compilation"); + System.out.println(" There must be one directory for each version in srcdir"); + System.out.println(" Each directory must have the name of the version, such as 2009a"); + System.out.println(" Each directory must contain the unpacked tzdb files, such as asia or europe"); + System.out.println(" Directories must match the regex [12][0-9][0-9][0-9][A-Za-z0-9._-]+"); + System.out.println(" There will be one jar file for each version and one combined jar in dstdir"); + System.out.println(" If the version is specified, only that version is processed"); + } + + /** + * Process to create the jar files. + */ + private static void process(List srcDirs, List srcFileNames, File dstDir, String version, boolean verbose) { + // build actual jar files + Map> allBuiltZones = new TreeMap<>(); + Set allRegionIds = new TreeSet(); + Set allRules = new HashSet(); + + for (File srcDir : srcDirs) { + // source files in this directory + List srcFiles = new ArrayList<>(); + for (String srcFileName : srcFileNames) { + File file = new File(srcDir, srcFileName); + if (file.exists()) { + srcFiles.add(file); + } + } + if (srcFiles.isEmpty()) { + continue; // nothing to process + } + + // compile + String loopVersion = srcDir.getName(); + TzdbZoneRulesCompiler compiler = new TzdbZoneRulesCompiler(loopVersion, srcFiles, verbose); + try { + // compile + compiler.compile(); + SortedMap builtZones = compiler.getZones(); + + // output version-specific file + File dstFile = version == null ? new File(dstDir, "tzdb" + loopVersion + ".jar") + : new File(dstDir, "tzdb.jar"); + if (verbose) { + System.out.println("Outputting file: " + dstFile); + } + outputFile(dstFile, loopVersion, builtZones); + + // create totals + allBuiltZones.put(loopVersion, builtZones); + allRegionIds.addAll(builtZones.keySet()); + allRules.addAll(builtZones.values()); + } catch (Exception ex) { + System.out.println("Failed: " + ex.toString()); + ex.printStackTrace(); + System.exit(1); + } + } + + // output merged file + if (version == null) { + File dstFile = new File(dstDir, "tzdb-all.jar"); + if (verbose) { + System.out.println("Outputting combined file: " + dstFile); + } + outputFile(dstFile, allBuiltZones, allRegionIds, allRules); + } + } + + /** + * Outputs the file. + */ + private static void outputFile(File dstFile, + String version, + SortedMap builtZones) { + Map> loopAllBuiltZones = new TreeMap<>(); + loopAllBuiltZones.put(version, builtZones); + Set loopAllRegionIds = new TreeSet(builtZones.keySet()); + Set loopAllRules = new HashSet(builtZones.values()); + outputFile(dstFile, loopAllBuiltZones, loopAllRegionIds, loopAllRules); + } + + /** + * Outputs the file. + */ + private static void outputFile(File dstFile, + Map> allBuiltZones, + Set allRegionIds, + Set allRules) + { + try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(dstFile))) { + outputTZEntry(jos, allBuiltZones, allRegionIds, allRules); + } catch (Exception ex) { + System.out.println("Failed: " + ex.toString()); + ex.printStackTrace(); + System.exit(1); + } + } + + /** + * Outputs the timezone entry in the JAR file. + */ + private static void outputTZEntry(JarOutputStream jos, + Map> allBuiltZones, + Set allRegionIds, + Set allRules) { + // this format is not publicly specified + try { + jos.putNextEntry(new ZipEntry("TZDB.dat")); + DataOutputStream out = new DataOutputStream(jos); + + // file version + out.writeByte(1); + // group + out.writeUTF("TZDB"); + // versions + String[] versionArray = allBuiltZones.keySet().toArray(new String[allBuiltZones.size()]); + out.writeShort(versionArray.length); + for (String version : versionArray) { + out.writeUTF(version); + } + // regions + String[] regionArray = allRegionIds.toArray(new String[allRegionIds.size()]); + out.writeShort(regionArray.length); + for (String regionId : regionArray) { + out.writeUTF(regionId); + } + // rules + List rulesList = new ArrayList<>(allRules); + out.writeShort(rulesList.size()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); + for (ZoneRules rules : rulesList) { + baos.reset(); + DataOutputStream dataos = new DataOutputStream(baos); + rules.writeExternal(dataos); + dataos.close(); + byte[] bytes = baos.toByteArray(); + out.writeShort(bytes.length); + out.write(bytes); + } + // link version-region-rules + for (String version : allBuiltZones.keySet()) { + out.writeShort(allBuiltZones.get(version).size()); + for (Map.Entry entry : allBuiltZones.get(version).entrySet()) { + int regionIndex = Arrays.binarySearch(regionArray, entry.getKey()); + int rulesIndex = rulesList.indexOf(entry.getValue()); + out.writeShort(regionIndex); + out.writeShort(rulesIndex); + } + } + out.flush(); + jos.closeEntry(); + } catch (Exception ex) { + System.out.println("Failed: " + ex.toString()); + ex.printStackTrace(); + System.exit(1); + } + } + + //----------------------------------------------------------------------- + /** The TZDB rules. */ + private final Map> rules = new HashMap<>(); + + /** The TZDB zones. */ + private final Map> zones = new HashMap<>(); + /** The TZDB links. */ + + private final Map links = new HashMap<>(); + + /** The built zones. */ + private final SortedMap builtZones = new TreeMap<>(); + + + /** The version to produce. */ + private final String version; + + /** The source files. */ + + private final List sourceFiles; + + /** The version to produce. */ + private final boolean verbose; + + /** + * Creates an instance if you want to invoke the compiler manually. + * + * @param version the version, such as 2009a, not null + * @param sourceFiles the list of source files, not empty, not null + * @param verbose whether to output verbose messages + */ + public TzdbZoneRulesCompiler(String version, List sourceFiles, boolean verbose) { + this.version = version; + this.sourceFiles = sourceFiles; + this.verbose = verbose; + } + + /** + * Compile the rules file. + *

+ * Use {@link #getZones()} to retrieve the parsed data. + * + * @throws Exception if an error occurs + */ + public void compile() throws Exception { + printVerbose("Compiling TZDB version " + version); + parseFiles(); + buildZoneRules(); + printVerbose("Compiled TZDB version " + version); + } + + /** + * Gets the parsed zone rules. + * + * @return the parsed zone rules, not null + */ + public SortedMap getZones() { + return builtZones; + } + + /** + * Parses the source files. + * + * @throws Exception if an error occurs + */ + private void parseFiles() throws Exception { + for (File file : sourceFiles) { + printVerbose("Parsing file: " + file); + parseFile(file); + } + } + + /** + * Parses a source file. + * + * @param file the file being read, not null + * @throws Exception if an error occurs + */ + private void parseFile(File file) throws Exception { + int lineNumber = 1; + String line = null; + BufferedReader in = null; + try { + in = new BufferedReader(new FileReader(file)); + List openZone = null; + for ( ; (line = in.readLine()) != null; 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; + } + StringTokenizer st = new StringTokenizer(line, " \t"); + if (openZone != null && Character.isWhitespace(line.charAt(0)) && st.hasMoreTokens()) { + if (parseZoneLine(st, openZone)) { + openZone = null; + } + } else { + if (st.hasMoreTokens()) { + String first = st.nextToken(); + if (first.equals("Zone")) { + if (st.countTokens() < 3) { + printVerbose("Invalid Zone line in file: " + file + ", line: " + line); + throw new IllegalArgumentException("Invalid Zone line"); + } + openZone = new ArrayList<>(); + zones.put(st.nextToken(), openZone); + if (parseZoneLine(st, openZone)) { + openZone = null; + } + } else { + openZone = null; + if (first.equals("Rule")) { + if (st.countTokens() < 9) { + printVerbose("Invalid Rule line in file: " + file + ", line: " + line); + throw new IllegalArgumentException("Invalid Rule line"); + } + parseRuleLine(st); + + } else if (first.equals("Link")) { + if (st.countTokens() < 2) { + printVerbose("Invalid Link line in file: " + file + ", line: " + line); + throw new IllegalArgumentException("Invalid Link line"); + } + String realId = st.nextToken(); + String aliasId = st.nextToken(); + links.put(aliasId, realId); + + } else { + throw new IllegalArgumentException("Unknown line"); + } + } + } + } + } + } catch (Exception ex) { + throw new Exception("Failed while processing file '" + file + "' on line " + lineNumber + " '" + line + "'", ex); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (Exception ex) { + // ignore NPE and IOE + } + } + } + + /** + * Parses a Rule line. + * + * @param st the tokenizer, not null + */ + private void parseRuleLine(StringTokenizer st) { + TZDBRule rule = new TZDBRule(); + String name = st.nextToken(); + if (rules.containsKey(name) == false) { + rules.put(name, new ArrayList()); + } + rules.get(name).add(rule); + rule.startYear = parseYear(st.nextToken(), 0); + rule.endYear = parseYear(st.nextToken(), rule.startYear); + if (rule.startYear > rule.endYear) { + throw new IllegalArgumentException("Year order invalid: " + rule.startYear + " > " + rule.endYear); + } + parseOptional(st.nextToken()); // type is unused + parseMonthDayTime(st, rule); + rule.savingsAmount = parsePeriod(st.nextToken()); + rule.text = parseOptional(st.nextToken()); + } + + /** + * Parses a Zone line. + * + * @param st the tokenizer, not null + * @return true if the zone is complete + */ + private boolean parseZoneLine(StringTokenizer st, List zoneList) { + TZDBZone zone = new TZDBZone(); + zoneList.add(zone); + zone.standardOffset = parseOffset(st.nextToken()); + String savingsRule = parseOptional(st.nextToken()); + 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 = st.nextToken(); + if (st.hasMoreTokens()) { + zone.year = Integer.parseInt(st.nextToken()); + if (st.hasMoreTokens()) { + parseMonthDayTime(st, zone); + } + return false; + } else { + return true; + } + } + + /** + * Parses a Rule line. + * + * @param st the tokenizer, not null + * @param mdt the object to parse into, not null + */ + private void parseMonthDayTime(StringTokenizer st, TZDBMonthDayTime mdt) { + mdt.month = parseMonth(st.nextToken()); + if (st.hasMoreTokens()) { + String dayRule = st.nextToken(); + 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 (st.hasMoreTokens()) { + String timeStr = st.nextToken(); + 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(String str, int defaultYear) { + if (YEAR.reset(str).matches()) { + if (YEAR.group("min") != null) { + return YEAR_MIN_VALUE; + } else if (YEAR.group("max") != null) { + return YEAR_MAX_VALUE; + } else if (YEAR.group("only") != null) { + return defaultYear; + } + return Integer.parseInt(YEAR.group("year")); + } + throw new IllegalArgumentException("Unknown year: " + str); + } + + private int parseMonth(String str) { + if (MONTH.reset(str).matches()) { + for (int moy = 1; moy < 13; moy++) { + if (MONTH.group(moy) != null) { + return moy; + } + } + } + throw new IllegalArgumentException("Unknown month: " + str); + } + + 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 tzdbZones = zones.get(zoneId); + ZoneRulesBuilder bld = new ZoneRulesBuilder(); + for (TZDBZone tzdbZone : tzdbZones) { + bld = tzdbZone.addToBuilder(bld, rules); + } + ZoneRules buildRules = bld.toRules(zoneId); + builtZones.put(zoneId, buildRules); + } + + // 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 + "' for '" + version + "'"); + } + } + 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"); + } + + //----------------------------------------------------------------------- + /** + * Prints a verbose message. + * + * @param message the message, not null + */ + private void printVerbose(String message) { + if (verbose) { + 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> rules) { + if (year != YEAR_MAX_VALUE) { + bld.addWindow(standardOffset, toDateTime(year), timeDefinition); + } else { + bld.addWindowForever(standardOffset); + } + if (fixedSavingsSecs != null) { + bld.setFixedSavingsToWindow(fixedSavingsSecs); + } else { + List 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; + } + } + +} diff --git a/make/tools/src/build/tools/tzdb/Utils.java b/make/tools/src/build/tools/tzdb/Utils.java new file mode 100644 index 0000000000000000000000000000000000000000..d129dd3bc7fbbeb01d0c8f465a431a9c44c441c4 --- /dev/null +++ b/make/tools/src/build/tools/tzdb/Utils.java @@ -0,0 +1,176 @@ +/* + * 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); + } + } + +} diff --git a/make/tools/src/build/tools/tzdb/ZoneOffset.java b/make/tools/src/build/tools/tzdb/ZoneOffset.java new file mode 100644 index 0000000000000000000000000000000000000000..4f95fb3dd59b43a2463d0bc9f5b26562e88560d1 --- /dev/null +++ b/make/tools/src/build/tools/tzdb/ZoneOffset.java @@ -0,0 +1,474 @@ +/* + * 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}. + *

+ * 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 { + + /** Cache of time-zone offset by offset in seconds. */ + private static final ConcurrentMap SECONDS_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4); + /** Cache of time-zone offset by ID. */ + private static final ConcurrentMap 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. + *

+ * 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: + *

+ * Note that ± means either the plus or minus symbol. + *

+ * The ID of the returned offset will be normalized to one of the formats + * described by {@link #getId()}. + *

+ * 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. + *

+ * 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. + *

+ * 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 + *

+ * 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. + *

+ * 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. + *

+ * The ID is minor variation to the standard ISO-8601 formatted string + * for the offset. There are three formats: + *

+ * + * @return the zone offset ID, not null + */ + public String getId() { + return id; + } + + /** + * Compares this offset to another offset in descending order. + *

+ * 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}. + *

+ * 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. + *

+ * 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; + } + +} diff --git a/make/tools/src/build/tools/tzdb/ZoneOffsetTransition.java b/make/tools/src/build/tools/tzdb/ZoneOffsetTransition.java new file mode 100644 index 0000000000000000000000000000000000000000..167b5f112dee1da440b9eddc64a75787768df432 --- /dev/null +++ b/make/tools/src/build/tools/tzdb/ZoneOffsetTransition.java @@ -0,0 +1,290 @@ +/* + * 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 { + + /** + * 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. + *

+ * 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. + *

+ * 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. + *

+ * This is the first date-time after the discontinuity, when the new offset applies. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * A gap will return an empty list, while an overlap will return both offsets. + * + * @return the list of valid offsets + */ + List getValidOffsets() { + if (isGap()) { + return Collections.emptyList(); + } + return Arrays.asList(getOffsetBefore(), getOffsetAfter()); + } + + /** + * Compares this transition to another based on the transition instant. + *

+ * 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. + *

+ * 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); + } + +} diff --git a/make/tools/src/build/tools/tzdb/ZoneOffsetTransitionRule.java b/make/tools/src/build/tools/tzdb/ZoneOffsetTransitionRule.java new file mode 100644 index 0000000000000000000000000000000000000000..783499c21632911c8972d346e320e6306d44cdfc --- /dev/null +++ b/make/tools/src/build/tools/tzdb/ZoneOffsetTransitionRule.java @@ -0,0 +1,223 @@ +/* + * 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. + *

+ * This class allows rules for identifying future transitions to be expressed. + * A rule might be written in many forms: + *

+ * These different rule types can be expressed and queried. + * + *

Specification for implementors

+ * 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. + *

+ * 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(); + } + +} diff --git a/make/tools/src/build/tools/tzdb/ZoneRules.java b/make/tools/src/build/tools/tzdb/ZoneRules.java new file mode 100644 index 0000000000000000000000000000000000000000..22c3be80c027590b9c45e4082eba5d72c4728a7c --- /dev/null +++ b/make/tools/src/build/tools/tzdb/ZoneRules.java @@ -0,0 +1,311 @@ +/* + * 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) 2011-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.io.DataOutput; +import java.io.IOException; +import java.io.ObjectOutput; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +/** + * Duplicated code of javax.time.zone.ZoneRules, ZoneOffsetTransitionRule + * and Ser to generate the serialization form output of ZoneRules for + * tzdb.jar. + * + * Implementation here is the copy/paste of ZoneRules, ZoneOffsetTransitionRule + * and Ser in javax.time.zone package. Make sure the code here is synchrionozed + * with the serialization implementation there. + * + * @since 1.8 + */ + +final class ZoneRules { + + /** + * The transitions between standard offsets (epoch seconds), sorted. + */ + private final long[] standardTransitions; + /** + * The standard offsets. + */ + private final ZoneOffset[] standardOffsets; + /** + * The transitions between instants (epoch seconds), sorted. + */ + private final long[] savingsInstantTransitions; + + /** + * The wall offsets. + */ + private final ZoneOffset[] wallOffsets; + /** + * The last rule. + */ + private final ZoneOffsetTransitionRule[] lastRules; + + /** + * Creates an instance. + * + * @param baseStandardOffset the standard offset to use before legal rules were set, not null + * @param baseWallOffset the wall offset to use before legal rules were set, not null + * @param standardOffsetTransitionList the list of changes to the standard offset, not null + * @param transitionList the list of transitions, not null + * @param lastRules the recurring last rules, size 16 or less, not null + */ + ZoneRules(ZoneOffset baseStandardOffset, + ZoneOffset baseWallOffset, + List standardOffsetTransitionList, + List transitionList, + List lastRules) { + + this.standardTransitions = new long[standardOffsetTransitionList.size()]; + + this.standardOffsets = new ZoneOffset[standardOffsetTransitionList.size() + 1]; + this.standardOffsets[0] = baseStandardOffset; + for (int i = 0; i < standardOffsetTransitionList.size(); i++) { + this.standardTransitions[i] = standardOffsetTransitionList.get(i).toEpochSecond(); + this.standardOffsets[i + 1] = standardOffsetTransitionList.get(i).getOffsetAfter(); + } + + // convert savings transitions to locals + List localTransitionOffsetList = new ArrayList<>(); + localTransitionOffsetList.add(baseWallOffset); + for (ZoneOffsetTransition trans : transitionList) { + localTransitionOffsetList.add(trans.getOffsetAfter()); + } + + this.wallOffsets = localTransitionOffsetList.toArray(new ZoneOffset[localTransitionOffsetList.size()]); + + // convert savings transitions to instants + this.savingsInstantTransitions = new long[transitionList.size()]; + for (int i = 0; i < transitionList.size(); i++) { + this.savingsInstantTransitions[i] = transitionList.get(i).toEpochSecond(); + } + + // last rules + if (lastRules.size() > 16) { + throw new IllegalArgumentException("Too many transition rules"); + } + this.lastRules = lastRules.toArray(new ZoneOffsetTransitionRule[lastRules.size()]); + } + + /** Type for ZoneRules. */ + static final byte ZRULES = 1; + + /** + * Writes the state to the stream. + * + * @param out the output stream, not null + * @throws IOException if an error occurs + */ + void writeExternal(DataOutput out) throws IOException { + out.writeByte(ZRULES); + out.writeInt(standardTransitions.length); + for (long trans : standardTransitions) { + writeEpochSec(trans, out); + } + for (ZoneOffset offset : standardOffsets) { + writeOffset(offset, out); + } + out.writeInt(savingsInstantTransitions.length); + for (long trans : savingsInstantTransitions) { + writeEpochSec(trans, out); + } + for (ZoneOffset offset : wallOffsets) { + writeOffset(offset, out); + } + out.writeByte(lastRules.length); + for (ZoneOffsetTransitionRule rule : lastRules) { + writeRule(rule, out); + } + } + + /** + * Writes the state the ZoneOffset to the stream. + * + * @param offset the offset, not null + * @param out the output stream, not null + * @throws IOException if an error occurs + */ + static void writeOffset(ZoneOffset offset, DataOutput out) throws IOException { + final int offsetSecs = offset.getTotalSeconds(); + int offsetByte = offsetSecs % 900 == 0 ? offsetSecs / 900 : 127; // compress to -72 to +72 + out.writeByte(offsetByte); + if (offsetByte == 127) { + out.writeInt(offsetSecs); + } + } + + /** + * Writes the epoch seconds to the stream. + * + * @param epochSec the epoch seconds, not null + * @param out the output stream, not null + * @throws IOException if an error occurs + */ + static void writeEpochSec(long epochSec, DataOutput out) throws IOException { + if (epochSec >= -4575744000L && epochSec < 10413792000L && epochSec % 900 == 0) { // quarter hours between 1825 and 2300 + int store = (int) ((epochSec + 4575744000L) / 900); + out.writeByte((store >>> 16) & 255); + out.writeByte((store >>> 8) & 255); + out.writeByte(store & 255); + } else { + out.writeByte(255); + out.writeLong(epochSec); + } + } + + /** + * Writes the state of the transition rule to the stream. + * + * @param rule the transition rule, not null + * @param out the output stream, not null + * @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 timeSecs = (timeEndOfDay ? 86400 : time.toSecondOfDay()); + int stdOffset = standardOffset.getTotalSeconds(); + int beforeDiff = offsetBefore.getTotalSeconds() - stdOffset; + int afterDiff = offsetAfter.getTotalSeconds() - stdOffset; + int timeByte = (timeSecs % 3600 == 0 ? (timeEndOfDay ? 24 : time.getHour()) : 31); + int stdOffsetByte = (stdOffset % 900 == 0 ? stdOffset / 900 + 128 : 255); + int beforeByte = (beforeDiff == 0 || beforeDiff == 1800 || beforeDiff == 3600 ? beforeDiff / 1800 : 3); + int afterByte = (afterDiff == 0 || afterDiff == 1800 || afterDiff == 3600 ? afterDiff / 1800 : 3); + int dowByte = (dow == -1 ? 0 : dow); + int b = (month << 28) + // 4 bytes + ((dom + 32) << 22) + // 6 bytes + (dowByte << 19) + // 3 bytes + (timeByte << 14) + // 5 bytes + (timeDefinition.ordinal() << 12) + // 2 bytes + (stdOffsetByte << 4) + // 8 bytes + (beforeByte << 2) + // 2 bytes + afterByte; // 2 bytes + out.writeInt(b); + if (timeByte == 31) { + out.writeInt(timeSecs); + } + if (stdOffsetByte == 255) { + out.writeInt(stdOffset); + } + if (beforeByte == 3) { + out.writeInt(offsetBefore.getTotalSeconds()); + } + if (afterByte == 3) { + out.writeInt(offsetAfter.getTotalSeconds()); + } + } + + /** + * Checks if this set of rules equals another. + *

+ * Two rule sets are equal if they will always result in the same output + * for any given input instant or local date-time. + * Rules from two different groups may return false even if they are in fact the same. + *

+ * This definition should result in implementations comparing their entire state. + * + * @param otherRules the other rules, null returns false + * @return true if this rules is the same as that specified + */ + @Override + public boolean equals(Object otherRules) { + if (this == otherRules) { + return true; + } + if (otherRules instanceof ZoneRules) { + ZoneRules other = (ZoneRules) otherRules; + return Arrays.equals(standardTransitions, other.standardTransitions) && + Arrays.equals(standardOffsets, other.standardOffsets) && + Arrays.equals(savingsInstantTransitions, other.savingsInstantTransitions) && + Arrays.equals(wallOffsets, other.wallOffsets) && + Arrays.equals(lastRules, other.lastRules); + } + return false; + } + + /** + * Returns a suitable hash code given the definition of {@code #equals}. + * + * @return the hash code + */ + @Override + public int hashCode() { + return Arrays.hashCode(standardTransitions) ^ + Arrays.hashCode(standardOffsets) ^ + Arrays.hashCode(savingsInstantTransitions) ^ + Arrays.hashCode(wallOffsets) ^ + Arrays.hashCode(lastRules); + } + +} diff --git a/make/tools/src/build/tools/tzdb/ZoneRulesBuilder.java b/make/tools/src/build/tools/tzdb/ZoneRulesBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..f4a437dc074bf6398b13e60ea7156e3575d6b712 --- /dev/null +++ b/make/tools/src/build/tools/tzdb/ZoneRulesBuilder.java @@ -0,0 +1,743 @@ +/* + * 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. + *

+ * 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: + *

+ * + *

Implementation notes

+ * 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 windowList = new ArrayList<>(); + + //----------------------------------------------------------------------- + /** + * Constructs an instance of the builder that can be used to create zone rules. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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 standardTransitionList = new ArrayList<>(4); + final List transitionList = new ArrayList<>(256); + final List 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 ruleList = new ArrayList<>(); + /** The latest year that the last year starts at. */ + private int maxLastRuleStartYear = YEAR_MIN_VALUE; + /** The last rules. */ + private List 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 { + 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; + } + } + +} diff --git a/make/tools/tzdb/Makefile b/make/tools/tzdb/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..895a0e8cfb07a15684492e614cd543125815a9a9 --- /dev/null +++ b/make/tools/tzdb/Makefile @@ -0,0 +1,43 @@ +# +# Copyright (c) 1998, 2005, 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. +# + +# +# Makefile for building the tzdb compiler tool +# + +BUILDDIR = ../.. +PACKAGE = build.tools.tzdb +PRODUCT = tzdb +PROGRAM = tzdb +include $(BUILDDIR)/common/Defs.gmk + +BUILDTOOL_SOURCE_ROOT = $(BUILDDIR)/tools/src +BUILDTOOL_MAIN = $(PKGDIR)/TzdbZoneRulesCompiler.java + +# +# Build tool jar rules. +# +include $(BUILDDIR)/common/BuildToolJar.gmk + diff --git a/makefiles/CreateJars.gmk b/makefiles/CreateJars.gmk index 6250dd83fe20939516e949b478fa04fbf3be096d..21c3ca98bfbc1a9164a3fc890f661e2c757b9a61 100644 --- a/makefiles/CreateJars.gmk +++ b/makefiles/CreateJars.gmk @@ -72,6 +72,13 @@ JARS+=$(IMAGES_OUTPUTDIR)/lib/ext/dnsns.jar ########################################################################################## +$(IMAGES_OUTPUTDIR)/lib/tzdb.jar: $(JDK_OUTPUTDIR)/lib/tzdb.jar + $(install-file) + +JARS += $(IMAGES_OUTPUTDIR)/lib/tzdb.jar + +########################################################################################## + LOCALEDATA_INCLUDE_LOCALES := ar be bg ca cs da de el es et fi fr ga hi hr hu in is it \ iw ja ko lt lv mk ms mt nl no pl pt ro ru sk sl sq sr sv \ th tr uk vi zh diff --git a/makefiles/GendataTZDB.gmk b/makefiles/GendataTZDB.gmk new file mode 100644 index 0000000000000000000000000000000000000000..3c608fb06ba611c7dcee0497b833ff45ecc7619c --- /dev/null +++ b/makefiles/GendataTZDB.gmk @@ -0,0 +1,44 @@ +# +# 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. +# + +GENDATA_TZDB := + +# +# Time zone data file creation +# +TZDATA_DIR := $(JDK_TOPDIR)/make/sun/javazic/tzdata +TZDATA_VER := $(subst tzdata,,$(shell $(GREP) '^tzdata' $(TZDATA_DIR)/VERSION)) +TZDATA_TZFILE := africa antarctica asia australasia europe northamerica southamerica backward etcetera +TZDATA_TZFILES := $(addprefix $(TZDATA_DIR)/,$(TZDATA_TZFILE)) + +GENDATA_TZDB_DST := $(JDK_OUTPUTDIR)/lib +GENDATA_TZDB_JAR := tzdb.jar + +$(GENDATA_TZDB_DST)/$(GENDATA_TZDB_JAR) : $(TZDATA_TZFILES) + $(RM) $(GENDATA_TZDB_DST)/$(GENDATA_TZDB_JAR) + echo building tzdb from version $(TZDATA_VER) + $(TOOL_TZDB) -verbose -version $(TZDATA_VER) -srcdir $(TZDATA_DIR) -dstdir $(GENDATA_TZDB_DST) $(TZDATA_TZFILE) + +GENDATA_TZDB += $(GENDATA_TZDB_DST)/$(GENDATA_TZDB_JAR) diff --git a/makefiles/GenerateData.gmk b/makefiles/GenerateData.gmk index f57741700b9d9a1031a4e19bbda2f9069ab46c21..7d7e16b690353561adbbcbf6bc7fce72169d135a 100644 --- a/makefiles/GenerateData.gmk +++ b/makefiles/GenerateData.gmk @@ -47,6 +47,9 @@ GENDATA += $(GENDATA_FONT_CONFIG) include GendataTimeZone.gmk GENDATA += $(GENDATA_TIMEZONE) +include GendataTZDB.gmk +GENDATA += $(GENDATA_TZDB) + include GendataHtml32dtd.gmk GENDATA += $(GENDATA_HTML32DTD) diff --git a/makefiles/Tools.gmk b/makefiles/Tools.gmk index a794d745520e3dda98aa1514a63b88f4942671e9..811dd47fe28c00590d04bfd4c1dc874d1a86284e 100644 --- a/makefiles/Tools.gmk +++ b/makefiles/Tools.gmk @@ -106,6 +106,10 @@ TOOL_JARSPLIT=$(JAVA) -cp $(JDK_OUTPUTDIR)/btclasses \ TOOL_JAVAZIC=$(JAVA) -cp $(JDK_OUTPUTDIR)/btclasses \ build.tools.javazic.Main +TOOL_TZDB=$(JAVA) -cp $(JDK_OUTPUTDIR)/btclasses \ + build.tools.tzdb.TzdbZoneRulesCompiler + + # TODO: There are references to the jdwpgen.jar in jdk/make/netbeans/jdwpgen/build.xml # and nbproject/project.properties in the same dir. Needs to be looked at. TOOL_JDWPGEN=$(JAVA) -cp $(JDK_OUTPUTDIR)/btclasses build.tools.jdwpgen.Main diff --git a/src/share/classes/java/time/Clock.java b/src/share/classes/java/time/Clock.java new file mode 100644 index 0000000000000000000000000000000000000000..e4006075ddcac3b646fc93817410f642b01ac796 --- /dev/null +++ b/src/share/classes/java/time/Clock.java @@ -0,0 +1,638 @@ +/* + * 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 java.time; + +import static java.time.LocalTime.NANOS_PER_MINUTE; +import static java.time.LocalTime.NANOS_PER_SECOND; + +import java.io.Serializable; +import java.util.Objects; +import java.util.TimeZone; + +/** + * A clock providing access to the current instant, date and time using a time-zone. + *

+ * Instances of this class are used to find the current instant, which can be + * interpreted using the stored time-zone to find the current date and time. + * As such, a clock can be used instead of {@link System#currentTimeMillis()} + * and {@link TimeZone#getDefault()}. + *

+ * Use of a {@code Clock} is optional. All key date-time classes also have a + * {@code now()} factory method that uses the system clock in the default time zone. + * The primary purpose of this abstraction is to allow alternate clocks to be + * plugged in as and when required. Applications use an object to obtain the + * current time rather than a static method. This can simplify testing. + *

+ * Best practice for applications is to pass a {@code Clock} into any method + * that requires the current instant. A dependency injection framework is one + * way to achieve this: + *

+ *  public class MyBean {
+ *    private Clock clock;  // dependency inject
+ *    ...
+ *    public void process(LocalDate eventDate) {
+ *      if (eventDate.isBefore(LocalDate.now(clock)) {
+ *        ...
+ *      }
+ *    }
+ *  }
+ * 
+ * This approach allows an alternate clock, such as {@link #fixed(Instant, ZoneId) fixed} + * or {@link #offset(Clock, Duration) offset} to be used during testing. + *

+ * The {@code system} factory methods provide clocks based on the best available + * system clock This may use {@link System#currentTimeMillis()}, or a higher + * resolution clock if one is available. + * + *

Specification for implementors

+ * This abstract class must be implemented with care to ensure other operate correctly. + * All implementations that can be instantiated must be final, immutable and thread-safe. + *

+ * The principal methods are defined to allow the throwing of an exception. + * In normal use, no exceptions will be thrown, however one possible implementation would be to + * obtain the time from a central time server across the network. Obviously, in this case the + * lookup could fail, and so the method is permitted to throw an exception. + *

+ * The returned instants from {@code Clock} work on a time-scale that ignores leap seconds. + * If the implementation wraps a source that provides leap second information, then a mechanism + * should be used to "smooth" the leap second, such as UTC-SLS. + *

+ * Implementations should implement {@code Serializable} wherever possible and must + * document whether or not they do support serialization. + * + * @since 1.8 + */ +public abstract class Clock { + + /** + * Obtains a clock that returns the current instant using the best available + * system clock, converting to date and time using the UTC time-zone. + *

+ * This clock, rather than {@link #systemDefaultZone()}, should be used when + * you need the current instant without the date or time. + *

+ * This clock is based on the best available system clock. + * This may use {@link System#currentTimeMillis()}, or a higher resolution + * clock if one is available. + *

+ * Conversion from instant to date or time uses the {@linkplain ZoneOffset#UTC UTC time-zone}. + *

+ * The returned implementation is immutable, thread-safe and {@code Serializable}. + * It is equivalent to {@code system(ZoneOffset.UTC)}. + * + * @return a clock that uses the best available system clock in the UTC zone, not null + */ + public static Clock systemUTC() { + return new SystemClock(ZoneOffset.UTC); + } + + /** + * Obtains a clock that returns the current instant using the best available + * system clock, converting to date and time using the default time-zone. + *

+ * This clock is based on the best available system clock. + * This may use {@link System#currentTimeMillis()}, or a higher resolution + * clock if one is available. + *

+ * Using this method hard codes a dependency to the default time-zone into your application. + * It is recommended to avoid this and use a specific time-zone whenever possible. + * The {@link #systemUTC() UTC clock} should be used when you need the current instant + * without the date or time. + *

+ * The returned implementation is immutable, thread-safe and {@code Serializable}. + * It is equivalent to {@code system(ZoneId.systemDefault())}. + * + * @return a clock that uses the best available system clock in the default zone, not null + * @see ZoneId#systemDefault() + */ + public static Clock systemDefaultZone() { + return new SystemClock(ZoneId.systemDefault()); + } + + /** + * Obtains a clock that returns the current instant using best available + * system clock. + *

+ * This clock is based on the best available system clock. + * This may use {@link System#currentTimeMillis()}, or a higher resolution + * clock if one is available. + *

+ * Conversion from instant to date or time uses the specified time-zone. + *

+ * The returned implementation is immutable, thread-safe and {@code Serializable}. + * + * @param zone the time-zone to use to convert the instant to date-time, not null + * @return a clock that uses the best available system clock in the specified zone, not null + */ + public static Clock system(ZoneId zone) { + Objects.requireNonNull(zone, "zone"); + return new SystemClock(zone); + } + + //------------------------------------------------------------------------- + /** + * Obtains a clock that returns the current instant ticking in whole seconds + * using best available system clock. + *

+ * This clock will always have the nano-of-second field set to zero. + * This ensures that the visible time ticks in whole seconds. + * The underlying clock is the best available system clock, equivalent to + * using {@link #system(ZoneId)}. + *

+ * Implementations may use a caching strategy for performance reasons. + * As such, it is possible that the start of the second observed via this + * clock will be later than that observed directly via the underlying clock. + *

+ * The returned implementation is immutable, thread-safe and {@code Serializable}. + * It is equivalent to {@code tick(system(zone), Duration.ofSeconds(1))}. + * + * @param zone the time-zone to use to convert the instant to date-time, not null + * @return a clock that ticks in whole seconds using the specified zone, not null + */ + public static Clock tickSeconds(ZoneId zone) { + return new TickClock(system(zone), NANOS_PER_SECOND); + } + + /** + * Obtains a clock that returns the current instant ticking in whole minutes + * using best available system clock. + *

+ * This clock will always have the nano-of-second and second-of-minute fields set to zero. + * This ensures that the visible time ticks in whole minutes. + * The underlying clock is the best available system clock, equivalent to + * using {@link #system(ZoneId)}. + *

+ * Implementations may use a caching strategy for performance reasons. + * As such, it is possible that the start of the minute observed via this + * clock will be later than that observed directly via the underlying clock. + *

+ * The returned implementation is immutable, thread-safe and {@code Serializable}. + * It is equivalent to {@code tick(system(zone), Duration.ofMinutes(1))}. + * + * @param zone the time-zone to use to convert the instant to date-time, not null + * @return a clock that ticks in whole minutes using the specified zone, not null + */ + public static Clock tickMinutes(ZoneId zone) { + return new TickClock(system(zone), NANOS_PER_MINUTE); + } + + /** + * Obtains a clock that returns instants from the specified clock truncated + * to the nearest occurrence of the specified duration. + *

+ * This clock will only tick as per the specified duration. Thus, if the duration + * is half a second, the clock will return instants truncated to the half second. + *

+ * The tick duration must be positive. If it has a part smaller than a whole + * millisecond, then the whole duration must divide into one second without + * leaving a remainder. All normal tick durations will match these criteria, + * including any multiple of hours, minutes, seconds and milliseconds, and + * sensible nanosecond durations, such as 20ns, 250,000ns and 500,000ns. + *

+ * A duration of zero or one nanosecond would have no truncation effect. + * Passing one of these will return the underlying clock. + *

+ * Implementations may use a caching strategy for performance reasons. + * As such, it is possible that the start of the requested duration observed + * via this clock will be later than that observed directly via the underlying clock. + *

+ * The returned implementation is immutable, thread-safe and {@code Serializable} + * providing that the base clock is. + * + * @param baseClock the base clock to base the ticking clock on, not null + * @param tickDuration the duration of each visible tick, not negative, not null + * @return a clock that ticks in whole units of the duration, not null + * @throws IllegalArgumentException if the duration is negative, or has a + * part smaller than a whole millisecond such that the whole duration is not + * divisible into one second + * @throws ArithmeticException if the duration is too large to be represented as nanos + */ + public static Clock tick(Clock baseClock, Duration tickDuration) { + Objects.requireNonNull(baseClock, "baseClock"); + Objects.requireNonNull(tickDuration, "tickDuration"); + if (tickDuration.isNegative()) { + throw new IllegalArgumentException("Tick duration must not be negative"); + } + long tickNanos = tickDuration.toNanos(); + if (tickNanos % 1000_000 == 0) { + // ok, no fraction of millisecond + } else if (1000_000_000 % tickNanos == 0) { + // ok, divides into one second without remainder + } else { + throw new IllegalArgumentException("Invalid tick duration"); + } + if (tickNanos <= 1) { + return baseClock; + } + return new TickClock(baseClock, tickNanos); + } + + //----------------------------------------------------------------------- + /** + * Obtains a clock that always returns the same instant. + *

+ * This clock simply returns the specified instant. + * As such, it is not a clock in the conventional sense. + * The main use case for this is in testing, where the fixed clock ensures + * tests are not dependent on the current clock. + *

+ * The returned implementation is immutable, thread-safe and {@code Serializable}. + * + * @param fixedInstant the instant to use as the clock, not null + * @param zone the time-zone to use to convert the instant to date-time, not null + * @return a clock that always returns the same instant, not null + */ + public static Clock fixed(Instant fixedInstant, ZoneId zone) { + Objects.requireNonNull(fixedInstant, "fixedInstant"); + Objects.requireNonNull(zone, "zone"); + return new FixedClock(fixedInstant, zone); + } + + //------------------------------------------------------------------------- + /** + * Obtains a clock that returns instants from the specified clock with the + * specified duration added + *

+ * This clock wraps another clock, returning instants that are later by the + * specified duration. If the duration is negative, the instants will be + * earlier than the current date and time. + * The main use case for this is to simulate running in the future or in the past. + *

+ * A duration of zero would have no offsetting effect. + * Passing zero will return the underlying clock. + *

+ * The returned implementation is immutable, thread-safe and {@code Serializable} + * providing that the base clock is. + * + * @param baseClock the base clock to add the duration to, not null + * @param offsetDuration the duration to add, not null + * @return a clock based on the base clock with the duration added, not null + */ + public static Clock offset(Clock baseClock, Duration offsetDuration) { + Objects.requireNonNull(baseClock, "baseClock"); + Objects.requireNonNull(offsetDuration, "offsetDuration"); + if (offsetDuration.equals(Duration.ZERO)) { + return baseClock; + } + return new OffsetClock(baseClock, offsetDuration); + } + + //----------------------------------------------------------------------- + /** + * Constructor accessible by subclasses. + */ + protected Clock() { + } + + //----------------------------------------------------------------------- + /** + * Gets the time-zone being used to create dates and times. + *

+ * A clock will typically obtain the current instant and then convert that + * to a date or time using a time-zone. This method returns the time-zone used. + * + * @return the time-zone being used to interpret instants, not null + */ + public abstract ZoneId getZone(); + + /** + * Returns a copy of this clock with a different time-zone. + *

+ * A clock will typically obtain the current instant and then convert that + * to a date or time using a time-zone. This method returns a clock with + * similar properties but using a different time-zone. + * + * @param zone the time-zone to change to, not null + * @return a clock based on this clock with the specified time-zone, not null + */ + public abstract Clock withZone(ZoneId zone); + + //------------------------------------------------------------------------- + /** + * Gets the current millisecond instant of the clock. + *

+ * This returns the millisecond-based instant, measured from 1970-01-01T00:00 UTC. + * This is equivalent to the definition of {@link System#currentTimeMillis()}. + *

+ * Most applications should avoid this method and use {@link Instant} to represent + * an instant on the time-line rather than a raw millisecond value. + * This method is provided to allow the use of the clock in high performance use cases + * where the creation of an object would be unacceptable. + * + * @return the current millisecond instant from this clock, measured from + * the Java epoch of 1970-01-01T00:00 UTC, not null + * @throws DateTimeException if the instant cannot be obtained, not thrown by most implementations + */ + public abstract long millis(); + + //----------------------------------------------------------------------- + /** + * Gets the current instant of the clock. + *

+ * This returns an instant representing the current instant as defined by the clock. + *

+ * The default implementation currently calls {@link #millis}. + * + * @return the current instant from this clock, not null + * @throws DateTimeException if the instant cannot be obtained, not thrown by most implementations + */ + public Instant instant() { + return Instant.ofEpochMilli(millis()); + } + + //----------------------------------------------------------------------- + /** + * Checks if this clock is equal to another clock. + *

+ * Clocks must compare equal based on their state and behavior. + * + * @param obj the object to check, null returns false + * @return true if this is equal to the other clock + */ + @Override + public abstract boolean equals(Object obj); + + /** + * A hash code for this clock. + * + * @return a suitable hash code + */ + @Override + public abstract int hashCode(); + + //----------------------------------------------------------------------- + /** + * Returns a string describing this clock. + *

+ * Clocks must have a string representation based on their state and behavior. + * For example, 'System[Europe/Paris]' could be used to represent the System + * clock in the 'Europe/Paris' time-zone. + * + * @return a string representation of this clock, not null + */ + @Override + public abstract String toString(); + + //----------------------------------------------------------------------- + /** + * Implementation of a clock that always returns the latest time from + * {@link System#currentTimeMillis()}. + */ + static final class SystemClock extends Clock implements Serializable { + private static final long serialVersionUID = 6740630888130243051L; + private final ZoneId zone; + + SystemClock(ZoneId zone) { + this.zone = zone; + } + @Override + public ZoneId getZone() { + return zone; + } + @Override + public Clock withZone(ZoneId zone) { + if (zone.equals(this.zone)) { // intentional NPE + return this; + } + return new SystemClock(zone); + } + @Override + public long millis() { + return System.currentTimeMillis(); + } + @Override + public boolean equals(Object obj) { + if (obj instanceof SystemClock) { + return zone.equals(((SystemClock) obj).zone); + } + return false; + } + @Override + public int hashCode() { + return zone.hashCode() + 1; + } + @Override + public String toString() { + return "SystemClock[" + zone + "]"; + } + } + + //----------------------------------------------------------------------- + /** + * Implementation of a clock that always returns the same instant. + * This is typically used for testing. + */ + static final class FixedClock extends Clock implements Serializable { + private static final long serialVersionUID = 7430389292664866958L; + private final Instant instant; + private final ZoneId zone; + + FixedClock(Instant fixedInstant, ZoneId zone) { + this.instant = fixedInstant; + this.zone = zone; + } + @Override + public ZoneId getZone() { + return zone; + } + @Override + public Clock withZone(ZoneId zone) { + if (zone.equals(this.zone)) { // intentional NPE + return this; + } + return new FixedClock(instant, zone); + } + @Override + public long millis() { + return instant.toEpochMilli(); + } + @Override + public Instant instant() { + return instant; + } + @Override + public boolean equals(Object obj) { + if (obj instanceof FixedClock) { + FixedClock other = (FixedClock) obj; + return instant.equals(other.instant) && zone.equals(other.zone); + } + return false; + } + @Override + public int hashCode() { + return instant.hashCode() ^ zone.hashCode(); + } + @Override + public String toString() { + return "FixedClock[" + instant + "," + zone + "]"; + } + } + + //----------------------------------------------------------------------- + /** + * Implementation of a clock that adds an offset to an underlying clock. + */ + static final class OffsetClock extends Clock implements Serializable { + private static final long serialVersionUID = 2007484719125426256L; + private final Clock baseClock; + private final Duration offset; + + OffsetClock(Clock baseClock, Duration offset) { + this.baseClock = baseClock; + this.offset = offset; + } + @Override + public ZoneId getZone() { + return baseClock.getZone(); + } + @Override + public Clock withZone(ZoneId zone) { + if (zone.equals(baseClock.getZone())) { // intentional NPE + return this; + } + return new OffsetClock(baseClock.withZone(zone), offset); + } + @Override + public long millis() { + return Math.addExact(baseClock.millis(), offset.toMillis()); + } + @Override + public Instant instant() { + return baseClock.instant().plus(offset); + } + @Override + public boolean equals(Object obj) { + if (obj instanceof OffsetClock) { + OffsetClock other = (OffsetClock) obj; + return baseClock.equals(other.baseClock) && offset.equals(other.offset); + } + return false; + } + @Override + public int hashCode() { + return baseClock.hashCode() ^ offset.hashCode(); + } + @Override + public String toString() { + return "OffsetClock[" + baseClock + "," + offset + "]"; + } + } + + //----------------------------------------------------------------------- + /** + * Implementation of a clock that adds an offset to an underlying clock. + */ + static final class TickClock extends Clock implements Serializable { + private static final long serialVersionUID = 6504659149906368850L; + private final Clock baseClock; + private final long tickNanos; + + TickClock(Clock baseClock, long tickNanos) { + this.baseClock = baseClock; + this.tickNanos = tickNanos; + } + @Override + public ZoneId getZone() { + return baseClock.getZone(); + } + @Override + public Clock withZone(ZoneId zone) { + if (zone.equals(baseClock.getZone())) { // intentional NPE + return this; + } + return new TickClock(baseClock.withZone(zone), tickNanos); + } + @Override + public long millis() { + long millis = baseClock.millis(); + return millis - Math.floorMod(millis, tickNanos / 1000_000L); + } + @Override + public Instant instant() { + if ((tickNanos % 1000_000) == 0) { + long millis = baseClock.millis(); + return Instant.ofEpochMilli(millis - Math.floorMod(millis, tickNanos / 1000_000L)); + } + Instant instant = baseClock.instant(); + long nanos = instant.getNano(); + long adjust = Math.floorMod(nanos, tickNanos); + return instant.minusNanos(adjust); + } + @Override + public boolean equals(Object obj) { + if (obj instanceof TickClock) { + TickClock other = (TickClock) obj; + return baseClock.equals(other.baseClock) && tickNanos == other.tickNanos; + } + return false; + } + @Override + public int hashCode() { + return baseClock.hashCode() ^ ((int) (tickNanos ^ (tickNanos >>> 32))); + } + @Override + public String toString() { + return "TickClock[" + baseClock + "," + Duration.ofNanos(tickNanos) + "]"; + } + } + +} diff --git a/src/share/classes/java/time/DateTimeException.java b/src/share/classes/java/time/DateTimeException.java new file mode 100644 index 0000000000000000000000000000000000000000..e5e9efa85ca99191d89cd0c186902a9a43940aed --- /dev/null +++ b/src/share/classes/java/time/DateTimeException.java @@ -0,0 +1,101 @@ +/* + * 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 java.time; + +/** + * Exception used to indicate a problem while calculating a date-time. + *

+ * This exception is used to indicate problems with creating, querying + * and manipulating date-time objects. + * + *

Specification for implementors

+ * This class is intended for use in a single thread. + * + * @since 1.8 + */ +public 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); + } + +} diff --git a/src/share/classes/java/time/DayOfWeek.java b/src/share/classes/java/time/DayOfWeek.java new file mode 100644 index 0000000000000000000000000000000000000000..5d1f3f34772746104d28756ec9e9866cd225e627 --- /dev/null +++ b/src/share/classes/java/time/DayOfWeek.java @@ -0,0 +1,446 @@ +/* + * 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 java.time; + +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoUnit.DAYS; + +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.TextStyle; +import java.time.temporal.ChronoField; +import java.time.temporal.Queries; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.ValueRange; +import java.time.temporal.WeekFields; +import java.util.Locale; + +/** + * A day-of-week, such as 'Tuesday'. + *

+ * {@code DayOfWeek} is an enum representing the 7 days of the week - + * Monday, Tuesday, Wednesday, Thursday, Friday, Saturday and Sunday. + *

+ * In addition to the textual enum name, each day-of-week has an {@code int} value. + * The {@code int} value follows the ISO-8601 standard, from 1 (Monday) to 7 (Sunday). + * It is recommended that applications use the enum rather than the {@code int} value + * to ensure code clarity. + *

+ * This enum provides access to the localized textual form of the day-of-week. + * Some locales also assign different numeric values to the days, declaring + * Sunday to have the value 1, however this class provides no support for this. + * See {@link WeekFields} for localized week-numbering. + *

+ * Do not use {@code ordinal()} to obtain the numeric representation of {@code DayOfWeek}. + * Use {@code getValue()} instead. + *

+ * This enum represents a common concept that is found in many calendar systems. + * As such, this enum may be used by any calendar system that has the day-of-week + * concept defined exactly equivalent to the ISO calendar system. + * + *

Specification for implementors

+ * This is an immutable and thread-safe enum. + * + * @since 1.8 + */ +public enum DayOfWeek implements TemporalAccessor, TemporalAdjuster { + + /** + * The singleton instance for the day-of-week of Monday. + * This has the numeric value of {@code 1}. + */ + MONDAY, + /** + * The singleton instance for the day-of-week of Tuesday. + * This has the numeric value of {@code 2}. + */ + TUESDAY, + /** + * The singleton instance for the day-of-week of Wednesday. + * This has the numeric value of {@code 3}. + */ + WEDNESDAY, + /** + * The singleton instance for the day-of-week of Thursday. + * This has the numeric value of {@code 4}. + */ + THURSDAY, + /** + * The singleton instance for the day-of-week of Friday. + * This has the numeric value of {@code 5}. + */ + FRIDAY, + /** + * The singleton instance for the day-of-week of Saturday. + * This has the numeric value of {@code 6}. + */ + SATURDAY, + /** + * The singleton instance for the day-of-week of Sunday. + * This has the numeric value of {@code 7}. + */ + SUNDAY; + /** + * Private cache of all the constants. + */ + private static final DayOfWeek[] ENUMS = DayOfWeek.values(); + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code DayOfWeek} from an {@code int} value. + *

+ * {@code DayOfWeek} is an enum representing the 7 days of the week. + * This factory allows the enum to be obtained from the {@code int} value. + * The {@code int} value follows the ISO-8601 standard, from 1 (Monday) to 7 (Sunday). + * + * @param dayOfWeek the day-of-week to represent, from 1 (Monday) to 7 (Sunday) + * @return the day-of-week singleton, not null + * @throws DateTimeException if the day-of-week is invalid + */ + public static DayOfWeek of(int dayOfWeek) { + if (dayOfWeek < 1 || dayOfWeek > 7) { + throw new DateTimeException("Invalid value for DayOfWeek: " + dayOfWeek); + } + return ENUMS[dayOfWeek - 1]; + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code DayOfWeek} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code DayOfWeek}. + *

+ * The conversion extracts the {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} field. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used as a query via method reference, {@code DayOfWeek::from}. + * + * @param temporal the temporal object to convert, not null + * @return the day-of-week, not null + * @throws DateTimeException if unable to convert to a {@code DayOfWeek} + */ + public static DayOfWeek from(TemporalAccessor temporal) { + if (temporal instanceof DayOfWeek) { + return (DayOfWeek) temporal; + } + return of(temporal.get(DAY_OF_WEEK)); + } + + //----------------------------------------------------------------------- + /** + * Gets the day-of-week {@code int} value. + *

+ * The values are numbered following the ISO-8601 standard, from 1 (Monday) to 7 (Sunday). + * See {@link WeekFields#dayOfWeek} for localized week-numbering. + * + * @return the day-of-week, from 1 (Monday) to 7 (Sunday) + */ + public int getValue() { + return ordinal() + 1; + } + + //----------------------------------------------------------------------- + /** + * Gets the textual representation, such as 'Mon' or 'Friday'. + *

+ * This returns the textual name used to identify the day-of-week. + * The parameters control the length of the returned text and the locale. + *

+ * If no textual mapping is found then the {@link #getValue() numeric value} is returned. + * + * @param style the length of the text required, not null + * @param locale the locale to use, not null + * @return the text value of the day-of-week, not null + */ + public String getText(TextStyle style, Locale locale) { + return new DateTimeFormatterBuilder().appendText(DAY_OF_WEEK, style).toFormatter(locale).print(this); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this day-of-week can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and + * {@link #get(TemporalField) get} methods will throw an exception. + *

+ * If the field is {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} then + * this method returns true. + * All other {@code ChronoField} instances will return false. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the field is supported is determined by the field. + * + * @param field the field to check, null returns false + * @return true if the field is supported on this day-of-week, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == DAY_OF_WEEK; + } + return field != null && field.doIsSupported(this); + } + + /** + * Gets the range of valid values for the specified field. + *

+ * The range object expresses the minimum and maximum valid values for a field. + * This day-of-week is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} then the + * range of the day-of-week, from 1 to 7, will be returned. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + @Override + public ValueRange range(TemporalField field) { + if (field == DAY_OF_WEEK) { + return field.range(); + } + return TemporalAccessor.super.range(field); + } + + /** + * Gets the value of the specified field from this day-of-week as an {@code int}. + *

+ * This queries this day-of-week for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} then the + * value of the day-of-week, from 1 to 7, will be returned. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field, within the valid range of values + * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if the range of valid values for the field exceeds an {@code int} + * @throws DateTimeException if the value is outside the range of valid values for the field + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public int get(TemporalField field) { + if (field == DAY_OF_WEEK) { + return getValue(); + } + return TemporalAccessor.super.get(field); + } + + /** + * Gets the value of the specified field from this day-of-week as a {@code long}. + *

+ * This queries this day-of-week for the value for the specified field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} then the + * value of the day-of-week, from 1 to 7, will be returned. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long getLong(TemporalField field) { + if (field == DAY_OF_WEEK) { + return getValue(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doGet(this); + } + + //----------------------------------------------------------------------- + /** + * Returns the day-of-week that is the specified number of days after this one. + *

+ * The calculation rolls around the end of the week from Sunday to Monday. + * The specified period may be negative. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param days the days to add, positive or negative + * @return the resulting day-of-week, not null + */ + public DayOfWeek plus(long days) { + int amount = (int) (days % 7); + return ENUMS[(ordinal() + (amount + 7)) % 7]; + } + + /** + * Returns the day-of-week that is the specified number of days before this one. + *

+ * The calculation rolls around the start of the year from Monday to Sunday. + * The specified period may be negative. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param days the days to subtract, positive or negative + * @return the resulting day-of-week, not null + */ + public DayOfWeek minus(long days) { + return plus(-(days % 7)); + } + + //----------------------------------------------------------------------- + /** + * Queries this day-of-week using the specified query. + *

+ * This queries this day-of-week using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.precision()) { + return (R) DAYS; + } + return TemporalAccessor.super.query(query); + } + + /** + * Adjusts the specified temporal object to have this day-of-week. + *

+ * This returns a temporal object of the same observable type as the input + * with the day-of-week changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * passing {@link ChronoField#DAY_OF_WEEK} as the field. + * Note that this adjusts forwards or backwards within a Monday to Sunday week. + * See {@link WeekFields#dayOfWeek} for localized week start days. + * See {@link java.time.temporal.Adjusters Adjusters} for other adjusters + * with more control, such as {@code next(MONDAY)}. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisDayOfWeek.adjustInto(temporal);
+     *   temporal = temporal.with(thisDayOfWeek);
+     * 
+ *

+ * For example, given a date that is a Wednesday, the following are output: + *

+     *   dateOnWed.with(MONDAY);     // two days earlier
+     *   dateOnWed.with(TUESDAY);    // one day earlier
+     *   dateOnWed.with(WEDNESDAY);  // same date
+     *   dateOnWed.with(THURSDAY);   // one day later
+     *   dateOnWed.with(FRIDAY);     // two days later
+     *   dateOnWed.with(SATURDAY);   // three days later
+     *   dateOnWed.with(SUNDAY);     // four days later
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal adjustInto(Temporal temporal) { + return temporal.with(DAY_OF_WEEK, getValue()); + } + +} diff --git a/src/share/classes/java/time/Duration.java b/src/share/classes/java/time/Duration.java new file mode 100644 index 0000000000000000000000000000000000000000..e8d75fadba19af887858574fc497820daa3ad11e --- /dev/null +++ b/src/share/classes/java/time/Duration.java @@ -0,0 +1,1045 @@ +/* + * 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 java.time; + +import static java.time.LocalTime.SECONDS_PER_DAY; +import static java.time.temporal.ChronoField.INSTANT_SECONDS; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoUnit.DAYS; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.TemporalUnit; +import java.util.Objects; + +/** + * A duration between two instants on the time-line. + *

+ * This class models a duration of time and is not tied to any instant. + * The model is of a directed duration, meaning that the duration may be negative. + *

+ * A physical duration could be of infinite length. + * For practicality, the duration is stored with constraints similar to {@link Instant}. + * The duration uses nanosecond resolution with a maximum value of the seconds that can + * be held in a {@code long}. This is greater than the current estimated age of the universe. + *

+ * The range of a duration requires the storage of a number larger than a {@code long}. + * To achieve this, the class stores a {@code long} representing seconds and an {@code int} + * representing nanosecond-of-second, which will always be between 0 and 999,999,999. + *

+ * The duration is measured in "seconds", but these are not necessarily identical to + * the scientific "SI second" definition based on atomic clocks. + * This difference only impacts durations measured near a leap-second and should not affect + * most applications. + * See {@link Instant} for a discussion as to the meaning of the second and time-scales. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class Duration + implements TemporalAdder, TemporalSubtractor, Comparable, Serializable { + + /** + * Constant for a duration of zero. + */ + public static final Duration ZERO = new Duration(0, 0); + /** + * Serialization version. + */ + private static final long serialVersionUID = 3078945930695997490L; + /** + * Constant for nanos per second. + */ + private static final int NANOS_PER_SECOND = 1000_000_000; + /** + * Constant for nanos per second. + */ + private static final BigInteger BI_NANOS_PER_SECOND = BigInteger.valueOf(NANOS_PER_SECOND); + + /** + * The number of seconds in the duration. + */ + private final long seconds; + /** + * The number of nanoseconds in the duration, expressed as a fraction of the + * number of seconds. This is always positive, and never exceeds 999,999,999. + */ + private final int nanos; + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Duration} from a number of seconds. + *

+ * The nanosecond in second field is set to zero. + * + * @param seconds the number of seconds, positive or negative + * @return a {@code Duration}, not null + */ + public static Duration ofSeconds(long seconds) { + return create(seconds, 0); + } + + /** + * Obtains an instance of {@code Duration} from a number of seconds + * and an adjustment in nanoseconds. + *

+ * This method allows an arbitrary number of nanoseconds to be passed in. + * The factory will alter the values of the second and nanosecond in order + * to ensure that the stored nanosecond is in the range 0 to 999,999,999. + * For example, the following will result in the exactly the same duration: + *

+     *  Duration.ofSeconds(3, 1);
+     *  Duration.ofSeconds(4, -999_999_999);
+     *  Duration.ofSeconds(2, 1000_000_001);
+     * 
+ * + * @param seconds the number of seconds, positive or negative + * @param nanoAdjustment the nanosecond adjustment to the number of seconds, positive or negative + * @return a {@code Duration}, not null + * @throws ArithmeticException if the adjustment causes the seconds to exceed the capacity of {@code Duration} + */ + public static Duration ofSeconds(long seconds, long nanoAdjustment) { + long secs = Math.addExact(seconds, Math.floorDiv(nanoAdjustment, NANOS_PER_SECOND)); + int nos = (int)Math.floorMod(nanoAdjustment, NANOS_PER_SECOND); + return create(secs, nos); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Duration} from a number of milliseconds. + *

+ * The seconds and nanoseconds are extracted from the specified milliseconds. + * + * @param millis the number of milliseconds, positive or negative + * @return a {@code Duration}, not null + */ + public static Duration ofMillis(long millis) { + long secs = millis / 1000; + int mos = (int) (millis % 1000); + if (mos < 0) { + mos += 1000; + secs--; + } + return create(secs, mos * 1000_000); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Duration} from a number of nanoseconds. + *

+ * The seconds and nanoseconds are extracted from the specified nanoseconds. + * + * @param nanos the number of nanoseconds, positive or negative + * @return a {@code Duration}, not null + */ + public static Duration ofNanos(long nanos) { + long secs = nanos / NANOS_PER_SECOND; + int nos = (int) (nanos % NANOS_PER_SECOND); + if (nos < 0) { + nos += NANOS_PER_SECOND; + secs--; + } + return create(secs, nos); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Duration} from a number of standard length minutes. + *

+ * The seconds are calculated based on the standard definition of a minute, + * where each minute is 60 seconds. + * The nanosecond in second field is set to zero. + * + * @param minutes the number of minutes, positive or negative + * @return a {@code Duration}, not null + * @throws ArithmeticException if the input minutes exceeds the capacity of {@code Duration} + */ + public static Duration ofMinutes(long minutes) { + return create(Math.multiplyExact(minutes, 60), 0); + } + + /** + * Obtains an instance of {@code Duration} from a number of standard length hours. + *

+ * The seconds are calculated based on the standard definition of an hour, + * where each hour is 3600 seconds. + * The nanosecond in second field is set to zero. + * + * @param hours the number of hours, positive or negative + * @return a {@code Duration}, not null + * @throws ArithmeticException if the input hours exceeds the capacity of {@code Duration} + */ + public static Duration ofHours(long hours) { + return create(Math.multiplyExact(hours, 3600), 0); + } + + /** + * Obtains an instance of {@code Duration} from a number of standard 24 hour days. + *

+ * The seconds are calculated based on the standard definition of a day, + * where each day is 86400 seconds which implies a 24 hour day. + * The nanosecond in second field is set to zero. + * + * @param days the number of days, positive or negative + * @return a {@code Duration}, not null + * @throws ArithmeticException if the input days exceeds the capacity of {@code Duration} + */ + public static Duration ofDays(long days) { + return create(Math.multiplyExact(days, 86400), 0); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Duration} from a duration in the specified unit. + *

+ * The parameters represent the two parts of a phrase like '6 Hours'. For example: + *

+     *  Duration.of(3, SECONDS);
+     *  Duration.of(465, HOURS);
+     * 
+ * Only a subset of units are accepted by this method. + * The unit must either have an {@linkplain TemporalUnit#isDurationEstimated() exact duration} or + * be {@link ChronoUnit#DAYS} which is treated as 24 hours. Other units throw an exception. + * + * @param amount the amount of the duration, measured in terms of the unit, positive or negative + * @param unit the unit that the duration is measured in, must have an exact duration, not null + * @return a {@code Duration}, not null + * @throws DateTimeException if the period unit has an estimated duration + * @throws ArithmeticException if a numeric overflow occurs + */ + public static Duration of(long amount, TemporalUnit unit) { + return ZERO.plus(amount, unit); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Duration} representing the duration between two instants. + *

+ * A {@code Duration} represents a directed distance between two points on the time-line. + * As such, this method will return a negative duration if the end is before the start. + * To guarantee to obtain a positive duration call {@link #abs()} on the result of this factory. + * + * @param startInclusive the start instant, inclusive, not null + * @param endExclusive the end instant, exclusive, not null + * @return a {@code Duration}, not null + * @throws ArithmeticException if the calculation exceeds the capacity of {@code Duration} + */ + public static Duration between(TemporalAccessor startInclusive, TemporalAccessor endExclusive) { + long secs = Math.subtractExact(endExclusive.getLong(INSTANT_SECONDS), startInclusive.getLong(INSTANT_SECONDS)); + long nanos = endExclusive.getLong(NANO_OF_SECOND) - startInclusive.getLong(NANO_OF_SECOND); + secs = Math.addExact(secs, Math.floorDiv(nanos, NANOS_PER_SECOND)); + nanos = Math.floorMod(nanos, NANOS_PER_SECOND); + return create(secs, (int) nanos); // safe from overflow + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Duration} by parsing a text string. + *

+ * This will parse the string produced by {@link #toString()} which is + * the ISO-8601 format {@code PTnS} where {@code n} is + * the number of seconds with optional decimal part. + * The number must consist of ASCII numerals. + * There must only be a negative sign at the start of the number and it can + * only be present if the value is less than zero. + * There must be at least one digit before any decimal point. + * There must be between 1 and 9 inclusive digits after any decimal point. + * The letters (P, T and S) will be accepted in upper or lower case. + * The decimal point may be either a dot or a comma. + * + * @param text the text to parse, not null + * @return a {@code Duration}, not null + * @throws DateTimeParseException if the text cannot be parsed to a {@code Duration} + */ + public static Duration parse(final CharSequence text) { + Objects.requireNonNull(text, "text"); + int len = text.length(); + if (len < 4 || + (text.charAt(0) != 'P' && text.charAt(0) != 'p') || + (text.charAt(1) != 'T' && text.charAt(1) != 't') || + (text.charAt(len - 1) != 'S' && text.charAt(len - 1) != 's') || + (len == 5 && text.charAt(2) == '-' && text.charAt(3) == '0')) { + throw new DateTimeParseException("Duration could not be parsed: " + text, text, 0); + } + String numberText = text.subSequence(2, len - 1).toString().replace(',', '.'); + if (numberText.charAt(0) == '+') { + throw new DateTimeParseException("Duration could not be parsed: " + text, text, 2); + } + int dot = numberText.indexOf('.'); + try { + if (dot == -1) { + // no decimal places + if (numberText.startsWith("-0")) { + throw new DateTimeParseException("Duration could not be parsed: " + text, text, 2); + } + return create(Long.parseLong(numberText), 0); + } + // decimal places + boolean negative = false; + if (numberText.charAt(0) == '-') { + negative = true; + } + long secs = Long.parseLong(numberText.substring(0, dot)); + numberText = numberText.substring(dot + 1); + len = numberText.length(); + if (len == 0 || len > 9 || numberText.charAt(0) == '-' || numberText.charAt(0) == '+') { + throw new DateTimeParseException("Duration could not be parsed: " + text, text, 2); + } + int nanos = Integer.parseInt(numberText); + switch (len) { + case 1: + nanos *= 100000000; + break; + case 2: + nanos *= 10000000; + break; + case 3: + nanos *= 1000000; + break; + case 4: + nanos *= 100000; + break; + case 5: + nanos *= 10000; + break; + case 6: + nanos *= 1000; + break; + case 7: + nanos *= 100; + break; + case 8: + nanos *= 10; + break; + } + return negative ? ofSeconds(secs, -nanos) : create(secs, nanos); + + } catch (ArithmeticException | NumberFormatException ex) { + throw new DateTimeParseException("Duration could not be parsed: " + text, text, 2, ex); + } + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Duration} using seconds and nanoseconds. + * + * @param seconds the length of the duration in seconds, positive or negative + * @param nanoAdjustment the nanosecond adjustment within the second, from 0 to 999,999,999 + */ + private static Duration create(long seconds, int nanoAdjustment) { + if ((seconds | nanoAdjustment) == 0) { + return ZERO; + } + return new Duration(seconds, nanoAdjustment); + } + + /** + * Constructs an instance of {@code Duration} using seconds and nanoseconds. + * + * @param seconds the length of the duration in seconds, positive or negative + * @param nanos the nanoseconds within the second, from 0 to 999,999,999 + */ + private Duration(long seconds, int nanos) { + super(); + this.seconds = seconds; + this.nanos = nanos; + } + + //----------------------------------------------------------------------- + /** + * Checks if this duration is zero length. + *

+ * A {@code Duration} represents a directed distance between two points on + * the time-line and can therefore be positive, zero or negative. + * This method checks whether the length is zero. + * + * @return true if this duration has a total length equal to zero + */ + public boolean isZero() { + return (seconds | nanos) == 0; + } + + /** + * Checks if this duration is positive, excluding zero. + *

+ * A {@code Duration} represents a directed distance between two points on + * the time-line and can therefore be positive, zero or negative. + * This method checks whether the length is greater than zero. + * + * @return true if this duration has a total length greater than zero + */ + public boolean isPositive() { + return seconds >= 0 && ((seconds | nanos) != 0); + } + + /** + * Checks if this duration is negative, excluding zero. + *

+ * A {@code Duration} represents a directed distance between two points on + * the time-line and can therefore be positive, zero or negative. + * This method checks whether the length is less than zero. + * + * @return true if this duration has a total length less than zero + */ + public boolean isNegative() { + return seconds < 0; + } + + //----------------------------------------------------------------------- + /** + * Gets the number of seconds in this duration. + *

+ * The length of the duration is stored using two fields - seconds and nanoseconds. + * The nanoseconds part is a value from 0 to 999,999,999 that is an adjustment to + * the length in seconds. + * The total duration is defined by calling this method and {@link #getNano()}. + *

+ * A {@code Duration} represents a directed distance between two points on the time-line. + * A negative duration is expressed by the negative sign of the seconds part. + * A duration of -1 nanosecond is stored as -1 seconds plus 999,999,999 nanoseconds. + * + * @return the whole seconds part of the length of the duration, positive or negative + */ + public long getSeconds() { + return seconds; + } + + /** + * Gets the number of nanoseconds within the second in this duration. + *

+ * The length of the duration is stored using two fields - seconds and nanoseconds. + * The nanoseconds part is a value from 0 to 999,999,999 that is an adjustment to + * the length in seconds. + * The total duration is defined by calling this method and {@link #getSeconds()}. + *

+ * A {@code Duration} represents a directed distance between two points on the time-line. + * A negative duration is expressed by the negative sign of the seconds part. + * A duration of -1 nanosecond is stored as -1 seconds plus 999,999,999 nanoseconds. + * + * @return the nanoseconds within the second part of the length of the duration, from 0 to 999,999,999 + */ + public int getNano() { + return nanos; + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this duration with the specified duration added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param duration the duration to add, positive or negative, not null + * @return a {@code Duration} based on this duration with the specified duration added, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Duration plus(Duration duration) { + return plus(duration.getSeconds(), duration.getNano()); + } + + /** + * Returns a copy of this duration with the specified duration added. + *

+ * The duration amount is measured in terms of the specified unit. + * Only a subset of units are accepted by this method. + * The unit must either have an {@linkplain TemporalUnit#isDurationEstimated() exact duration} or + * be {@link ChronoUnit#DAYS} which is treated as 24 hours. Other units throw an exception. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToAdd the amount of the period, measured in terms of the unit, positive or negative + * @param unit the unit that the period is measured in, must have an exact duration, not null + * @return a {@code Duration} based on this duration with the specified duration added, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Duration plus(long amountToAdd, TemporalUnit unit) { + Objects.requireNonNull(unit, "unit"); + if (unit == DAYS) { + return plus(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY), 0); + } + if (unit.isDurationEstimated()) { + throw new DateTimeException("Unit must not have an estimated duration"); + } + if (amountToAdd == 0) { + return this; + } + if (unit instanceof ChronoUnit) { + switch ((ChronoUnit) unit) { + case NANOS: return plusNanos(amountToAdd); + case MICROS: return plusSeconds((amountToAdd / (1000_000L * 1000)) * 1000).plusNanos((amountToAdd % (1000_000L * 1000)) * 1000); + case MILLIS: return plusMillis(amountToAdd); + case SECONDS: return plusSeconds(amountToAdd); + } + return plusSeconds(Math.multiplyExact(unit.getDuration().seconds, amountToAdd)); + } + Duration duration = unit.getDuration().multipliedBy(amountToAdd); + return plusSeconds(duration.getSeconds()).plusNanos(duration.getNano()); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this duration with the specified duration in seconds added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param secondsToAdd the seconds to add, positive or negative + * @return a {@code Duration} based on this duration with the specified seconds added, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Duration plusSeconds(long secondsToAdd) { + return plus(secondsToAdd, 0); + } + + /** + * Returns a copy of this duration with the specified duration in milliseconds added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param millisToAdd the milliseconds to add, positive or negative + * @return a {@code Duration} based on this duration with the specified milliseconds added, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Duration plusMillis(long millisToAdd) { + return plus(millisToAdd / 1000, (millisToAdd % 1000) * 1000_000); + } + + /** + * Returns a copy of this duration with the specified duration in nanoseconds added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanosToAdd the nanoseconds to add, positive or negative + * @return a {@code Duration} based on this duration with the specified nanoseconds added, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Duration plusNanos(long nanosToAdd) { + return plus(0, nanosToAdd); + } + + /** + * Returns a copy of this duration with the specified duration added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param secondsToAdd the seconds to add, positive or negative + * @param nanosToAdd the nanos to add, positive or negative + * @return a {@code Duration} based on this duration with the specified seconds added, not null + * @throws ArithmeticException if numeric overflow occurs + */ + private Duration plus(long secondsToAdd, long nanosToAdd) { + if ((secondsToAdd | nanosToAdd) == 0) { + return this; + } + long epochSec = Math.addExact(seconds, secondsToAdd); + epochSec = Math.addExact(epochSec, nanosToAdd / NANOS_PER_SECOND); + nanosToAdd = nanosToAdd % NANOS_PER_SECOND; + long nanoAdjustment = nanos + nanosToAdd; // safe int+NANOS_PER_SECOND + return ofSeconds(epochSec, nanoAdjustment); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this duration with the specified duration subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param duration the duration to subtract, positive or negative, not null + * @return a {@code Duration} based on this duration with the specified duration subtracted, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Duration minus(Duration duration) { + long secsToSubtract = duration.getSeconds(); + int nanosToSubtract = duration.getNano(); + if (secsToSubtract == Long.MIN_VALUE) { + return plus(Long.MAX_VALUE, -nanosToSubtract).plus(1, 0); + } + return plus(-secsToSubtract, -nanosToSubtract); + } + + /** + * Returns a copy of this duration with the specified duration subtracted. + *

+ * The duration amount is measured in terms of the specified unit. + * Only a subset of units are accepted by this method. + * The unit must either have an {@linkplain TemporalUnit#isDurationEstimated() exact duration} or + * be {@link ChronoUnit#DAYS} which is treated as 24 hours. Other units throw an exception. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToSubtract the amount of the period, measured in terms of the unit, positive or negative + * @param unit the unit that the period is measured in, must have an exact duration, not null + * @return a {@code Duration} based on this duration with the specified duration subtracted, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Duration minus(long amountToSubtract, TemporalUnit unit) { + return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this duration with the specified duration in seconds subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param secondsToSubtract the seconds to subtract, positive or negative + * @return a {@code Duration} based on this duration with the specified seconds subtracted, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Duration minusSeconds(long secondsToSubtract) { + return (secondsToSubtract == Long.MIN_VALUE ? plusSeconds(Long.MAX_VALUE).plusSeconds(1) : plusSeconds(-secondsToSubtract)); + } + + /** + * Returns a copy of this duration with the specified duration in milliseconds subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param millisToSubtract the milliseconds to subtract, positive or negative + * @return a {@code Duration} based on this duration with the specified milliseconds subtracted, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Duration minusMillis(long millisToSubtract) { + return (millisToSubtract == Long.MIN_VALUE ? plusMillis(Long.MAX_VALUE).plusMillis(1) : plusMillis(-millisToSubtract)); + } + + /** + * Returns a copy of this duration with the specified duration in nanoseconds subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanosToSubtract the nanoseconds to subtract, positive or negative + * @return a {@code Duration} based on this duration with the specified nanoseconds subtracted, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Duration minusNanos(long nanosToSubtract) { + return (nanosToSubtract == Long.MIN_VALUE ? plusNanos(Long.MAX_VALUE).plusNanos(1) : plusNanos(-nanosToSubtract)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this duration multiplied by the scalar. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param multiplicand the value to multiply the duration by, positive or negative + * @return a {@code Duration} based on this duration multiplied by the specified scalar, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Duration multipliedBy(long multiplicand) { + if (multiplicand == 0) { + return ZERO; + } + if (multiplicand == 1) { + return this; + } + return create(toSeconds().multiply(BigDecimal.valueOf(multiplicand))); + } + + /** + * Returns a copy of this duration divided by the specified value. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param divisor the value to divide the duration by, positive or negative, not zero + * @return a {@code Duration} based on this duration divided by the specified divisor, not null + * @throws ArithmeticException if the divisor is zero + * @throws ArithmeticException if numeric overflow occurs + */ + public Duration dividedBy(long divisor) { + if (divisor == 0) { + throw new ArithmeticException("Cannot divide by zero"); + } + if (divisor == 1) { + return this; + } + return create(toSeconds().divide(BigDecimal.valueOf(divisor), RoundingMode.DOWN)); + } + + /** + * Converts this duration to the total length in seconds and + * fractional nanoseconds expressed as a {@code BigDecimal}. + * + * @return the total length of the duration in seconds, with a scale of 9, not null + */ + private BigDecimal toSeconds() { + return BigDecimal.valueOf(seconds).add(BigDecimal.valueOf(nanos, 9)); + } + + /** + * Creates an instance of {@code Duration} from a number of seconds. + * + * @param seconds the number of seconds, up to scale 9, positive or negative + * @return a {@code Duration}, not null + * @throws ArithmeticException if numeric overflow occurs + */ + private static Duration create(BigDecimal seconds) { + BigInteger nanos = seconds.movePointRight(9).toBigIntegerExact(); + BigInteger[] divRem = nanos.divideAndRemainder(BI_NANOS_PER_SECOND); + if (divRem[0].bitLength() > 63) { + throw new ArithmeticException("Exceeds capacity of Duration: " + nanos); + } + return ofSeconds(divRem[0].longValue(), divRem[1].intValue()); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this duration with the length negated. + *

+ * This method swaps the sign of the total length of this duration. + * For example, {@code PT1.3S} will be returned as {@code PT-1.3S}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @return a {@code Duration} based on this duration with the amount negated, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Duration negated() { + return multipliedBy(-1); + } + + /** + * Returns a copy of this duration with a positive length. + *

+ * This method returns a positive duration by effectively removing the sign from any negative total length. + * For example, {@code PT-1.3S} will be returned as {@code PT1.3S}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @return a {@code Duration} based on this duration with an absolute length, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Duration abs() { + return isNegative() ? negated() : this; + } + + //------------------------------------------------------------------------- + /** + * Adds this duration to the specified temporal object. + *

+ * This returns a temporal object of the same observable type as the input + * with this duration added. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#plus(TemporalAdder)}. + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   dateTime = thisDuration.addTo(dateTime);
+     *   dateTime = dateTime.plus(thisDuration);
+     * 
+ *

+ * A {@code Duration} can only be added to a {@code Temporal} that + * represents an instant and can supply {@link ChronoField#INSTANT_SECONDS}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the temporal object to adjust, not null + * @return an object of the same type with the adjustment made, not null + * @throws DateTimeException if unable to add + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal addTo(Temporal temporal) { + long instantSecs = temporal.getLong(INSTANT_SECONDS); + long instantNanos = temporal.getLong(NANO_OF_SECOND); + instantSecs = Math.addExact(instantSecs, seconds); + instantNanos = Math.addExact(instantNanos, nanos); + instantSecs = Math.addExact(instantSecs, Math.floorDiv(instantNanos, NANOS_PER_SECOND)); + instantNanos = Math.floorMod(instantNanos, NANOS_PER_SECOND); + return temporal.with(INSTANT_SECONDS, instantSecs).with(NANO_OF_SECOND, instantNanos); + } + + /** + * Subtracts this duration from the specified temporal object. + *

+ * This returns a temporal object of the same observable type as the input + * with this duration subtracted. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#minus(TemporalSubtractor)}. + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   dateTime = thisDuration.subtractFrom(dateTime);
+     *   dateTime = dateTime.minus(thisDuration);
+     * 
+ *

+ * A {@code Duration} can only be subtracted from a {@code Temporal} that + * represents an instant and can supply {@link ChronoField#INSTANT_SECONDS}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the temporal object to adjust, not null + * @return an object of the same type with the adjustment made, not null + * @throws DateTimeException if unable to subtract + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal subtractFrom(Temporal temporal) { + long instantSecs = temporal.getLong(INSTANT_SECONDS); + long instantNanos = temporal.getLong(NANO_OF_SECOND); + instantSecs = Math.subtractExact(instantSecs, seconds); + instantNanos = Math.subtractExact(instantNanos, nanos); + instantSecs = Math.addExact(instantSecs, Math.floorDiv(instantNanos, NANOS_PER_SECOND)); + instantNanos = Math.floorMod(instantNanos, NANOS_PER_SECOND); + return temporal.with(INSTANT_SECONDS, instantSecs).with(NANO_OF_SECOND, instantNanos); + } + + //----------------------------------------------------------------------- + /** + * Converts this duration to the total length in milliseconds. + *

+ * If this duration is too large to fit in a {@code long} milliseconds, then an + * exception is thrown. + *

+ * If this duration has greater than millisecond precision, then the conversion + * will drop any excess precision information as though the amount in nanoseconds + * was subject to integer division by one million. + * + * @return the total length of the duration in milliseconds + * @throws ArithmeticException if numeric overflow occurs + */ + public long toMillis() { + long millis = Math.multiplyExact(seconds, 1000); + millis = Math.addExact(millis, nanos / 1000_000); + return millis; + } + + /** + * Converts this duration to the total length in nanoseconds expressed as a {@code long}. + *

+ * If this duration is too large to fit in a {@code long} nanoseconds, then an + * exception is thrown. + * + * @return the total length of the duration in nanoseconds + * @throws ArithmeticException if numeric overflow occurs + */ + public long toNanos() { + long millis = Math.multiplyExact(seconds, 1000_000_000); + millis = Math.addExact(millis, nanos); + return millis; + } + + //----------------------------------------------------------------------- + /** + * Compares this duration to the specified {@code Duration}. + *

+ * The comparison is based on the total length of the durations. + * It is "consistent with equals", as defined by {@link Comparable}. + * + * @param otherDuration the other duration to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + @Override + public int compareTo(Duration otherDuration) { + int cmp = Long.compare(seconds, otherDuration.seconds); + if (cmp != 0) { + return cmp; + } + return nanos - otherDuration.nanos; + } + + /** + * Checks if this duration is greater than the specified {@code Duration}. + *

+ * The comparison is based on the total length of the durations. + * + * @param otherDuration the other duration to compare to, not null + * @return true if this duration is greater than the specified duration + */ + public boolean isGreaterThan(Duration otherDuration) { + return compareTo(otherDuration) > 0; + } + + /** + * Checks if this duration is less than the specified {@code Duration}. + *

+ * The comparison is based on the total length of the durations. + * + * @param otherDuration the other duration to compare to, not null + * @return true if this duration is less than the specified duration + */ + public boolean isLessThan(Duration otherDuration) { + return compareTo(otherDuration) < 0; + } + + //----------------------------------------------------------------------- + /** + * Checks if this duration is equal to the specified {@code Duration}. + *

+ * The comparison is based on the total length of the durations. + * + * @param otherDuration the other duration, null returns false + * @return true if the other duration is equal to this one + */ + @Override + public boolean equals(Object otherDuration) { + if (this == otherDuration) { + return true; + } + if (otherDuration instanceof Duration) { + Duration other = (Duration) otherDuration; + return this.seconds == other.seconds && + this.nanos == other.nanos; + } + return false; + } + + /** + * A hash code for this duration. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return ((int) (seconds ^ (seconds >>> 32))) + (51 * nanos); + } + + //----------------------------------------------------------------------- + /** + * A string representation of this duration using ISO-8601 seconds + * based representation, such as {@code PT12.345S}. + *

+ * The format of the returned string will be {@code PTnS} where n is + * the seconds and fractional seconds of the duration. + * + * @return an ISO-8601 representation of this duration, not null + */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder(24); + buf.append("PT"); + if (seconds < 0 && nanos > 0) { + if (seconds == -1) { + buf.append("-0"); + } else { + buf.append(seconds + 1); + } + } else { + buf.append(seconds); + } + if (nanos > 0) { + int pos = buf.length(); + if (seconds < 0) { + buf.append(2 * NANOS_PER_SECOND - nanos); + } else { + buf.append(nanos + NANOS_PER_SECOND); + } + while (buf.charAt(buf.length() - 1) == '0') { + buf.setLength(buf.length() - 1); + } + buf.setCharAt(pos, '.'); + } + buf.append('S'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(1);  // identifies this as a Duration
+     *  out.writeLong(seconds);
+     *  out.writeInt(nanos);
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.DURATION_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(DataOutput out) throws IOException { + out.writeLong(seconds); + out.writeInt(nanos); + } + + static Duration readExternal(DataInput in) throws IOException { + long seconds = in.readLong(); + int nanos = in.readInt(); + return Duration.ofSeconds(seconds, nanos); + } + +} diff --git a/src/share/classes/java/time/Instant.java b/src/share/classes/java/time/Instant.java new file mode 100644 index 0000000000000000000000000000000000000000..f253eb58e5192eda1db8f0e2f532704efa51e60e --- /dev/null +++ b/src/share/classes/java/time/Instant.java @@ -0,0 +1,1107 @@ +/* + * 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 java.time; + +import static java.time.LocalTime.SECONDS_PER_DAY; +import static java.time.LocalTime.SECONDS_PER_HOUR; +import static java.time.LocalTime.SECONDS_PER_MINUTE; +import static java.time.temporal.ChronoField.INSTANT_SECONDS; +import static java.time.temporal.ChronoField.MICRO_OF_SECOND; +import static java.time.temporal.ChronoField.MILLI_OF_SECOND; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoUnit.NANOS; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Queries; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.TemporalUnit; +import java.time.temporal.ValueRange; +import java.util.Objects; + +/** + * An instantaneous point on the time-line. + *

+ * This class models a single instantaneous point on the time-line. + * This might be used to record event time-stamps in the application. + *

+ * For practicality, the instant is stored with some constraints. + * The measurable time-line is restricted to the number of seconds that can be held + * in a {@code long}. This is greater than the current estimated age of the universe. + * The instant is stored to nanosecond resolution. + *

+ * The range of an instant requires the storage of a number larger than a {@code long}. + * To achieve this, the class stores a {@code long} representing epoch-seconds and an + * {@code int} representing nanosecond-of-second, which will always be between 0 and 999,999,999. + * The epoch-seconds are measured from the standard Java epoch of {@code 1970-01-01T00:00:00Z} + * where instants after the epoch have positive values, and earlier instants have negative values. + * For both the epoch-second and nanosecond parts, a larger value is always later on the time-line + * than a smaller value. + * + *

Time-scale

+ *

+ * The length of the solar day is the standard way that humans measure time. + * This has traditionally been subdivided into 24 hours of 60 minutes of 60 seconds, + * forming a 86400 second day. + *

+ * Modern timekeeping is based on atomic clocks which precisely define an SI second + * relative to the transitions of a Caesium atom. The length of an SI second was defined + * to be very close to the 86400th fraction of a day. + *

+ * Unfortunately, as the Earth rotates the length of the day varies. + * In addition, over time the average length of the day is getting longer as the Earth slows. + * As a result, the length of a solar day in 2012 is slightly longer than 86400 SI seconds. + * The actual length of any given day and the amount by which the Earth is slowing + * are not predictable and can only be determined by measurement. + * The UT1 time-scale captures the accurate length of day, but is only available some + * time after the day has completed. + *

+ * The UTC time-scale is a standard approach to bundle up all the additional fractions + * of a second from UT1 into whole seconds, known as leap-seconds. + * A leap-second may be added or removed depending on the Earth's rotational changes. + * As such, UTC permits a day to have 86399 SI seconds or 86401 SI seconds where + * necessary in order to keep the day aligned with the Sun. + *

+ * The modern UTC time-scale was introduced in 1972, introducing the concept of whole leap-seconds. + * Between 1958 and 1972, the definition of UTC was complex, with minor sub-second leaps and + * alterations to the length of the notional second. As of 2012, discussions are underway + * to change the definition of UTC again, with the potential to remove leap seconds or + * introduce other changes. + *

+ * Given the complexity of accurate timekeeping described above, this Java API defines + * its own time-scale with a simplification. The Java time-scale is defined as follows: + *

+ * Agreed international civil time is the base time-scale agreed by international convention, + * which in 2012 is UTC (with leap-seconds). + *

+ * In 2012, the definition of the Java time-scale is the same as UTC for all days except + * those where a leap-second occurs. On days where a leap-second does occur, the time-scale + * effectively eliminates the leap-second, maintaining the fiction of 86400 seconds in the day. + *

+ * The main benefit of always dividing the day into 86400 subdivisions is that it matches the + * expectations of most users of the API. The alternative is to force every user to understand + * what a leap second is and to force them to have special logic to handle them. + * Most applications do not have access to a clock that is accurate enough to record leap-seconds. + * Most applications also do not have a problem with a second being a very small amount longer or + * shorter than a real SI second during a leap-second. + *

+ * If an application does have access to an accurate clock that reports leap-seconds, then the + * recommended technique to implement the Java time-scale is to use the UTC-SLS convention. + * UTC-SLS effectively smoothes the + * leap-second over the last 1000 seconds of the day, making each of the last 1000 "seconds" + * 1/1000th longer or shorter than a real SI second. + *

+ * One final problem is the definition of the agreed international civil time before the + * introduction of modern UTC in 1972. This includes the Java epoch of {@code 1970-01-01}. + * It is intended that instants before 1972 be interpreted based on the solar day divided + * into 86400 subdivisions. + *

+ * The Java time-scale is used for all date-time classes. + * This includes {@code Instant}, {@code LocalDate}, {@code LocalTime}, {@code OffsetDateTime}, + * {@code ZonedDateTime} and {@code Duration}. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class Instant + implements Temporal, TemporalAdjuster, Comparable, Serializable { + + /** + * Constant for the 1970-01-01T00:00:00Z epoch instant. + */ + public static final Instant EPOCH = new Instant(0, 0); + /** + * The minimum supported epoch second. + */ + private static final long MIN_SECOND = -31557014167219200L; + /** + * The maximum supported epoch second. + */ + private static final long MAX_SECOND = 31556889864403199L; + /** + * The minimum supported {@code Instant}, '-1000000000-01-01T00:00Z'. + * This could be used by an application as a "far past" instant. + *

+ * This is one year earlier than the minimum {@code LocalDateTime}. + * This provides sufficient values to handle the range of {@code ZoneOffset} + * which affect the instant in addition to the local date-time. + * The value is also chosen such that the value of the year fits in + * an {@code int}. + */ + public static final Instant MIN = Instant.ofEpochSecond(MIN_SECOND, 0); + /** + * The minimum supported {@code Instant}, '-1000000000-01-01T00:00Z'. + * This could be used by an application as a "far future" instant. + *

+ * This is one year later than the maximum {@code LocalDateTime}. + * This provides sufficient values to handle the range of {@code ZoneOffset} + * which affect the instant in addition to the local date-time. + * The value is also chosen such that the value of the year fits in + * an {@code int}. + */ + public static final Instant MAX = Instant.ofEpochSecond(MAX_SECOND, 999_999_999); + + /** + * Serialization version. + */ + private static final long serialVersionUID = -665713676816604388L; + /** + * Constant for nanos per second. + */ + private static final int NANOS_PER_SECOND = 1000_000_000; + + /** + * The number of seconds from the epoch of 1970-01-01T00:00:00Z. + */ + private final long seconds; + /** + * The number of nanoseconds, later along the time-line, from the seconds field. + * This is always positive, and never exceeds 999,999,999. + */ + private final int nanos; + + //----------------------------------------------------------------------- + /** + * Obtains the current instant from the system clock. + *

+ * This will query the {@link Clock#systemUTC() system UTC clock} to + * obtain the current instant. + *

+ * Using this method will prevent the ability to use an alternate time-source for + * testing because the clock is effectively hard-coded. + * + * @return the current instant using the system clock, not null + */ + public static Instant now() { + return Clock.systemUTC().instant(); + } + + /** + * Obtains the current instant from the specified clock. + *

+ * This will query the specified clock to obtain the current time. + *

+ * Using this method allows the use of an alternate clock for testing. + * The alternate clock may be introduced using {@link Clock dependency injection}. + * + * @param clock the clock to use, not null + * @return the current instant, not null + */ + public static Instant now(Clock clock) { + Objects.requireNonNull(clock, "clock"); + return clock.instant(); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Instant} using seconds from the + * epoch of 1970-01-01T00:00:00Z. + *

+ * The nanosecond field is set to zero. + * + * @param epochSecond the number of seconds from 1970-01-01T00:00:00Z + * @return an instant, not null + * @throws DateTimeException if the instant exceeds the maximum or minimum instant + */ + public static Instant ofEpochSecond(long epochSecond) { + return create(epochSecond, 0); + } + + /** + * Obtains an instance of {@code Instant} using seconds from the + * epoch of 1970-01-01T00:00:00Z and nanosecond fraction of second. + *

+ * This method allows an arbitrary number of nanoseconds to be passed in. + * The factory will alter the values of the second and nanosecond in order + * to ensure that the stored nanosecond is in the range 0 to 999,999,999. + * For example, the following will result in the exactly the same instant: + *

+     *  Instant.ofSeconds(3, 1);
+     *  Instant.ofSeconds(4, -999_999_999);
+     *  Instant.ofSeconds(2, 1000_000_001);
+     * 
+ * + * @param epochSecond the number of seconds from 1970-01-01T00:00:00Z + * @param nanoAdjustment the nanosecond adjustment to the number of seconds, positive or negative + * @return an instant, not null + * @throws DateTimeException if the instant exceeds the maximum or minimum instant + * @throws ArithmeticException if numeric overflow occurs + */ + public static Instant ofEpochSecond(long epochSecond, long nanoAdjustment) { + long secs = Math.addExact(epochSecond, Math.floorDiv(nanoAdjustment, NANOS_PER_SECOND)); + int nos = (int)Math.floorMod(nanoAdjustment, NANOS_PER_SECOND); + return create(secs, nos); + } + + /** + * Obtains an instance of {@code Instant} using milliseconds from the + * epoch of 1970-01-01T00:00:00Z. + *

+ * The seconds and nanoseconds are extracted from the specified milliseconds. + * + * @param epochMilli the number of milliseconds from 1970-01-01T00:00:00Z + * @return an instant, not null + * @throws DateTimeException if the instant exceeds the maximum or minimum instant + */ + public static Instant ofEpochMilli(long epochMilli) { + long secs = Math.floorDiv(epochMilli, 1000); + int mos = (int)Math.floorMod(epochMilli, 1000); + return create(secs, mos * 1000_000); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Instant} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code Instant}. + *

+ * The conversion extracts the {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS} + * and {@link ChronoField#NANO_OF_SECOND NANO_OF_SECOND} fields. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used as a query via method reference, {@code Instant::from}. + * + * @param temporal the temporal object to convert, not null + * @return the instant, not null + * @throws DateTimeException if unable to convert to an {@code Instant} + */ + public static Instant from(TemporalAccessor temporal) { + long instantSecs = temporal.getLong(INSTANT_SECONDS); + int nanoOfSecond = temporal.get(NANO_OF_SECOND); + return Instant.ofEpochSecond(instantSecs, nanoOfSecond); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Instant} from a text string such as + * {@code 2007-12-03T10:15:30:00}. + *

+ * The string must represent a valid instant in UTC and is parsed using + * {@link DateTimeFormatters#isoInstant()}. + * + * @param text the text to parse, not null + * @return the parsed instant, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static Instant parse(final CharSequence text) { + return DateTimeFormatters.isoInstant().parse(text, Instant::from); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Instant} using seconds and nanoseconds. + * + * @param seconds the length of the duration in seconds + * @param nanoOfSecond the nano-of-second, from 0 to 999,999,999 + * @throws DateTimeException if the instant exceeds the maximum or minimum instant + */ + private static Instant create(long seconds, int nanoOfSecond) { + if ((seconds | nanoOfSecond) == 0) { + return EPOCH; + } + if (seconds < MIN_SECOND || seconds > MAX_SECOND) { + throw new DateTimeException("Instant exceeds minimum or maximum instant"); + } + return new Instant(seconds, nanoOfSecond); + } + + /** + * Constructs an instance of {@code Instant} using seconds from the epoch of + * 1970-01-01T00:00:00Z and nanosecond fraction of second. + * + * @param epochSecond the number of seconds from 1970-01-01T00:00:00Z + * @param nanos the nanoseconds within the second, must be positive + */ + private Instant(long epochSecond, int nanos) { + super(); + this.seconds = epochSecond; + this.nanos = nanos; + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this instant can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and + * {@link #get(TemporalField) get} methods will throw an exception. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The supported fields are: + *

    + *
  • {@code NANO_OF_SECOND} + *
  • {@code MICRO_OF_SECOND} + *
  • {@code MILLI_OF_SECOND} + *
  • {@code INSTANT_SECONDS} + *
+ * All other {@code ChronoField} instances will return false. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the field is supported is determined by the field. + * + * @param field the field to check, null returns false + * @return true if the field is supported on this instant, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == INSTANT_SECONDS || field == NANO_OF_SECOND || field == MICRO_OF_SECOND || field == MILLI_OF_SECOND; + } + return field != null && field.doIsSupported(this); + } + + /** + * Gets the range of valid values for the specified field. + *

+ * The range object expresses the minimum and maximum valid values for a field. + * This instant is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return + * appropriate range instances. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + @Override // override for Javadoc + public ValueRange range(TemporalField field) { + return Temporal.super.range(field); + } + + /** + * Gets the value of the specified field from this instant as an {@code int}. + *

+ * This queries this instant for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date-time, except {@code INSTANT_SECONDS} which is too + * large to fit in an {@code int} and throws a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override // override for Javadoc and performance + public int get(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case NANO_OF_SECOND: return nanos; + case MICRO_OF_SECOND: return nanos / 1000; + case MILLI_OF_SECOND: return nanos / 1000_000; + case INSTANT_SECONDS: INSTANT_SECONDS.checkValidIntValue(seconds); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return range(field).checkValidIntValue(field.doGet(this), field); + } + + /** + * Gets the value of the specified field from this instant as a {@code long}. + *

+ * This queries this instant for the value for the specified field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date-time. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case NANO_OF_SECOND: return nanos; + case MICRO_OF_SECOND: return nanos / 1000; + case MILLI_OF_SECOND: return nanos / 1000_000; + case INSTANT_SECONDS: return seconds; + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doGet(this); + } + + //----------------------------------------------------------------------- + /** + * Gets the number of seconds from the Java epoch of 1970-01-01T00:00:00Z. + *

+ * The epoch second count is a simple incrementing count of seconds where + * second 0 is 1970-01-01T00:00:00Z. + * The nanosecond part of the day is returned by {@code getNanosOfSecond}. + * + * @return the seconds from the epoch of 1970-01-01T00:00:00Z + */ + public long getEpochSecond() { + return seconds; + } + + /** + * Gets the number of nanoseconds, later along the time-line, from the start + * of the second. + *

+ * The nanosecond-of-second value measures the total number of nanoseconds from + * the second returned by {@code getEpochSecond}. + * + * @return the nanoseconds within the second, always positive, never exceeds 999,999,999 + */ + public int getNano() { + return nanos; + } + + //------------------------------------------------------------------------- + /** + * Returns an adjusted copy of this instant. + *

+ * This returns a new {@code Instant}, based on this one, with the date adjusted. + * The adjustment takes place using the specified adjuster strategy object. + * Read the documentation of the adjuster to understand what adjustment will be made. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalAdjuster#adjustInto(Temporal)} method on the + * specified adjuster passing {@code this} as the argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adjuster the adjuster to use, not null + * @return an {@code Instant} based on {@code this} with the adjustment made, not null + * @throws DateTimeException if the adjustment cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Instant with(TemporalAdjuster adjuster) { + return (Instant) adjuster.adjustInto(this); + } + + /** + * Returns a copy of this instant with the specified field set to a new value. + *

+ * This returns a new {@code Instant}, based on this one, with the value + * for the specified field changed. + * If it is not possible to set the value, because the field is not supported or for + * some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the adjustment is implemented here. + * The supported fields behave as follows: + *

    + *
  • {@code NANO_OF_SECOND} - + * Returns an {@code Instant} with the specified nano-of-second. + * The epoch-second will be unchanged. + *
  • {@code MICRO_OF_SECOND} - + * Returns an {@code Instant} with the nano-of-second replaced by the specified + * micro-of-second multiplied by 1,000. The epoch-second will be unchanged. + *
  • {@code MILLI_OF_SECOND} - + * Returns an {@code Instant} with the nano-of-second replaced by the specified + * milli-of-second multiplied by 1,000,000. The epoch-second will be unchanged. + *
  • {@code INSTANT_SECONDS} - + * Returns an {@code Instant} with the specified epoch-second. + * The nano-of-second will be unchanged. + *
+ *

+ * In all cases, if the new value is outside the valid range of values for the field + * then a {@code DateTimeException} will be thrown. + *

+ * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doWith(Temporal, long)} + * passing {@code this} as the argument. In this case, the field determines + * whether and how to adjust the instant. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param field the field to set in the result, not null + * @param newValue the new value of the field in the result + * @return an {@code Instant} based on {@code this} with the specified field set, not null + * @throws DateTimeException if the field cannot be set + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Instant with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + f.checkValidValue(newValue); + switch (f) { + case MILLI_OF_SECOND: { + int nval = (int) newValue * 1000_000; + return (nval != nanos ? create(seconds, nval) : this); + } + case MICRO_OF_SECOND: { + int nval = (int) newValue * 1000; + return (nval != nanos ? create(seconds, nval) : this); + } + case NANO_OF_SECOND: return (newValue != nanos ? create(seconds, (int) newValue) : this); + case INSTANT_SECONDS: return (newValue != seconds ? create(newValue, nanos) : this); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doWith(this, newValue); + } + + //----------------------------------------------------------------------- + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public Instant plus(TemporalAdder adder) { + return (Instant) adder.addTo(this); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public Instant plus(long amountToAdd, TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + switch ((ChronoUnit) unit) { + case NANOS: return plusNanos(amountToAdd); + case MICROS: return plus(amountToAdd / 1000_000, (amountToAdd % 1000_000) * 1000); + case MILLIS: return plusMillis(amountToAdd); + case SECONDS: return plusSeconds(amountToAdd); + case MINUTES: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_MINUTE)); + case HOURS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_HOUR)); + case HALF_DAYS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY / 2)); + case DAYS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY)); + } + throw new DateTimeException("Unsupported unit: " + unit.getName()); + } + return unit.doPlus(this, amountToAdd); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this instant with the specified duration in seconds added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param secondsToAdd the seconds to add, positive or negative + * @return an {@code Instant} based on this instant with the specified seconds added, not null + * @throws DateTimeException if the result exceeds the maximum or minimum instant + * @throws ArithmeticException if numeric overflow occurs + */ + public Instant plusSeconds(long secondsToAdd) { + return plus(secondsToAdd, 0); + } + + /** + * Returns a copy of this instant with the specified duration in milliseconds added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param millisToAdd the milliseconds to add, positive or negative + * @return an {@code Instant} based on this instant with the specified milliseconds added, not null + * @throws DateTimeException if the result exceeds the maximum or minimum instant + * @throws ArithmeticException if numeric overflow occurs + */ + public Instant plusMillis(long millisToAdd) { + return plus(millisToAdd / 1000, (millisToAdd % 1000) * 1000_000); + } + + /** + * Returns a copy of this instant with the specified duration in nanoseconds added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanosToAdd the nanoseconds to add, positive or negative + * @return an {@code Instant} based on this instant with the specified nanoseconds added, not null + * @throws DateTimeException if the result exceeds the maximum or minimum instant + * @throws ArithmeticException if numeric overflow occurs + */ + public Instant plusNanos(long nanosToAdd) { + return plus(0, nanosToAdd); + } + + /** + * Returns a copy of this instant with the specified duration added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param secondsToAdd the seconds to add, positive or negative + * @param nanosToAdd the nanos to add, positive or negative + * @return an {@code Instant} based on this instant with the specified seconds added, not null + * @throws DateTimeException if the result exceeds the maximum or minimum instant + * @throws ArithmeticException if numeric overflow occurs + */ + private Instant plus(long secondsToAdd, long nanosToAdd) { + if ((secondsToAdd | nanosToAdd) == 0) { + return this; + } + long epochSec = Math.addExact(seconds, secondsToAdd); + epochSec = Math.addExact(epochSec, nanosToAdd / NANOS_PER_SECOND); + nanosToAdd = nanosToAdd % NANOS_PER_SECOND; + long nanoAdjustment = nanos + nanosToAdd; // safe int+NANOS_PER_SECOND + return ofEpochSecond(epochSec, nanoAdjustment); + } + + //----------------------------------------------------------------------- + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public Instant minus(TemporalSubtractor subtractor) { + return (Instant) subtractor.subtractFrom(this); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public Instant minus(long amountToSubtract, TemporalUnit unit) { + return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this instant with the specified duration in seconds subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param secondsToSubtract the seconds to subtract, positive or negative + * @return an {@code Instant} based on this instant with the specified seconds subtracted, not null + * @throws DateTimeException if the result exceeds the maximum or minimum instant + * @throws ArithmeticException if numeric overflow occurs + */ + public Instant minusSeconds(long secondsToSubtract) { + if (secondsToSubtract == Long.MIN_VALUE) { + return plusSeconds(Long.MAX_VALUE).plusSeconds(1); + } + return plusSeconds(-secondsToSubtract); + } + + /** + * Returns a copy of this instant with the specified duration in milliseconds subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param millisToSubtract the milliseconds to subtract, positive or negative + * @return an {@code Instant} based on this instant with the specified milliseconds subtracted, not null + * @throws DateTimeException if the result exceeds the maximum or minimum instant + * @throws ArithmeticException if numeric overflow occurs + */ + public Instant minusMillis(long millisToSubtract) { + if (millisToSubtract == Long.MIN_VALUE) { + return plusMillis(Long.MAX_VALUE).plusMillis(1); + } + return plusMillis(-millisToSubtract); + } + + /** + * Returns a copy of this instant with the specified duration in nanoseconds subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanosToSubtract the nanoseconds to subtract, positive or negative + * @return an {@code Instant} based on this instant with the specified nanoseconds subtracted, not null + * @throws DateTimeException if the result exceeds the maximum or minimum instant + * @throws ArithmeticException if numeric overflow occurs + */ + public Instant minusNanos(long nanosToSubtract) { + if (nanosToSubtract == Long.MIN_VALUE) { + return plusNanos(Long.MAX_VALUE).plusNanos(1); + } + return plusNanos(-nanosToSubtract); + } + + //------------------------------------------------------------------------- + /** + * Queries this instant using the specified query. + *

+ * This queries this instant using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.precision()) { + return (R) NANOS; + } + // inline TemporalAccessor.super.query(query) as an optimization + if (query == Queries.chrono() || query == Queries.zoneId() || query == Queries.zone() || query == Queries.offset()) { + return null; + } + return query.queryFrom(this); + } + + /** + * Adjusts the specified temporal object to have this instant. + *

+ * This returns a temporal object of the same observable type as the input + * with the instant changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * twice, passing {@link ChronoField#INSTANT_SECONDS} and + * {@link ChronoField#NANO_OF_SECOND} as the fields. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisInstant.adjustInto(temporal);
+     *   temporal = temporal.with(thisInstant);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal adjustInto(Temporal temporal) { + return temporal.with(INSTANT_SECONDS, seconds).with(NANO_OF_SECOND, nanos); + } + + /** + * Calculates the period between this instant and another instant in + * terms of the specified unit. + *

+ * This calculates the period between two instants in terms of a single unit. + * The start and end points are {@code this} and the specified instant. + * The result will be negative if the end is before the start. + * The calculation returns a whole number, representing the number of + * complete units between the two instants. + * The {@code Temporal} passed to this method must be an {@code Instant}. + * For example, the period in days between two dates can be calculated + * using {@code startInstant.periodUntil(endInstant, SECONDS)}. + *

+ * This method operates in association with {@link TemporalUnit#between}. + * The result of this method is a {@code long} representing the amount of + * the specified unit. By contrast, the result of {@code between} is an + * object that can be used directly in addition/subtraction: + *

+     *   long period = start.periodUntil(end, SECONDS);   // this method
+     *   dateTime.plus(SECONDS.between(start, end));      // use in plus/minus
+     * 
+ *

+ * The calculation is implemented in this method for {@link ChronoUnit}. + * The units {@code NANOS}, {@code MICROS}, {@code MILLIS}, {@code SECONDS}, + * {@code MINUTES}, {@code HOURS}, {@code HALF_DAYS} and {@code DAYS} + * are supported. Other {@code ChronoUnit} values will throw an exception. + *

+ * If the unit is not a {@code ChronoUnit}, then the result of this method + * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)} + * passing {@code this} as the first argument and the input temporal as + * the second argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param endInstant the end date, which must be a {@code LocalDate}, not null + * @param unit the unit to measure the period in, not null + * @return the amount of the period between this date and the end date + * @throws DateTimeException if the period cannot be calculated + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long periodUntil(Temporal endInstant, TemporalUnit unit) { + if (endInstant instanceof Instant == false) { + Objects.requireNonNull(endInstant, "endInstant"); + throw new DateTimeException("Unable to calculate period between objects of two different types"); + } + Instant end = (Instant) endInstant; + if (unit instanceof ChronoUnit) { + ChronoUnit f = (ChronoUnit) unit; + switch (f) { + case NANOS: return nanosUntil(end); + case MICROS: return nanosUntil(end) / 1000; + case MILLIS: return Math.subtractExact(end.toEpochMilli(), toEpochMilli()); + case SECONDS: return secondsUntil(end); + case MINUTES: return secondsUntil(end) / SECONDS_PER_MINUTE; + case HOURS: return secondsUntil(end) / SECONDS_PER_HOUR; + case HALF_DAYS: return secondsUntil(end) / (12 * SECONDS_PER_HOUR); + case DAYS: return secondsUntil(end) / (SECONDS_PER_DAY); + } + throw new DateTimeException("Unsupported unit: " + unit.getName()); + } + return unit.between(this, endInstant).getAmount(); + } + + private long nanosUntil(Instant end) { + long secs = Math.multiplyExact(secondsUntil(end), NANOS_PER_SECOND); + return Math.addExact(secs, end.nanos - nanos); + } + + private long secondsUntil(Instant end) { + return Math.subtractExact(end.seconds, seconds); + } + + //----------------------------------------------------------------------- + /** + * Converts this instant to the number of milliseconds from the epoch + * of 1970-01-01T00:00:00Z. + *

+ * If this instant represents a point on the time-line too far in the future + * or past to fit in a {@code long} milliseconds, then an exception is thrown. + *

+ * If this instant has greater than millisecond precision, then the conversion + * will drop any excess precision information as though the amount in nanoseconds + * was subject to integer division by one million. + * + * @return the number of milliseconds since the epoch of 1970-01-01T00:00:00Z + * @throws ArithmeticException if numeric overflow occurs + */ + public long toEpochMilli() { + long millis = Math.multiplyExact(seconds, 1000); + return millis + nanos / 1000_000; + } + + //----------------------------------------------------------------------- + /** + * Compares this instant to the specified instant. + *

+ * The comparison is based on the time-line position of the instants. + * It is "consistent with equals", as defined by {@link Comparable}. + * + * @param otherInstant the other instant to compare to, not null + * @return the comparator value, negative if less, positive if greater + * @throws NullPointerException if otherInstant is null + */ + @Override + public int compareTo(Instant otherInstant) { + int cmp = Long.compare(seconds, otherInstant.seconds); + if (cmp != 0) { + return cmp; + } + return nanos - otherInstant.nanos; + } + + /** + * Checks if this instant is after the specified instant. + *

+ * The comparison is based on the time-line position of the instants. + * + * @param otherInstant the other instant to compare to, not null + * @return true if this instant is after the specified instant + * @throws NullPointerException if otherInstant is null + */ + public boolean isAfter(Instant otherInstant) { + return compareTo(otherInstant) > 0; + } + + /** + * Checks if this instant is before the specified instant. + *

+ * The comparison is based on the time-line position of the instants. + * + * @param otherInstant the other instant to compare to, not null + * @return true if this instant is before the specified instant + * @throws NullPointerException if otherInstant is null + */ + public boolean isBefore(Instant otherInstant) { + return compareTo(otherInstant) < 0; + } + + //----------------------------------------------------------------------- + /** + * Checks if this instant is equal to the specified instant. + *

+ * The comparison is based on the time-line position of the instants. + * + * @param otherInstant the other instant, null returns false + * @return true if the other instant is equal to this one + */ + @Override + public boolean equals(Object otherInstant) { + if (this == otherInstant) { + return true; + } + if (otherInstant instanceof Instant) { + Instant other = (Instant) otherInstant; + return this.seconds == other.seconds && + this.nanos == other.nanos; + } + return false; + } + + /** + * Returns a hash code for this instant. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return ((int) (seconds ^ (seconds >>> 32))) + 51 * nanos; + } + + //----------------------------------------------------------------------- + /** + * A string representation of this instant using ISO-8601 representation. + *

+ * The format used is the same as {@link DateTimeFormatters#isoInstant()}. + * + * @return an ISO-8601 representation of this instant, not null + */ + @Override + public String toString() { + return DateTimeFormatters.isoInstant().print(this); + } + + // ----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(2);  // identifies this as an Instant
+     *  out.writeLong(seconds);
+     *  out.writeInt(nanos);
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.INSTANT_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(DataOutput out) throws IOException { + out.writeLong(seconds); + out.writeInt(nanos); + } + + static Instant readExternal(DataInput in) throws IOException { + long seconds = in.readLong(); + int nanos = in.readInt(); + return Instant.ofEpochSecond(seconds, nanos); + } + +} diff --git a/src/share/classes/java/time/LocalDate.java b/src/share/classes/java/time/LocalDate.java new file mode 100644 index 0000000000000000000000000000000000000000..8d40f72871fb3e21293fb12218cb9f4f9553e0d2 --- /dev/null +++ b/src/share/classes/java/time/LocalDate.java @@ -0,0 +1,1875 @@ +/* + * 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 java.time; + +import static java.time.LocalTime.SECONDS_PER_DAY; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.EPOCH_MONTH; +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.format.DateTimeBuilder; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Era; +import java.time.temporal.ISOChrono; +import java.time.temporal.OffsetDate; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.TemporalUnit; +import java.time.temporal.ValueRange; +import java.time.temporal.Year; +import java.time.zone.ZoneOffsetTransition; +import java.time.zone.ZoneRules; +import java.util.Objects; + +/** + * A date without a time-zone in the ISO-8601 calendar system, + * such as {@code 2007-12-03}. + *

+ * {@code LocalDate} is an immutable date-time object that represents a date, + * often viewed as year-month-day. Other date fields, such as day-of-year, + * day-of-week and week-of-year, can also be accessed. + * For example, the value "2nd October 2007" can be stored in a {@code LocalDate}. + *

+ * This class does not store or represent a time or time-zone. + * Instead, it is a description of the date, as used for birthdays. + * It cannot represent an instant on the time-line without additional information + * such as an offset or time-zone. + *

+ * The ISO-8601 calendar system is the modern civil calendar system used today + * in most of the world. It is equivalent to the proleptic Gregorian calendar + * system, in which today's rules for leap years are applied for all time. + * For most applications written today, the ISO-8601 rules are entirely suitable. + * However, any application that makes use of historical dates, and requires them + * to be accurate will find the ISO-8601 approach unsuitable. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class LocalDate + implements Temporal, TemporalAdjuster, ChronoLocalDate, Serializable { + + /** + * 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 = LocalDate.of(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 = LocalDate.of(Year.MAX_VALUE, 12, 31); + + /** + * Serialization version. + */ + private static final long serialVersionUID = 2942565459149668126L; + /** + * 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 the current date from the system clock in the default time-zone. + *

+ * This will query the {@link Clock#systemDefaultZone() system clock} in the default + * time-zone to obtain the current date. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @return the current date using the system clock and default time-zone, not null + */ + public static LocalDate now() { + return now(Clock.systemDefaultZone()); + } + + /** + * Obtains the current date from the system clock in the specified time-zone. + *

+ * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date. + * Specifying the time-zone avoids dependence on the default time-zone. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @param zone the zone ID to use, not null + * @return the current date using the system clock, not null + */ + public static LocalDate now(ZoneId zone) { + return now(Clock.system(zone)); + } + + /** + * Obtains the current date from the specified clock. + *

+ * This will query the specified clock to obtain the current date - today. + * Using this method allows the use of an alternate clock for testing. + * The alternate clock may be introduced using {@link Clock dependency injection}. + * + * @param clock the clock to use, not null + * @return the current date, not null + */ + public static LocalDate now(Clock clock) { + Objects.requireNonNull(clock, "clock"); + // inline OffsetDate factory to avoid creating object and InstantProvider checks + final Instant now = clock.instant(); // called once + ZoneOffset offset = clock.getZone().getRules().getOffset(now); + long epochSec = now.getEpochSecond() + offset.getTotalSeconds(); // overflow caught later + long epochDay = Math.floorDiv(epochSec, SECONDS_PER_DAY); + return LocalDate.ofEpochDay(epochDay); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code LocalDate} from a year, month and day. + *

+ * 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, not null + * @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, Month month, int dayOfMonth) { + YEAR.checkValidValue(year); + Objects.requireNonNull(month, "month"); + DAY_OF_MONTH.checkValidValue(dayOfMonth); + return create(year, month, dayOfMonth); + } + + /** + * Obtains an instance of {@code LocalDate} from a year, month and day. + *

+ * 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); + return create(year, Month.of(month), dayOfMonth); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code LocalDate} from a year and day-of-year. + *

+ * The day-of-year must be valid for the year, otherwise an exception will be thrown. + * + * @param year the year to represent, from MIN_YEAR to MAX_YEAR + * @param dayOfYear the day-of-year to represent, from 1 to 366 + * @return the local date, not null + * @throws DateTimeException if the value of any field is out of range + * @throws DateTimeException if the day-of-year is invalid for the month-year + */ + public static LocalDate ofYearDay(int year, int dayOfYear) { + YEAR.checkValidValue(year); + DAY_OF_YEAR.checkValidValue(dayOfYear); + boolean leap = ISOChrono.INSTANCE.isLeapYear(year); + if (dayOfYear == 366 && leap == false) { + throw new DateTimeException("Invalid date 'DayOfYear 366' as '" + year + "' is not a leap year"); + } + Month moy = Month.of((dayOfYear - 1) / 31 + 1); + int monthEnd = moy.firstDayOfYear(leap) + moy.length(leap) - 1; + if (dayOfYear > monthEnd) { + moy = moy.plus(1); + } + int dom = dayOfYear - moy.firstDayOfYear(leap) + 1; + return create(year, moy, dom); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code LocalDate} from the epoch day count. + *

+ * 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.checkValidIntValue(yearEst); + return new LocalDate(year, month, dom); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code LocalDate} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code LocalDate}. + *

+ * The conversion extracts the {@link ChronoField#EPOCH_DAY EPOCH_DAY} field. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used as a query via method reference, {@code LocalDate::from}. + * + * @param temporal the temporal object to convert, not null + * @return the local date, not null + * @throws DateTimeException if unable to convert to a {@code LocalDate} + */ + public static LocalDate from(TemporalAccessor temporal) { + if (temporal instanceof LocalDate) { + return (LocalDate) temporal; + } else if (temporal instanceof LocalDateTime) { + return ((LocalDateTime) temporal).getDate(); + } else if (temporal instanceof ZonedDateTime) { + return ((ZonedDateTime) temporal).getDate(); + } + // handle builder as a special case + if (temporal instanceof DateTimeBuilder) { + DateTimeBuilder builder = (DateTimeBuilder) temporal; + LocalDate date = builder.extract(LocalDate.class); + if (date != null) { + return date; + } + } + try { + return ofEpochDay(temporal.getLong(EPOCH_DAY)); + } catch (DateTimeException ex) { + throw new DateTimeException("Unable to obtain LocalDate from TemporalAccessor: " + temporal.getClass(), ex); + } + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code LocalDate} from a text string such as {@code 2007-12-03}. + *

+ * The string must represent a valid date and is parsed using + * {@link java.time.format.DateTimeFormatters#isoLocalDate()}. + * + * @param text the text to parse such as "2007-12-03", not null + * @return the parsed local date, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static LocalDate parse(CharSequence text) { + return parse(text, DateTimeFormatters.isoLocalDate()); + } + + /** + * Obtains an instance of {@code LocalDate} from a text string using a specific formatter. + *

+ * The text is parsed using the formatter, returning a date. + * + * @param text the text to parse, not null + * @param formatter the formatter to use, not null + * @return the parsed local date, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static LocalDate parse(CharSequence text, DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.parse(text, LocalDate::from); + } + + //----------------------------------------------------------------------- + /** + * Creates a local date from the year, month and day fields. + * + * @param year the year to represent, validated from MIN_YEAR to MAX_YEAR + * @param month the month-of-year to represent, validated not null + * @param dayOfMonth the day-of-month to represent, validated from 1 to 31 + * @return the local date, not null + * @throws DateTimeException if the day-of-month is invalid for the month-year + */ + private static LocalDate create(int year, Month month, int dayOfMonth) { + if (dayOfMonth > 28 && dayOfMonth > month.length(ISOChrono.INSTANCE.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.name() + " " + dayOfMonth + "'"); + } + } + return new LocalDate(year, month.getValue(), dayOfMonth); + } + + /** + * Resolves the date, resolving days past the end of month. + * + * @param year the year to represent, validated from MIN_YEAR to MAX_YEAR + * @param month the month-of-year to represent, validated from 1 to 12 + * @param day the day-of-month to represent, validated from 1 to 31 + * @return the resolved date, not null + */ + private static LocalDate resolvePreviousValid(int year, int month, int day) { + switch (month) { + case 2: + day = Math.min(day, ISOChrono.INSTANCE.isLeapYear(year) ? 29 : 28); + break; + case 4: + case 6: + case 9: + case 11: + day = Math.min(day, 30); + break; + } + return LocalDate.of(year, month, day); + } + + /** + * 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; + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this date can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and + * {@link #get(TemporalField) get} methods will throw an exception. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date-time. + * The supported fields are: + *

    + *
  • {@code DAY_OF_WEEK} + *
  • {@code ALIGNED_DAY_OF_WEEK_IN_MONTH} + *
  • {@code ALIGNED_DAY_OF_WEEK_IN_YEAR} + *
  • {@code DAY_OF_MONTH} + *
  • {@code DAY_OF_YEAR} + *
  • {@code EPOCH_DAY} + *
  • {@code ALIGNED_WEEK_OF_MONTH} + *
  • {@code ALIGNED_WEEK_OF_YEAR} + *
  • {@code MONTH_OF_YEAR} + *
  • {@code EPOCH_MONTH} + *
  • {@code YEAR_OF_ERA} + *
  • {@code YEAR} + *
  • {@code ERA} + *
+ * All other {@code ChronoField} instances will return false. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the field is supported is determined by the field. + * + * @param field the field to check, null returns false + * @return true if the field is supported on this date, false if not + */ + @Override // override for Javadoc + public boolean isSupported(TemporalField field) { + return ChronoLocalDate.super.isSupported(field); + } + + /** + * Gets the range of valid values for the specified field. + *

+ * The range object expresses the minimum and maximum valid values for a field. + * This date is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return + * appropriate range instances. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + @Override + public ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + if (f.isDateField()) { + switch (f) { + case DAY_OF_MONTH: return ValueRange.of(1, lengthOfMonth()); + case DAY_OF_YEAR: return ValueRange.of(1, lengthOfYear()); + case ALIGNED_WEEK_OF_MONTH: return ValueRange.of(1, getMonth() == Month.FEBRUARY && isLeapYear() == false ? 4 : 5); + case YEAR_OF_ERA: + return (getYear() <= 0 ? ValueRange.of(1, Year.MAX_VALUE + 1) : ValueRange.of(1, Year.MAX_VALUE)); + } + return field.range(); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doRange(this); + } + + /** + * Gets the value of the specified field from this date as an {@code int}. + *

+ * This queries this date for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date, except {@code EPOCH_DAY} and {@code EPOCH_MONTH} + * which are too large to fit in an {@code int} and throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override // override for Javadoc and performance + public int get(TemporalField field) { + if (field instanceof ChronoField) { + return get0(field); + } + return ChronoLocalDate.super.get(field); + } + + /** + * Gets the value of the specified field from this date as a {@code long}. + *

+ * This queries this date for the value for the specified field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + if (field == EPOCH_DAY) { + return toEpochDay(); + } + if (field == EPOCH_MONTH) { + return getEpochMonth(); + } + return get0(field); + } + return field.doGet(this); + } + + private int get0(TemporalField field) { + switch ((ChronoField) field) { + case DAY_OF_WEEK: return getDayOfWeek().getValue(); + case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((day - 1) % 7) + 1; + case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((getDayOfYear() - 1) % 7) + 1; + case DAY_OF_MONTH: return day; + case DAY_OF_YEAR: return getDayOfYear(); + case EPOCH_DAY: throw new DateTimeException("Field too large for an int: " + field); + case ALIGNED_WEEK_OF_MONTH: return ((day - 1) / 7) + 1; + case ALIGNED_WEEK_OF_YEAR: return ((getDayOfYear() - 1) / 7) + 1; + case MONTH_OF_YEAR: return month; + case EPOCH_MONTH: throw new DateTimeException("Field too large for an int: " + field); + case YEAR_OF_ERA: return (year >= 1 ? year : 1 - year); + case YEAR: return year; + case ERA: return (year >= 1 ? 1 : 0); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + + private long getEpochMonth() { + return ((year - 1970) * 12L) + (month - 1); + } + + //----------------------------------------------------------------------- + /** + * Gets the chronology of this date, which is the ISO calendar system. + *

+ * The {@code Chrono} represents the calendar system in use. + * The ISO-8601 calendar system is the modern civil calendar system used today + * in most of the world. It is equivalent to the proleptic Gregorian calendar + * system, in which todays's rules for leap years are applied for all time. + * + * @return the ISO chronology, not null + */ + @Override + public ISOChrono getChrono() { + return ISOChrono.INSTANCE; + } + + /** + * Gets the era applicable at this date. + *

+ * The official ISO-8601 standard does not define eras, however {@code ISOChrono} does. + * It defines two eras, 'CE' from year one onwards and 'BCE' from year zero backwards. + * Since dates before the Julian-Gregorian cutover are not in line with history, + * the cutover between 'BCE' and 'CE' is also not aligned with the commonly used + * eras, often referred to using 'BC' and 'AD'. + *

+ * Users of this class should typically ignore this method as it exists primarily + * to fulfill the {@link ChronoLocalDate} contract where it is necessary to support + * the Japanese calendar system. + *

+ * The returned era will be a singleton capable of being compared with the constants + * in {@link ISOChrono} using the {@code ==} operator. + * + * @return the {@code ISOChrono} era constant applicable at this date, not null + */ + @Override // override for Javadoc + public Era getEra() { + return ChronoLocalDate.super.getEra(); + } + + /** + * Gets the year field. + *

+ * This method returns the primitive {@code int} value for the year. + *

+ * 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 from 1 to 12. + *

+ * This method returns the month as an {@code int} from 1 to 12. + * Application code is frequently clearer if the enum {@link Month} + * is used by calling {@link #getMonth()}. + * + * @return the month-of-year, from 1 to 12 + * @see #getMonth() + */ + public int getMonthValue() { + return month; + } + + /** + * Gets the month-of-year field using the {@code Month} enum. + *

+ * This method returns the enum {@link Month} for the month. + * This avoids confusion as to what {@code int} values mean. + * If you need access to the primitive {@code int} value then the enum + * provides the {@link Month#getValue() int value}. + * + * @return the month-of-year, not null + * @see #getMonthValue() + */ + public Month getMonth() { + return Month.of(month); + } + + /** + * Gets the day-of-month field. + *

+ * 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-year field. + *

+ * This method returns the primitive {@code int} value for the day-of-year. + * + * @return the day-of-year, from 1 to 365, or 366 in a leap year + */ + public int getDayOfYear() { + return getMonth().firstDayOfYear(isLeapYear()) + day - 1; + } + + /** + * Gets the day-of-week field, which is an enum {@code DayOfWeek}. + *

+ * This method returns the enum {@link DayOfWeek} for the day-of-week. + * This avoids confusion as to what {@code int} values mean. + * If you need access to the primitive {@code int} value then the enum + * provides the {@link DayOfWeek#getValue() int value}. + *

+ * Additional information can be obtained from the {@code DayOfWeek}. + * This includes textual names of the values. + * + * @return the day-of-week, not null + */ + public DayOfWeek getDayOfWeek() { + int dow0 = (int)Math.floorMod(toEpochDay() + 3, 7); + return DayOfWeek.of(dow0 + 1); + } + + //----------------------------------------------------------------------- + /** + * Checks if the year is a leap year, according to the ISO proleptic + * calendar system rules. + *

+ * This method applies the current rules for leap years across the whole time-line. + * In general, a year is a leap year if it is divisible by four without + * remainder. However, years divisible by 100, are not leap years, with + * the exception of years divisible by 400 which are. + *

+ * For example, 1904 is a leap year it is divisible by 4. + * 1900 was not a leap year as it is divisible by 100, however 2000 was a + * leap year as it is divisible by 400. + *

+ * The calculation is proleptic - applying the same rules into the far future and far past. + * This is historically inaccurate, but is correct for the ISO-8601 standard. + * + * @return true if the year is leap, false otherwise + */ + @Override // override for Javadoc and performance + public boolean isLeapYear() { + return ISOChrono.INSTANCE.isLeapYear(year); + } + + /** + * Returns the length of the month represented by this date. + *

+ * This returns the length of the month in days. + * For example, a date in January would return 31. + * + * @return the length of the month in days + */ + @Override + public int lengthOfMonth() { + switch (month) { + case 2: + return (isLeapYear() ? 29 : 28); + case 4: + case 6: + case 9: + case 11: + return 30; + default: + return 31; + } + } + + /** + * Returns the length of the year represented by this date. + *

+ * This returns the length of the year in days, either 365 or 366. + * + * @return 366 if the year is leap, 365 otherwise + */ + @Override // override for Javadoc and performance + public int lengthOfYear() { + return (isLeapYear() ? 366 : 365); + } + + //----------------------------------------------------------------------- + /** + * Returns an adjusted copy of this date. + *

+ * This returns a new {@code LocalDate}, based on this one, with the date adjusted. + * The adjustment takes place using the specified adjuster strategy object. + * Read the documentation of the adjuster to understand what adjustment will be made. + *

+ * A simple adjuster might simply set the one of the fields, such as the year field. + * A more complex adjuster might set the date to the last day of the month. + * A selection of common adjustments is provided in {@link java.time.temporal.Adjusters}. + * These include finding the "last day of the month" and "next Wednesday". + * Key date-time classes also implement the {@code TemporalAdjuster} interface, + * such as {@link Month} and {@link java.time.temporal.MonthDay MonthDay}. + * The adjuster is responsible for handling special cases, such as the varying + * lengths of month and leap years. + *

+ * For example this code returns a date on the last day of July: + *

+     *  import static java.time.Month.*;
+     *  import static java.time.temporal.Adjusters.*;
+     *
+     *  result = localDate.with(JULY).with(lastDayOfMonth());
+     * 
+ *

+ * The result of this method is obtained by invoking the + * {@link TemporalAdjuster#adjustInto(Temporal)} method on the + * specified adjuster passing {@code this} as the argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adjuster the adjuster to use, not null + * @return a {@code LocalDate} based on {@code this} with the adjustment made, not null + * @throws DateTimeException if the adjustment cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public LocalDate with(TemporalAdjuster adjuster) { + // optimizations + if (adjuster instanceof LocalDate) { + return (LocalDate) adjuster; + } + return (LocalDate) adjuster.adjustInto(this); + } + + /** + * Returns a copy of this date with the specified field set to a new value. + *

+ * This returns a new {@code LocalDate}, based on this one, with the value + * for the specified field changed. + * This can be used to change any supported field, such as the year, month or day-of-month. + * If it is not possible to set the value, because the field is not supported or for + * some other reason, an exception is thrown. + *

+ * In some cases, changing the specified field can cause the resulting date to become invalid, + * such as changing the month from 31st January to February would make the day-of-month invalid. + * In cases like this, the field is responsible for resolving the date. Typically it will choose + * the previous valid date, which would be the last valid day of February in this example. + *

+ * If the field is a {@link ChronoField} then the adjustment is implemented here. + * The supported fields behave as follows: + *

    + *
  • {@code DAY_OF_WEEK} - + * Returns a {@code LocalDate} with the specified day-of-week. + * The date is adjusted up to 6 days forward or backward within the boundary + * of a Monday to Sunday week. + *
  • {@code ALIGNED_DAY_OF_WEEK_IN_MONTH} - + * Returns a {@code LocalDate} with the specified aligned-day-of-week. + * The date is adjusted to the specified month-based aligned-day-of-week. + * Aligned weeks are counted such that the first week of a given month starts + * on the first day of that month. + * This may cause the date to be moved up to 6 days into the following month. + *
  • {@code ALIGNED_DAY_OF_WEEK_IN_YEAR} - + * Returns a {@code LocalDate} with the specified aligned-day-of-week. + * The date is adjusted to the specified year-based aligned-day-of-week. + * Aligned weeks are counted such that the first week of a given year starts + * on the first day of that year. + * This may cause the date to be moved up to 6 days into the following year. + *
  • {@code DAY_OF_MONTH} - + * Returns a {@code LocalDate} with the specified day-of-month. + * The month and year will be unchanged. If the day-of-month is invalid for the + * year and month, then a {@code DateTimeException} is thrown. + *
  • {@code DAY_OF_YEAR} - + * Returns a {@code LocalDate} with the specified day-of-year. + * The year will be unchanged. If the day-of-year is invalid for the + * year, then a {@code DateTimeException} is thrown. + *
  • {@code EPOCH_DAY} - + * Returns a {@code LocalDate} with the specified epoch-day. + * This completely replaces the date and is equivalent to {@link #ofEpochDay(long)}. + *
  • {@code ALIGNED_WEEK_OF_MONTH} - + * Returns a {@code LocalDate} with the specified aligned-week-of-month. + * Aligned weeks are counted such that the first week of a given month starts + * on the first day of that month. + * This adjustment moves the date in whole week chunks to match the specified week. + * The result will have the same day-of-week as this date. + * This may cause the date to be moved into the following month. + *
  • {@code ALIGNED_WEEK_OF_YEAR} - + * Returns a {@code LocalDate} with the specified aligned-week-of-year. + * Aligned weeks are counted such that the first week of a given year starts + * on the first day of that year. + * This adjustment moves the date in whole week chunks to match the specified week. + * The result will have the same day-of-week as this date. + * This may cause the date to be moved into the following year. + *
  • {@code MONTH_OF_YEAR} - + * Returns a {@code LocalDate} with the specified month-of-year. + * The year will be unchanged. The day-of-month will also be unchanged, + * unless it would be invalid for the new month and year. In that case, the + * day-of-month is adjusted to the maximum valid value for the new month and year. + *
  • {@code EPOCH_MONTH} - + * Returns a {@code LocalDate} with the specified epoch-month. + * The day-of-month will be unchanged, unless it would be invalid for the new month + * and year. In that case, the day-of-month is adjusted to the maximum valid value + * for the new month and year. + *
  • {@code YEAR_OF_ERA} - + * Returns a {@code LocalDate} with the specified year-of-era. + * The era and month will be unchanged. The day-of-month will also be unchanged, + * unless it would be invalid for the new month and year. In that case, the + * day-of-month is adjusted to the maximum valid value for the new month and year. + *
  • {@code YEAR} - + * Returns a {@code LocalDate} with the specified year. + * The month will be unchanged. The day-of-month will also be unchanged, + * unless it would be invalid for the new month and year. In that case, the + * day-of-month is adjusted to the maximum valid value for the new month and year. + *
  • {@code ERA} - + * Returns a {@code LocalDate} with the specified era. + * The year-of-era and month will be unchanged. The day-of-month will also be unchanged, + * unless it would be invalid for the new month and year. In that case, the + * day-of-month is adjusted to the maximum valid value for the new month and year. + *
+ *

+ * In all cases, if the new value is outside the valid range of values for the field + * then a {@code DateTimeException} will be thrown. + *

+ * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doWith(Temporal, long)} + * passing {@code this} as the argument. In this case, the field determines + * whether and how to adjust the instant. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param field the field to set in the result, not null + * @param newValue the new value of the field in the result + * @return a {@code LocalDate} based on {@code this} with the specified field set, not null + * @throws DateTimeException if the field cannot be set + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public LocalDate with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + f.checkValidValue(newValue); + switch (f) { + case DAY_OF_WEEK: return plusDays(newValue - getDayOfWeek().getValue()); + case ALIGNED_DAY_OF_WEEK_IN_MONTH: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_MONTH)); + case ALIGNED_DAY_OF_WEEK_IN_YEAR: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_YEAR)); + case DAY_OF_MONTH: return withDayOfMonth((int) newValue); + case DAY_OF_YEAR: return withDayOfYear((int) newValue); + case EPOCH_DAY: return LocalDate.ofEpochDay(newValue); + case ALIGNED_WEEK_OF_MONTH: return plusWeeks(newValue - getLong(ALIGNED_WEEK_OF_MONTH)); + case ALIGNED_WEEK_OF_YEAR: return plusWeeks(newValue - getLong(ALIGNED_WEEK_OF_YEAR)); + case MONTH_OF_YEAR: return withMonth((int) newValue); + case EPOCH_MONTH: return plusMonths(newValue - getLong(EPOCH_MONTH)); + case YEAR_OF_ERA: return withYear((int) (year >= 1 ? newValue : 1 - newValue)); + case YEAR: return withYear((int) newValue); + case ERA: return (getLong(ERA) == newValue ? this : withYear(1 - year)); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doWith(this, newValue); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date with the year altered. + * If the day-of-month is invalid for the year, it will be changed to the last valid day of the month. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param year the year to set in the result, from MIN_YEAR to MAX_YEAR + * @return a {@code LocalDate} based on this date with the requested year, not null + * @throws DateTimeException if the year value is invalid + */ + public LocalDate withYear(int year) { + if (this.year == year) { + return this; + } + YEAR.checkValidValue(year); + return resolvePreviousValid(year, month, day); + } + + /** + * Returns a copy of this date with the month-of-year altered. + * If the day-of-month is invalid for the year, it will be changed to the last valid day of the month. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param month the month-of-year to set in the result, from 1 (January) to 12 (December) + * @return a {@code LocalDate} based on this date with the requested month, not null + * @throws DateTimeException if the month-of-year value is invalid + */ + public LocalDate withMonth(int month) { + if (this.month == month) { + return this; + } + MONTH_OF_YEAR.checkValidValue(month); + return resolvePreviousValid(year, month, day); + } + + /** + * Returns a copy of this date with the day-of-month altered. + * If the resulting date is invalid, an exception is thrown. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param dayOfMonth the day-of-month to set in the result, from 1 to 28-31 + * @return a {@code LocalDate} based on this date with the requested day, not null + * @throws DateTimeException if the day-of-month value is invalid + * @throws DateTimeException if the day-of-month is invalid for the month-year + */ + public LocalDate withDayOfMonth(int dayOfMonth) { + if (this.day == dayOfMonth) { + return this; + } + return of(year, month, dayOfMonth); + } + + /** + * Returns a copy of this date with the day-of-year altered. + * If the resulting date is invalid, an exception is thrown. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param dayOfYear the day-of-year to set in the result, from 1 to 365-366 + * @return a {@code LocalDate} based on this date with the requested day, not null + * @throws DateTimeException if the day-of-year value is invalid + * @throws DateTimeException if the day-of-year is invalid for the year + */ + public LocalDate withDayOfYear(int dayOfYear) { + if (this.getDayOfYear() == dayOfYear) { + return this; + } + return ofYearDay(year, dayOfYear); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date with the specified period added. + *

+ * This method returns a new date based on this date with the specified period added. + * The adder is typically {@link Period} but may be any other type implementing + * the {@link TemporalAdder} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #plus(long, TemporalUnit)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adder the adder to use, not null + * @return a {@code LocalDate} based on this date with the addition made, not null + * @throws DateTimeException if the addition cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public LocalDate plus(TemporalAdder adder) { + return (LocalDate) adder.addTo(this); + } + + /** + * Returns a copy of this date with the specified period added. + *

+ * This method returns a new date based on this date with the specified period added. + * This can be used to add any period that is defined by a unit, for example to add years, months or days. + * The unit is responsible for the details of the calculation, including the resolution + * of any edge cases in the calculation. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToAdd the amount of the unit to add to the result, may be negative + * @param unit the unit of the period to add, not null + * @return a {@code LocalDate} based on this date with the specified period added, not null + * @throws DateTimeException if the unit cannot be added to this type + */ + @Override + public LocalDate plus(long amountToAdd, TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + ChronoUnit f = (ChronoUnit) unit; + switch (f) { + case DAYS: return plusDays(amountToAdd); + case WEEKS: return plusWeeks(amountToAdd); + case MONTHS: return plusMonths(amountToAdd); + case YEARS: return plusYears(amountToAdd); + case DECADES: return plusYears(Math.multiplyExact(amountToAdd, 10)); + case CENTURIES: return plusYears(Math.multiplyExact(amountToAdd, 100)); + case MILLENNIA: return plusYears(Math.multiplyExact(amountToAdd, 1000)); + case ERAS: return with(ERA, Math.addExact(getLong(ERA), amountToAdd)); + } + throw new DateTimeException("Unsupported unit: " + unit.getName()); + } + return unit.doPlus(this, amountToAdd); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code LocalDate} with the specified period in years added. + *

+ * This method adds the specified amount to the years field in three steps: + *

    + *
  1. Add the input years to the year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2008-02-29 (leap year) plus one year would result in the + * invalid date 2009-02-29 (standard year). Instead of returning an invalid + * result, the last valid day of the month, 2009-02-28, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param yearsToAdd the years to add, may be negative + * @return a {@code LocalDate} based on this date with the years added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDate plusYears(long yearsToAdd) { + if (yearsToAdd == 0) { + return this; + } + int newYear = YEAR.checkValidIntValue(year + yearsToAdd); // safe overflow + return resolvePreviousValid(newYear, month, day); + } + + /** + * Returns a copy of this {@code LocalDate} with the specified period in months added. + *

+ * This method adds the specified amount to the months field in three steps: + *

    + *
  1. Add the input months to the month-of-year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2007-03-31 plus one month would result in the invalid date + * 2007-04-31. Instead of returning an invalid result, the last valid day + * of the month, 2007-04-30, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param monthsToAdd the months to add, may be negative + * @return a {@code LocalDate} based on this date with the months added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDate plusMonths(long monthsToAdd) { + if (monthsToAdd == 0) { + return this; + } + long monthCount = year * 12L + (month - 1); + long calcMonths = monthCount + monthsToAdd; // safe overflow + int newYear = YEAR.checkValidIntValue(Math.floorDiv(calcMonths, 12)); + int newMonth = (int)Math.floorMod(calcMonths, 12) + 1; + return resolvePreviousValid(newYear, newMonth, day); + } + + /** + * Returns a copy of this {@code LocalDate} with the specified period in weeks added. + *

+ * This method adds the specified amount in weeks 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. + *

+ * For example, 2008-12-31 plus one week would result in 2009-01-07. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param weeksToAdd the weeks to add, may be negative + * @return a {@code LocalDate} based on this date with the weeks added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDate plusWeeks(long weeksToAdd) { + return plusDays(Math.multiplyExact(weeksToAdd, 7)); + } + + /** + * Returns a copy of this {@code LocalDate} with the specified number of days added. + *

+ * 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. + *

+ * For example, 2008-12-31 plus one day would result in 2009-01-01. + *

+ * 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 = Math.addExact(toEpochDay(), daysToAdd); + return LocalDate.ofEpochDay(mjDay); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date with the specified period subtracted. + *

+ * This method returns a new date based on this date with the specified period subtracted. + * The subtractor is typically {@link Period} but may be any other type implementing + * the {@link TemporalSubtractor} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #minus(long, TemporalUnit)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param subtractor the subtractor to use, not null + * @return a {@code LocalDate} based on this date with the subtraction made, not null + * @throws DateTimeException if the subtraction cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public LocalDate minus(TemporalSubtractor subtractor) { + return (LocalDate) subtractor.subtractFrom(this); + } + + /** + * Returns a copy of this date with the specified period subtracted. + *

+ * This method returns a new date based on this date with the specified period subtracted. + * This can be used to subtract any period that is defined by a unit, for example to subtract years, months or days. + * The unit is responsible for the details of the calculation, including the resolution + * of any edge cases in the calculation. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToSubtract the amount of the unit to subtract from the result, may be negative + * @param unit the unit of the period to subtract, not null + * @return a {@code LocalDate} based on this date with the specified period subtracted, not null + * @throws DateTimeException if the unit cannot be added to this type + */ + @Override + public LocalDate minus(long amountToSubtract, TemporalUnit unit) { + return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code LocalDate} with the specified period in years subtracted. + *

+ * This method subtracts the specified amount from the years field in three steps: + *

    + *
  1. Subtract the input years to the year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2008-02-29 (leap year) minus one year would result in the + * invalid date 2007-02-29 (standard year). Instead of returning an invalid + * result, the last valid day of the month, 2007-02-28, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param yearsToSubtract the years to subtract, may be negative + * @return a {@code LocalDate} based on this date with the years subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDate minusYears(long yearsToSubtract) { + return (yearsToSubtract == Long.MIN_VALUE ? plusYears(Long.MAX_VALUE).plusYears(1) : plusYears(-yearsToSubtract)); + } + + /** + * Returns a copy of this {@code LocalDate} with the specified period in months subtracted. + *

+ * This method subtracts the specified amount from the months field in three steps: + *

    + *
  1. Subtract the input months to the month-of-year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2007-03-31 minus one month would result in the invalid date + * 2007-02-31. Instead of returning an invalid result, the last valid day + * of the month, 2007-02-28, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param monthsToSubtract the months to subtract, may be negative + * @return a {@code LocalDate} based on this date with the months subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDate minusMonths(long monthsToSubtract) { + return (monthsToSubtract == Long.MIN_VALUE ? plusMonths(Long.MAX_VALUE).plusMonths(1) : plusMonths(-monthsToSubtract)); + } + + /** + * Returns a copy of this {@code LocalDate} with the specified period in weeks subtracted. + *

+ * This method subtracts the specified amount in weeks 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. + *

+ * For example, 2009-01-07 minus one week would result in 2008-12-31. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param weeksToSubtract the weeks to subtract, may be negative + * @return a {@code LocalDate} based on this date with the weeks subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDate minusWeeks(long weeksToSubtract) { + return (weeksToSubtract == Long.MIN_VALUE ? plusWeeks(Long.MAX_VALUE).plusWeeks(1) : plusWeeks(-weeksToSubtract)); + } + + /** + * Returns a copy of this {@code LocalDate} with the specified number of days subtracted. + *

+ * 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. + *

+ * For example, 2009-01-01 minus one day would result in 2008-12-31. + *

+ * 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)); + } + + //----------------------------------------------------------------------- + /** + * Queries this date using the specified query. + *

+ * This queries this date using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @Override // override for Javadoc + public R query(TemporalQuery query) { + return ChronoLocalDate.super.query(query); + } + + /** + * Adjusts the specified temporal object to have the same date as this object. + *

+ * This returns a temporal object of the same observable type as the input + * with the date changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * passing {@link ChronoField#EPOCH_DAY} as the field. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisLocalDate.adjustInto(temporal);
+     *   temporal = temporal.with(thisLocalDate);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override // override for Javadoc + public Temporal adjustInto(Temporal temporal) { + return ChronoLocalDate.super.adjustInto(temporal); + } + + /** + * Calculates the period between this date and another date in + * terms of the specified unit. + *

+ * This calculates the period between two dates in terms of a single unit. + * The start and end points are {@code this} and the specified date. + * The result will be negative if the end is before the start. + * The {@code Temporal} passed to this method must be a {@code LocalDate}. + * For example, the period in days between two dates can be calculated + * using {@code startDate.periodUntil(endDate, DAYS)}. + *

+ * The calculation returns a whole number, representing the number of + * complete units between the two dates. + * For example, the period in months between 2012-06-15 and 2012-08-14 + * will only be one month as it is one day short of two months. + *

+ * This method operates in association with {@link TemporalUnit#between}. + * The result of this method is a {@code long} representing the amount of + * the specified unit. By contrast, the result of {@code between} is an + * object that can be used directly in addition/subtraction: + *

+     *   long period = start.periodUntil(end, MONTHS);   // this method
+     *   dateTime.plus(MONTHS.between(start, end));      // use in plus/minus
+     * 
+ *

+ * The calculation is implemented in this method for {@link ChronoUnit}. + * The units {@code DAYS}, {@code WEEKS}, {@code MONTHS}, {@code YEARS}, + * {@code DECADES}, {@code CENTURIES}, {@code MILLENNIA} and {@code ERAS} + * are supported. Other {@code ChronoUnit} values will throw an exception. + *

+ * If the unit is not a {@code ChronoUnit}, then the result of this method + * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)} + * passing {@code this} as the first argument and the input temporal as + * the second argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param endDate the end date, which must be a {@code LocalDate}, not null + * @param unit the unit to measure the period in, not null + * @return the amount of the period between this date and the end date + * @throws DateTimeException if the period cannot be calculated + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long periodUntil(Temporal endDate, TemporalUnit unit) { + if (endDate instanceof LocalDate == false) { + Objects.requireNonNull(endDate, "endDate"); + throw new DateTimeException("Unable to calculate period between objects of two different types"); + } + LocalDate end = (LocalDate) endDate; + if (unit instanceof ChronoUnit) { + switch ((ChronoUnit) unit) { + case DAYS: return daysUntil(end); + case WEEKS: return daysUntil(end) / 7; + case MONTHS: return monthsUntil(end); + case YEARS: return monthsUntil(end) / 12; + case DECADES: return monthsUntil(end) / 120; + case CENTURIES: return monthsUntil(end) / 1200; + case MILLENNIA: return monthsUntil(end) / 12000; + case ERAS: return end.getLong(ERA) - getLong(ERA); + } + throw new DateTimeException("Unsupported unit: " + unit.getName()); + } + return unit.between(this, endDate).getAmount(); + } + + long daysUntil(LocalDate end) { + return end.toEpochDay() - toEpochDay(); // no overflow + } + + private long monthsUntil(LocalDate end) { + long packed1 = getEpochMonth() * 32L + getDayOfMonth(); // no overflow + long packed2 = end.getEpochMonth() * 32L + end.getDayOfMonth(); // no overflow + return (packed2 - packed1) / 32; + } + + //----------------------------------------------------------------------- + /** + * Returns a local date-time formed from this date at the specified time. + *

+ * This combines this date with the specified time to form a {@code LocalDateTime}. + * All possible combinations of date and time are valid. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param time the time to combine with, not null + * @return the local date-time formed from this date and the specified time, not null + */ + @Override + public LocalDateTime atTime(LocalTime time) { + return LocalDateTime.of(this, time); + } + + /** + * Returns a local date-time formed from this date at the specified time. + *

+ * This combines this date with the specified time to form a {@code LocalDateTime}. + * The individual time fields must be within their valid range. + * All possible combinations of date and time are valid. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hour the hour-of-day to use, from 0 to 23 + * @param minute the minute-of-hour to use, from 0 to 59 + * @return the local date-time formed from this date and the specified time, not null + * @throws DateTimeException if the value of any field is out of range + */ + public LocalDateTime atTime(int hour, int minute) { + return atTime(LocalTime.of(hour, minute)); + } + + /** + * Returns a local date-time formed from this date at the specified time. + *

+ * This combines this date with the specified time to form a {@code LocalDateTime}. + * The individual time fields must be within their valid range. + * All possible combinations of date and time are valid. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hour the hour-of-day to use, from 0 to 23 + * @param minute the minute-of-hour to use, from 0 to 59 + * @param second the second-of-minute to represent, from 0 to 59 + * @return the local date-time formed from this date and the specified time, not null + * @throws DateTimeException if the value of any field is out of range + */ + public LocalDateTime atTime(int hour, int minute, int second) { + return atTime(LocalTime.of(hour, minute, second)); + } + + /** + * Returns a local date-time formed from this date at the specified time. + *

+ * This combines this date with the specified time to form a {@code LocalDateTime}. + * The individual time fields must be within their valid range. + * All possible combinations of date and time are valid. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hour the hour-of-day to use, from 0 to 23 + * @param minute the minute-of-hour to use, from 0 to 59 + * @param second the second-of-minute to represent, from 0 to 59 + * @param nanoOfSecond the nano-of-second to represent, from 0 to 999,999,999 + * @return the local date-time formed from this date and the specified time, not null + * @throws DateTimeException if the value of any field is out of range + */ + public LocalDateTime atTime(int hour, int minute, int second, int nanoOfSecond) { + return atTime(LocalTime.of(hour, minute, second, nanoOfSecond)); + } + + /** + * Returns an offset date formed from this date and the specified offset. + *

+ * This combines this date with the specified offset to form an {@code OffsetDate}. + * All possible combinations of date and offset are valid. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param offset the offset to combine with, not null + * @return the offset date formed from this date and the specified offset, not null + */ + public OffsetDate atOffset(ZoneOffset offset) { + return OffsetDate.of(this, offset); + } + + /** + * Returns a zoned date-time from this date at the earliest valid time according + * to the rules in the time-zone. + *

+ * Time-zone rules, such as daylight savings, mean that not every local date-time + * is valid for the specified zone, thus the local date-time may not be midnight. + *

+ * In most cases, there is only one valid offset for a local date-time. + * In the case of an overlap, there are two valid offsets, and the earlier one is used, + * corresponding to the first occurrence of midnight on the date. + * In the case of a gap, the zoned date-time will represent the instant just after the gap. + *

+ * If the zone ID is a {@link ZoneOffset}, then the result always has a time of midnight. + *

+ * To convert to a specific time in a given time-zone call {@link #atTime(LocalTime)} + * followed by {@link LocalDateTime#atZone(ZoneId)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param zone the zone ID to use, not null + * @return the zoned date-time formed from this date and the earliest valid time for the zone, not null + */ + public ZonedDateTime atStartOfDay(ZoneId zone) { + Objects.requireNonNull(zone, "zone"); + // need to handle case where there is a gap from 11:30 to 00:30 + // standard ZDT factory would result in 01:00 rather than 00:30 + LocalDateTime ldt = atTime(LocalTime.MIDNIGHT); + if (zone instanceof ZoneOffset == false) { + ZoneRules rules = zone.getRules(); + ZoneOffsetTransition trans = rules.getTransition(ldt); + if (trans != null && trans.isGap()) { + ldt = trans.getDateTimeAfter(); + } + } + return ZonedDateTime.of(ldt, zone); + } + + //----------------------------------------------------------------------- + @Override + 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() == false) { + total--; + } + } + return total - DAYS_0000_TO_1970; + } + + //----------------------------------------------------------------------- + /** + * Compares this date to another date. + *

+ * The comparison is primarily based on the date, from earliest to latest. + * It is "consistent with equals", as defined by {@link Comparable}. + *

+ * 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 + */ + @Override // override for Javadoc and performance + public int compareTo(ChronoLocalDate other) { + if (other instanceof LocalDate) { + return compareTo0((LocalDate) other); + } + return ChronoLocalDate.super.compareTo(other); + } + + int compareTo0(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 after the specified date. + *

+ * This checks to see if this date represents a point on the + * local time-line after the other date. + *

+     *   LocalDate a = LocalDate.of(2012, 6, 30);
+     *   LocalDate b = LocalDate.of(2012, 7, 1);
+     *   a.isAfter(b) == false
+     *   a.isAfter(a) == false
+     *   b.isAfter(a) == true
+     * 
+ *

+ * This method only considers the position of the two dates on the local time-line. + * It does not take into account the chronology, or calendar system. + * This is different from the comparison in {@link #compareTo(ChronoLocalDate)}, + * but is the same approach as {@link #DATE_COMPARATOR}. + * + * @param other the other date to compare to, not null + * @return true if this date is after the specified date + */ + @Override // override for Javadoc and performance + public boolean isAfter(ChronoLocalDate other) { + if (other instanceof LocalDate) { + return compareTo0((LocalDate) other) > 0; + } + return ChronoLocalDate.super.isAfter(other); + } + + /** + * Checks if this date is before the specified date. + *

+ * This checks to see if this date represents a point on the + * local time-line before the other date. + *

+     *   LocalDate a = LocalDate.of(2012, 6, 30);
+     *   LocalDate b = LocalDate.of(2012, 7, 1);
+     *   a.isBefore(b) == true
+     *   a.isBefore(a) == false
+     *   b.isBefore(a) == false
+     * 
+ *

+ * This method only considers the position of the two dates on the local time-line. + * It does not take into account the chronology, or calendar system. + * This is different from the comparison in {@link #compareTo(ChronoLocalDate)}, + * but is the same approach as {@link #DATE_COMPARATOR}. + * + * @param other the other date to compare to, not null + * @return true if this date is before the specified date + */ + @Override // override for Javadoc and performance + public boolean isBefore(ChronoLocalDate other) { + if (other instanceof LocalDate) { + return compareTo0((LocalDate) other) < 0; + } + return ChronoLocalDate.super.isBefore(other); + } + + /** + * Checks if this date is equal to the specified date. + *

+ * This checks to see if this date represents the same point on the + * local time-line as the other date. + *

+     *   LocalDate a = LocalDate.of(2012, 6, 30);
+     *   LocalDate b = LocalDate.of(2012, 7, 1);
+     *   a.isEqual(b) == false
+     *   a.isEqual(a) == true
+     *   b.isEqual(a) == false
+     * 
+ *

+ * This method only considers the position of the two dates on the local time-line. + * It does not take into account the chronology, or calendar system. + * This is different from the comparison in {@link #compareTo(ChronoLocalDate)} + * but is the same approach as {@link #DATE_COMPARATOR}. + * + * @param other the other date to compare to, not null + * @return true if this date is equal to the specified date + */ + @Override // override for Javadoc and performance + public boolean isEqual(ChronoLocalDate other) { + if (other instanceof LocalDate) { + return compareTo0((LocalDate) other) == 0; + } + return ChronoLocalDate.super.isEqual(other); + } + + //----------------------------------------------------------------------- + /** + * Checks if this date is equal to another date. + *

+ * Compares this {@code LocalDate} with another ensuring that the date is the same. + *

+ * 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 compareTo0((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)); + } + + //----------------------------------------------------------------------- + /** + * Outputs this date as a {@code String}, such as {@code 2007-12-03}. + *

+ * The output will be in the ISO-8601 format {@code yyyy-MM-dd}. + * + * @return a string representation of this date, not null + */ + @Override + public String toString() { + int yearValue = year; + int monthValue = month; + int dayValue = day; + int absYear = Math.abs(yearValue); + StringBuilder buf = new StringBuilder(10); + if (absYear < 1000) { + if (yearValue < 0) { + buf.append(yearValue - 10000).deleteCharAt(1); + } else { + buf.append(yearValue + 10000).deleteCharAt(0); + } + } else { + if (yearValue > 9999) { + buf.append('+'); + } + buf.append(yearValue); + } + return buf.append(monthValue < 10 ? "-0" : "-") + .append(monthValue) + .append(dayValue < 10 ? "-0" : "-") + .append(dayValue) + .toString(); + } + + /** + * Outputs this date as a {@code String} using the formatter. + *

+ * This date will be passed to the formatter + * {@link DateTimeFormatter#print(TemporalAccessor) print method}. + * + * @param formatter the formatter to use, not null + * @return the formatted date string, not null + * @throws DateTimeException if an error occurs during printing + */ + @Override // override for Javadoc + public String toString(DateTimeFormatter formatter) { + return ChronoLocalDate.super.toString(formatter); + } + + //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(3);  // identifies this as a LocalDate
+     *  out.writeInt(year);
+     *  out.writeByte(month);
+     *  out.writeByte(day);
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.LOCAL_DATE_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(DataOutput out) throws IOException { + out.writeInt(year); + out.writeByte(month); + out.writeByte(day); + } + + static LocalDate readExternal(DataInput in) throws IOException { + int year = in.readInt(); + int month = in.readByte(); + int dayOfMonth = in.readByte(); + return LocalDate.of(year, month, dayOfMonth); + } + +} diff --git a/src/share/classes/java/time/LocalDateTime.java b/src/share/classes/java/time/LocalDateTime.java new file mode 100644 index 0000000000000000000000000000000000000000..6dff20bc1aa5d96d130046fc57fbd410863cc909 --- /dev/null +++ b/src/share/classes/java/time/LocalDateTime.java @@ -0,0 +1,1860 @@ +/* + * 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 java.time; + +import static java.time.LocalTime.HOURS_PER_DAY; +import static java.time.LocalTime.MICROS_PER_DAY; +import static java.time.LocalTime.MILLIS_PER_DAY; +import static java.time.LocalTime.MINUTES_PER_DAY; +import static java.time.LocalTime.NANOS_PER_DAY; +import static java.time.LocalTime.NANOS_PER_HOUR; +import static java.time.LocalTime.NANOS_PER_MINUTE; +import static java.time.LocalTime.NANOS_PER_SECOND; +import static java.time.LocalTime.SECONDS_PER_DAY; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.ISOChrono; +import java.time.temporal.OffsetDateTime; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.TemporalUnit; +import java.time.temporal.ValueRange; +import java.time.zone.ZoneRules; +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}. + *

+ * {@code LocalDateTime} is an immutable date-time object that represents a date-time, + * often viewed as year-month-day-hour-minute-second. Other date and time fields, + * such as day-of-year, day-of-week and week-of-year, can also be accessed. + * Time is represented to nanosecond precision. + * For example, the value "2nd October 2007 at 13:45.30.123456789" can be + * stored in a {@code LocalDateTime}. + *

+ * This class does not store or represent a time-zone. + * Instead, it is a description of the date, as used for birthdays, combined with + * the local time as seen on a wall clock. + * It cannot represent an instant on the time-line without additional information + * such as an offset or time-zone. + *

+ * The ISO-8601 calendar system is the modern civil calendar system used today + * in most of the world. It is equivalent to the proleptic Gregorian calendar + * system, in which today's rules for leap years are applied for all time. + * For most applications written today, the ISO-8601 rules are entirely suitable. + * However, any application that makes use of historical dates, and requires them + * to be accurate will find the ISO-8601 approach unsuitable. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class LocalDateTime + implements Temporal, TemporalAdjuster, ChronoLocalDateTime, Serializable { + + /** + * 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); + + /** + * Serialization version. + */ + private static final long serialVersionUID = 6207766400415563566L; + + /** + * The date part. + */ + private final LocalDate date; + /** + * The time part. + */ + private final LocalTime time; + + //----------------------------------------------------------------------- + /** + * Obtains the current date-time from the system clock in the default time-zone. + *

+ * This will query the {@link Clock#systemDefaultZone() system clock} in the default + * time-zone to obtain the current date-time. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @return the current date-time using the system clock and default time-zone, not null + */ + public static LocalDateTime now() { + return now(Clock.systemDefaultZone()); + } + + /** + * Obtains the current date-time from the system clock in the specified time-zone. + *

+ * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date-time. + * Specifying the time-zone avoids dependence on the default time-zone. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @param zone the zone ID to use, not null + * @return the current date-time using the system clock, not null + */ + public static LocalDateTime now(ZoneId zone) { + return now(Clock.system(zone)); + } + + /** + * Obtains the current date-time from the specified clock. + *

+ * This will query the specified clock to obtain the current date-time. + * Using this method allows the use of an alternate clock for testing. + * The alternate clock may be introduced using {@link Clock dependency injection}. + * + * @param clock the clock to use, not null + * @return the current date-time, not null + */ + public static LocalDateTime now(Clock clock) { + Objects.requireNonNull(clock, "clock"); + final Instant now = clock.instant(); // called once + ZoneOffset offset = clock.getZone().getRules().getOffset(now); + return ofEpochSecond(now.getEpochSecond(), now.getNano(), offset); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code LocalDateTime} from year, month, + * day, hour and minute, setting the second and nanosecond to zero. + *

+ * 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, not null + * @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, Month 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 year, month, + * day, hour, minute and second, setting the nanosecond to zero. + *

+ * The day must be valid for the year and month, otherwise an exception will be thrown. + * The nanosecond field 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, not null + * @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 + * @param second the second-of-minute 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, Month month, int dayOfMonth, int hour, int minute, int second) { + LocalDate date = LocalDate.of(year, month, dayOfMonth); + LocalTime time = LocalTime.of(hour, minute, second); + return new LocalDateTime(date, time); + } + + /** + * Obtains an instance of {@code LocalDateTime} from year, month, + * day, hour, minute, second and nanosecond. + *

+ * 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, not null + * @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 + * @param second the second-of-minute to represent, from 0 to 59 + * @param nanoOfSecond the nano-of-second to represent, from 0 to 999,999,999 + * @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, Month month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond) { + LocalDate date = LocalDate.of(year, month, dayOfMonth); + LocalTime time = LocalTime.of(hour, minute, second, nanoOfSecond); + return new LocalDateTime(date, time); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code LocalDateTime} from year, month, + * day, hour and minute, setting the second and nanosecond to zero. + *

+ * 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 year, month, + * day, hour, minute and second, setting the nanosecond to zero. + *

+ * The day must be valid for the year and month, otherwise an exception will be thrown. + * The nanosecond field 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 + * @param second the second-of-minute 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, int second) { + LocalDate date = LocalDate.of(year, month, dayOfMonth); + LocalTime time = LocalTime.of(hour, minute, second); + return new LocalDateTime(date, time); + } + + /** + * Obtains an instance of {@code LocalDateTime} from year, month, + * day, hour, minute, second and nanosecond. + *

+ * 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 + * @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 + * @param nanoOfSecond the nano-of-second to represent, from 0 to 999,999,999 + * @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, int second, int nanoOfSecond) { + LocalDate date = LocalDate.of(year, month, dayOfMonth); + LocalTime time = LocalTime.of(hour, minute, second, nanoOfSecond); + 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} from an {@code Instant} and zone ID. + *

+ * This creates a local date-time based on the specified instant. + * First, the offset from UTC/Greenwich is obtained using the zone ID and instant, + * which is simple as there is only one valid offset for each instant. + * Then, the instant and offset are used to calculate the local date-time. + * + * @param instant the instant to create the date-time from, not null + * @param zone the time-zone, which may be an offset, not null + * @return the local date-time, not null + * @throws DateTimeException if the result exceeds the supported range + */ + public static LocalDateTime ofInstant(Instant instant, ZoneId zone) { + Objects.requireNonNull(instant, "instant"); + Objects.requireNonNull(zone, "zone"); + ZoneRules rules = zone.getRules(); + ZoneOffset offset = rules.getOffset(instant); + return ofEpochSecond(instant.getEpochSecond(), instant.getNano(), offset); + } + + /** + * Obtains an instance of {@code LocalDateTime} using seconds from the + * epoch of 1970-01-01T00:00:00Z. + *

+ * 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 = Math.floorDiv(localSecond, SECONDS_PER_DAY); + int secsOfDay = (int)Math.floorMod(localSecond, SECONDS_PER_DAY); + LocalDate date = LocalDate.ofEpochDay(localEpochDay); + LocalTime time = LocalTime.ofSecondOfDay(secsOfDay, nanoOfSecond); + return new LocalDateTime(date, time); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code LocalDateTime} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code LocalDateTime}. + *

+ * The conversion extracts and combines {@code LocalDate} and {@code LocalTime}. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used as a query via method reference, {@code LocalDateTime::from}. + * + * @param temporal the temporal object to convert, not null + * @return the local date-time, not null + * @throws DateTimeException if unable to convert to a {@code LocalDateTime} + */ + public static LocalDateTime from(TemporalAccessor temporal) { + if (temporal instanceof LocalDateTime) { + return (LocalDateTime) temporal; + } else if (temporal instanceof ZonedDateTime) { + return ((ZonedDateTime) temporal).getDateTime(); + } + try { + LocalDate date = LocalDate.from(temporal); + LocalTime time = LocalTime.from(temporal); + return new LocalDateTime(date, time); + } catch (DateTimeException ex) { + throw new DateTimeException("Unable to obtain LocalDateTime from TemporalAccessor: " + temporal.getClass(), ex); + } + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code LocalDateTime} from a text string such as {@code 2007-12-03T10:15:30}. + *

+ * The string must represent a valid date-time and is parsed using + * {@link java.time.format.DateTimeFormatters#isoLocalDateTime()}. + * + * @param text the text to parse such as "2007-12-03T10:15:30", not null + * @return the parsed local date-time, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static LocalDateTime parse(CharSequence text) { + return parse(text, DateTimeFormatters.isoLocalDateTime()); + } + + /** + * Obtains an instance of {@code LocalDateTime} from a text string using a specific formatter. + *

+ * The text is parsed using the formatter, returning a date-time. + * + * @param text the text to parse, not null + * @param formatter the formatter to use, not null + * @return the parsed local date-time, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.parse(text, LocalDateTime::from); + } + + //----------------------------------------------------------------------- + /** + * 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); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this date-time can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and + * {@link #get(TemporalField) get} methods will throw an exception. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The supported fields are: + *

    + *
  • {@code NANO_OF_SECOND} + *
  • {@code NANO_OF_DAY} + *
  • {@code MICRO_OF_SECOND} + *
  • {@code MICRO_OF_DAY} + *
  • {@code MILLI_OF_SECOND} + *
  • {@code MILLI_OF_DAY} + *
  • {@code SECOND_OF_MINUTE} + *
  • {@code SECOND_OF_DAY} + *
  • {@code MINUTE_OF_HOUR} + *
  • {@code MINUTE_OF_DAY} + *
  • {@code HOUR_OF_AMPM} + *
  • {@code CLOCK_HOUR_OF_AMPM} + *
  • {@code HOUR_OF_DAY} + *
  • {@code CLOCK_HOUR_OF_DAY} + *
  • {@code AMPM_OF_DAY} + *
  • {@code DAY_OF_WEEK} + *
  • {@code ALIGNED_DAY_OF_WEEK_IN_MONTH} + *
  • {@code ALIGNED_DAY_OF_WEEK_IN_YEAR} + *
  • {@code DAY_OF_MONTH} + *
  • {@code DAY_OF_YEAR} + *
  • {@code EPOCH_DAY} + *
  • {@code ALIGNED_WEEK_OF_MONTH} + *
  • {@code ALIGNED_WEEK_OF_YEAR} + *
  • {@code MONTH_OF_YEAR} + *
  • {@code EPOCH_MONTH} + *
  • {@code YEAR_OF_ERA} + *
  • {@code YEAR} + *
  • {@code ERA} + *
+ * All other {@code ChronoField} instances will return false. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the field is supported is determined by the field. + * + * @param field the field to check, null returns false + * @return true if the field is supported on this date-time, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + return f.isDateField() || f.isTimeField(); + } + return field != null && field.doIsSupported(this); + } + + /** + * Gets the range of valid values for the specified field. + *

+ * The range object expresses the minimum and maximum valid values for a field. + * This date-time is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return + * appropriate range instances. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + @Override + public ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + return (f.isTimeField() ? time.range(field) : date.range(field)); + } + return field.doRange(this); + } + + /** + * Gets the value of the specified field from this date-time as an {@code int}. + *

+ * This queries this date-time for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date-time, except {@code NANO_OF_DAY}, {@code MICRO_OF_DAY}, + * {@code EPOCH_DAY} and {@code EPOCH_MONTH} which are too large to fit in + * an {@code int} and throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public int get(TemporalField field) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + return (f.isTimeField() ? time.get(field) : date.get(field)); + } + return ChronoLocalDateTime.super.get(field); + } + + /** + * Gets the value of the specified field from this date-time as a {@code long}. + *

+ * This queries this date-time for the value for the specified field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date-time. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + return (f.isTimeField() ? time.getLong(field) : date.getLong(field)); + } + return field.doGet(this); + } + + //----------------------------------------------------------------------- + /** + * Gets the {@code LocalDate} part of this date-time. + *

+ * 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 + */ + @Override + public LocalDate getDate() { + return date; + } + + /** + * Gets the year field. + *

+ * This method returns the primitive {@code int} value for the year. + *

+ * 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 from 1 to 12. + *

+ * This method returns the month as an {@code int} from 1 to 12. + * Application code is frequently clearer if the enum {@link Month} + * is used by calling {@link #getMonth()}. + * + * @return the month-of-year, from 1 to 12 + * @see #getMonth() + */ + public int getMonthValue() { + return date.getMonthValue(); + } + + /** + * Gets the month-of-year field using the {@code Month} enum. + *

+ * This method returns the enum {@link Month} for the month. + * This avoids confusion as to what {@code int} values mean. + * If you need access to the primitive {@code int} value then the enum + * provides the {@link Month#getValue() int value}. + * + * @return the month-of-year, not null + * @see #getMonthValue() + */ + public Month getMonth() { + return date.getMonth(); + } + + /** + * Gets the day-of-month field. + *

+ * 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-year field. + *

+ * This method returns the primitive {@code int} value for the day-of-year. + * + * @return the day-of-year, from 1 to 365, or 366 in a leap year + */ + public int getDayOfYear() { + return date.getDayOfYear(); + } + + /** + * Gets the day-of-week field, which is an enum {@code DayOfWeek}. + *

+ * This method returns the enum {@link DayOfWeek} for the day-of-week. + * This avoids confusion as to what {@code int} values mean. + * If you need access to the primitive {@code int} value then the enum + * provides the {@link DayOfWeek#getValue() int value}. + *

+ * Additional information can be obtained from the {@code DayOfWeek}. + * This includes textual names of the values. + * + * @return the day-of-week, not null + */ + public DayOfWeek getDayOfWeek() { + return date.getDayOfWeek(); + } + + //----------------------------------------------------------------------- + /** + * Gets the {@code LocalTime} part of this date-time. + *

+ * 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 + */ + @Override + 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(); + } + + /** + * Gets the nano-of-second field. + * + * @return the nano-of-second, from 0 to 999,999,999 + */ + public int getNano() { + return time.getNano(); + } + + //----------------------------------------------------------------------- + /** + * Returns an adjusted copy of this date-time. + *

+ * This returns a new {@code LocalDateTime}, based on this one, with the date-time adjusted. + * The adjustment takes place using the specified adjuster strategy object. + * Read the documentation of the adjuster to understand what adjustment will be made. + *

+ * A simple adjuster might simply set the one of the fields, such as the year field. + * A more complex adjuster might set the date to the last day of the month. + * A selection of common adjustments is provided in {@link java.time.temporal.Adjusters}. + * These include finding the "last day of the month" and "next Wednesday". + * Key date-time classes also implement the {@code TemporalAdjuster} interface, + * such as {@link Month} and {@link java.time.temporal.MonthDay MonthDay}. + * The adjuster is responsible for handling special cases, such as the varying + * lengths of month and leap years. + *

+ * For example this code returns a date on the last day of July: + *

+     *  import static java.time.Month.*;
+     *  import static java.time.temporal.Adjusters.*;
+     *
+     *  result = localDateTime.with(JULY).with(lastDayOfMonth());
+     * 
+ *

+ * The classes {@link LocalDate} and {@link LocalTime} implement {@code TemporalAdjuster}, + * thus this method can be used to change the date, time or offset: + *

+     *  result = localDateTime.with(date);
+     *  result = localDateTime.with(time);
+     * 
+ *

+ * The result of this method is obtained by invoking the + * {@link TemporalAdjuster#adjustInto(Temporal)} method on the + * specified adjuster passing {@code this} as the argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adjuster the adjuster to use, not null + * @return a {@code LocalDateTime} based on {@code this} with the adjustment made, not null + * @throws DateTimeException if the adjustment cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public LocalDateTime with(TemporalAdjuster adjuster) { + // optimizations + if (adjuster instanceof LocalDate) { + return with((LocalDate) adjuster, time); + } else if (adjuster instanceof LocalTime) { + return with(date, (LocalTime) adjuster); + } else if (adjuster instanceof LocalDateTime) { + return (LocalDateTime) adjuster; + } + return (LocalDateTime) adjuster.adjustInto(this); + } + + /** + * Returns a copy of this date-time with the specified field set to a new value. + *

+ * This returns a new {@code LocalDateTime}, based on this one, with the value + * for the specified field changed. + * This can be used to change any supported field, such as the year, month or day-of-month. + * If it is not possible to set the value, because the field is not supported or for + * some other reason, an exception is thrown. + *

+ * In some cases, changing the specified field can cause the resulting date-time to become invalid, + * such as changing the month from 31st January to February would make the day-of-month invalid. + * In cases like this, the field is responsible for resolving the date. Typically it will choose + * the previous valid date, which would be the last valid day of February in this example. + *

+ * If the field is a {@link ChronoField} then the adjustment is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will behave as per + * the matching method on {@link LocalDate#with(TemporalField, long) LocalDate} + * or {@link LocalTime#with(TemporalField, long) LocalTime}. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doWith(Temporal, long)} + * passing {@code this} as the argument. In this case, the field determines + * whether and how to adjust the instant. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param field the field to set in the result, not null + * @param newValue the new value of the field in the result + * @return a {@code LocalDateTime} based on {@code this} with the specified field set, not null + * @throws DateTimeException if the field cannot be set + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public LocalDateTime with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + if (f.isTimeField()) { + return with(date, time.with(field, newValue)); + } else { + return with(date.with(field, newValue), time); + } + } + return field.doWith(this, newValue); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code LocalDateTime} with the year altered. + * The time does not affect the calculation and will be the same in the result. + * If the day-of-month is invalid for the year, it will be changed to the last valid day of the month. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param year the year to set in the result, from MIN_YEAR to MAX_YEAR + * @return a {@code LocalDateTime} based on this date-time with the requested year, not null + * @throws DateTimeException if the year value is invalid + */ + public LocalDateTime withYear(int year) { + return with(date.withYear(year), time); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the month-of-year altered. + * The time does not affect the calculation and will be the same in the result. + * If the day-of-month is invalid for the year, it will be changed to the last valid day of the month. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param month the month-of-year to set in the result, from 1 (January) to 12 (December) + * @return a {@code LocalDateTime} based on this date-time with the requested month, not null + * @throws DateTimeException if the month-of-year value is invalid + */ + public LocalDateTime withMonth(int month) { + return with(date.withMonth(month), time); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the day-of-month altered. + * If the resulting {@code LocalDateTime} is invalid, an exception is thrown. + * The time does not affect the calculation and will be the same in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param dayOfMonth the day-of-month to set in the result, from 1 to 28-31 + * @return a {@code LocalDateTime} based on this date-time with the requested day, not null + * @throws DateTimeException if the day-of-month value is invalid + * @throws DateTimeException if the day-of-month is invalid for the month-year + */ + public LocalDateTime withDayOfMonth(int dayOfMonth) { + return with(date.withDayOfMonth(dayOfMonth), time); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the day-of-year altered. + * If the resulting {@code LocalDateTime} is invalid, an exception is thrown. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param dayOfYear the day-of-year to set in the result, from 1 to 365-366 + * @return a {@code LocalDateTime} based on this date with the requested day, not null + * @throws DateTimeException if the day-of-year value is invalid + * @throws DateTimeException if the day-of-year is invalid for the year + */ + public LocalDateTime withDayOfYear(int dayOfYear) { + return with(date.withDayOfYear(dayOfYear), time); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code LocalDateTime} with the hour-of-day value altered. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hour the hour-of-day to set in the result, from 0 to 23 + * @return a {@code LocalDateTime} based on this date-time with the requested hour, not null + * @throws DateTimeException if the hour value is invalid + */ + public LocalDateTime withHour(int hour) { + LocalTime newTime = time.withHour(hour); + return with(date, newTime); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the minute-of-hour value altered. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param minute the minute-of-hour to set in the result, from 0 to 59 + * @return a {@code LocalDateTime} based on this date-time with the requested minute, not null + * @throws DateTimeException if the minute value is invalid + */ + public LocalDateTime withMinute(int minute) { + LocalTime newTime = time.withMinute(minute); + return with(date, newTime); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the second-of-minute value altered. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param second the second-of-minute to set in the result, from 0 to 59 + * @return a {@code LocalDateTime} based on this date-time with the requested second, not null + * @throws DateTimeException if the second value is invalid + */ + public LocalDateTime withSecond(int second) { + LocalTime newTime = time.withSecond(second); + return with(date, newTime); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the nano-of-second value altered. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanoOfSecond the nano-of-second to set in the result, from 0 to 999,999,999 + * @return a {@code LocalDateTime} based on this date-time with the requested nanosecond, not null + * @throws DateTimeException if the nano value is invalid + */ + public LocalDateTime withNano(int nanoOfSecond) { + LocalTime newTime = time.withNano(nanoOfSecond); + return with(date, newTime); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code LocalDateTime} with the time truncated. + *

+ * Truncation returns a copy of the original date-time with fields + * smaller than the specified unit set to zero. + * For example, truncating with the {@link ChronoUnit#MINUTES minutes} unit + * will set the second-of-minute and nano-of-second field to zero. + *

+ * Not all units are accepted. The {@link ChronoUnit#DAYS days} unit and time + * units with an exact duration can be used, other units throw an exception. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param unit the unit to truncate to, not null + * @return a {@code LocalDateTime} based on this date-time with the time truncated, not null + * @throws DateTimeException if unable to truncate + */ + public LocalDateTime truncatedTo(TemporalUnit unit) { + return with(date, time.truncatedTo(unit)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date-time with the specified period added. + *

+ * This method returns a new date-time based on this time with the specified period added. + * The adder is typically {@link Period} but may be any other type implementing + * the {@link TemporalAdder} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #plus(long, TemporalUnit)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adder the adder to use, not null + * @return a {@code LocalDateTime} based on this date-time with the addition made, not null + * @throws DateTimeException if the addition cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public LocalDateTime plus(TemporalAdder adder) { + return (LocalDateTime) adder.addTo(this); + } + + /** + * Returns a copy of this date-time with the specified period added. + *

+ * This method returns a new date-time based on this date-time with the specified period added. + * This can be used to add any period that is defined by a unit, for example to add years, months or days. + * The unit is responsible for the details of the calculation, including the resolution + * of any edge cases in the calculation. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToAdd the amount of the unit to add to the result, may be negative + * @param unit the unit of the period to add, not null + * @return a {@code LocalDateTime} based on this date-time with the specified period added, not null + * @throws DateTimeException if the unit cannot be added to this type + */ + @Override + public LocalDateTime plus(long amountToAdd, TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + ChronoUnit f = (ChronoUnit) unit; + switch (f) { + case NANOS: return plusNanos(amountToAdd); + case MICROS: return plusDays(amountToAdd / MICROS_PER_DAY).plusNanos((amountToAdd % MICROS_PER_DAY) * 1000); + case MILLIS: return plusDays(amountToAdd / MILLIS_PER_DAY).plusNanos((amountToAdd % MILLIS_PER_DAY) * 1000_000); + case SECONDS: return plusSeconds(amountToAdd); + case MINUTES: return plusMinutes(amountToAdd); + case HOURS: return plusHours(amountToAdd); + case HALF_DAYS: return plusDays(amountToAdd / 256).plusHours((amountToAdd % 256) * 12); // no overflow (256 is multiple of 2) + } + return with(date.plus(amountToAdd, unit), time); + } + return unit.doPlus(this, amountToAdd); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code LocalDateTime} with the specified period in years added. + *

+ * This method adds the specified amount to the years field in three steps: + *

    + *
  1. Add the input years to the year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2008-02-29 (leap year) plus one year would result in the + * invalid date 2009-02-29 (standard year). Instead of returning an invalid + * result, the last valid day of the month, 2009-02-28, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param years the years to add, may be negative + * @return a {@code LocalDateTime} based on this date-time with the years added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDateTime plusYears(long years) { + LocalDate newDate = date.plusYears(years); + return with(newDate, time); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the specified period in months added. + *

+ * This method adds the specified amount to the months field in three steps: + *

    + *
  1. Add the input months to the month-of-year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2007-03-31 plus one month would result in the invalid date + * 2007-04-31. Instead of returning an invalid result, the last valid day + * of the month, 2007-04-30, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param months the months to add, may be negative + * @return a {@code LocalDateTime} based on this date-time with the months added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDateTime plusMonths(long months) { + LocalDate newDate = date.plusMonths(months); + return with(newDate, time); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the specified period in weeks added. + *

+ * This method adds the specified amount in weeks 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. + *

+ * For example, 2008-12-31 plus one week would result in 2009-01-07. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param weeks the weeks to add, may be negative + * @return a {@code LocalDateTime} based on this date-time with the weeks added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDateTime plusWeeks(long weeks) { + LocalDate newDate = date.plusWeeks(weeks); + return with(newDate, time); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the specified period in days added. + *

+ * 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. + *

+ * For example, 2008-12-31 plus one day would result in 2009-01-01. + *

+ * 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 hours added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hours the hours to add, may be negative + * @return a {@code LocalDateTime} based on this date-time with the hours added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDateTime plusHours(long hours) { + return plusWithOverflow(date, hours, 0, 0, 0, 1); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the specified period in minutes added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param minutes the minutes to add, may be negative + * @return a {@code LocalDateTime} based on this date-time with the minutes added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDateTime plusMinutes(long minutes) { + return plusWithOverflow(date, 0, minutes, 0, 0, 1); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the specified period in seconds added. + *

+ * 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, 0, 1); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the specified period in nanoseconds added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanos the nanos to add, may be negative + * @return a {@code LocalDateTime} based on this date-time with the nanoseconds added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDateTime plusNanos(long nanos) { + return plusWithOverflow(date, 0, 0, 0, nanos, 1); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date-time with the specified period subtracted. + *

+ * This method returns a new date-time based on this time with the specified period subtracted. + * The subtractor is typically {@link Period} but may be any other type implementing + * the {@link TemporalSubtractor} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #minus(long, TemporalUnit)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param subtractor the subtractor to use, not null + * @return a {@code LocalDateTime} based on this date-time with the subtraction made, not null + * @throws DateTimeException if the subtraction cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public LocalDateTime minus(TemporalSubtractor subtractor) { + return (LocalDateTime) subtractor.subtractFrom(this); + } + + /** + * Returns a copy of this date-time with the specified period subtracted. + *

+ * This method returns a new date-time based on this date-time with the specified period subtracted. + * This can be used to subtract any period that is defined by a unit, for example to subtract years, months or days. + * The unit is responsible for the details of the calculation, including the resolution + * of any edge cases in the calculation. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToSubtract the amount of the unit to subtract from the result, may be negative + * @param unit the unit of the period to subtract, not null + * @return a {@code LocalDateTime} based on this date-time with the specified period subtracted, not null + * @throws DateTimeException if the unit cannot be added to this type + */ + @Override + public LocalDateTime minus(long amountToSubtract, TemporalUnit unit) { + return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code LocalDateTime} with the specified period in years subtracted. + *

+ * This method subtracts the specified amount from the years field in three steps: + *

    + *
  1. Subtract the input years from the year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2008-02-29 (leap year) minus one year would result in the + * invalid date 2009-02-29 (standard year). Instead of returning an invalid + * result, the last valid day of the month, 2009-02-28, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param years the years to subtract, may be negative + * @return a {@code LocalDateTime} based on this date-time with the years subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDateTime minusYears(long years) { + return (years == Long.MIN_VALUE ? plusYears(Long.MAX_VALUE).plusYears(1) : plusYears(-years)); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the specified period in months subtracted. + *

+ * This method subtracts the specified amount from the months field in three steps: + *

    + *
  1. Subtract the input months from the month-of-year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2007-03-31 minus one month would result in the invalid date + * 2007-04-31. Instead of returning an invalid result, the last valid day + * of the month, 2007-04-30, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param months the months to subtract, may be negative + * @return a {@code LocalDateTime} based on this date-time with the months subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDateTime minusMonths(long months) { + return (months == Long.MIN_VALUE ? plusMonths(Long.MAX_VALUE).plusMonths(1) : plusMonths(-months)); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the specified period in weeks subtracted. + *

+ * This method subtracts the specified amount in weeks 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. + *

+ * For example, 2009-01-07 minus one week would result in 2008-12-31. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param weeks the weeks to subtract, may be negative + * @return a {@code LocalDateTime} based on this date-time with the weeks subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDateTime minusWeeks(long weeks) { + return (weeks == Long.MIN_VALUE ? plusWeeks(Long.MAX_VALUE).plusWeeks(1) : plusWeeks(-weeks)); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the specified period in days subtracted. + *

+ * This method subtracts the specified amount from 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. + *

+ * For example, 2009-01-01 minus one day would result in 2008-12-31. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param days the days to subtract, may be negative + * @return a {@code LocalDateTime} based on this date-time with the days subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDateTime minusDays(long days) { + return (days == Long.MIN_VALUE ? plusDays(Long.MAX_VALUE).plusDays(1) : plusDays(-days)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code LocalDateTime} with the specified period in hours subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hours the hours to subtract, may be negative + * @return a {@code LocalDateTime} based on this date-time with the hours subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDateTime minusHours(long hours) { + return plusWithOverflow(date, hours, 0, 0, 0, -1); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the specified period in minutes subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param minutes the minutes to subtract, may be negative + * @return a {@code LocalDateTime} based on this date-time with the minutes subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDateTime minusMinutes(long minutes) { + return plusWithOverflow(date, 0, minutes, 0, 0, -1); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the specified period in seconds subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param seconds the seconds to subtract, may be negative + * @return a {@code LocalDateTime} based on this date-time with the seconds subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDateTime minusSeconds(long seconds) { + return plusWithOverflow(date, 0, 0, seconds, 0, -1); + } + + /** + * Returns a copy of this {@code LocalDateTime} with the specified period in nanoseconds subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanos the nanos to subtract, may be negative + * @return a {@code LocalDateTime} based on this date-time with the nanoseconds subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public LocalDateTime minusNanos(long nanos) { + return plusWithOverflow(date, 0, 0, 0, nanos, -1); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code LocalDateTime} with the specified period added. + *

+ * 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, long nanos, int sign) { + // 9223372036854775808 long, 2147483648 int + if ((hours | minutes | seconds | nanos) == 0) { + return with(newDate, time); + } + long totDays = nanos / NANOS_PER_DAY + // max/24*60*60*1B + 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 totNanos = nanos % NANOS_PER_DAY + // max 86400000000000 + (seconds % SECONDS_PER_DAY) * NANOS_PER_SECOND + // max 86400000000000 + (minutes % MINUTES_PER_DAY) * NANOS_PER_MINUTE + // max 86400000000000 + (hours % HOURS_PER_DAY) * NANOS_PER_HOUR; // max 86400000000000 + long curNoD = time.toNanoOfDay(); // max 86400000000000 + totNanos = totNanos * sign + curNoD; // total 432000000000000 + totDays += Math.floorDiv(totNanos, NANOS_PER_DAY); + long newNoD = Math.floorMod(totNanos, NANOS_PER_DAY); + LocalTime newTime = (newNoD == curNoD ? time : LocalTime.ofNanoOfDay(newNoD)); + return with(newDate.plusDays(totDays), newTime); + } + + //----------------------------------------------------------------------- + /** + * Queries this date-time using the specified query. + *

+ * This queries this date-time using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link java.time.temporal.TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @Override // override for Javadoc + public R query(TemporalQuery query) { + return ChronoLocalDateTime.super.query(query); + } + + /** + * Adjusts the specified temporal object to have the same date and time as this object. + *

+ * This returns a temporal object of the same observable type as the input + * with the date and time changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * twice, passing {@link ChronoField#EPOCH_DAY} and + * {@link ChronoField#NANO_OF_DAY} as the fields. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisLocalDateTime.adjustInto(temporal);
+     *   temporal = temporal.with(thisLocalDateTime);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override // override for Javadoc + public Temporal adjustInto(Temporal temporal) { + return ChronoLocalDateTime.super.adjustInto(temporal); + } + + /** + * Calculates the period between this date-time and another date-time in + * terms of the specified unit. + *

+ * This calculates the period between two date-times in terms of a single unit. + * The start and end points are {@code this} and the specified date-time. + * The result will be negative if the end is before the start. + * The {@code Temporal} passed to this method must be a {@code LocalDateTime}. + * For example, the period in days between two date-times can be calculated + * using {@code startDateTime.periodUntil(endDateTime, DAYS)}. + *

+ * The calculation returns a whole number, representing the number of + * complete units between the two date-times. + * For example, the period in months between 2012-06-15T00:00 and 2012-08-14T23:59 + * will only be one month as it is one minute short of two months. + *

+ * This method operates in association with {@link TemporalUnit#between}. + * The result of this method is a {@code long} representing the amount of + * the specified unit. By contrast, the result of {@code between} is an + * object that can be used directly in addition/subtraction: + *

+     *   long period = start.periodUntil(end, MONTHS);   // this method
+     *   dateTime.plus(MONTHS.between(start, end));      // use in plus/minus
+     * 
+ *

+ * The calculation is implemented in this method for {@link ChronoUnit}. + * The units {@code NANOS}, {@code MICROS}, {@code MILLIS}, {@code SECONDS}, + * {@code MINUTES}, {@code HOURS} and {@code HALF_DAYS}, {@code DAYS}, + * {@code WEEKS}, {@code MONTHS}, {@code YEARS}, {@code DECADES}, + * {@code CENTURIES}, {@code MILLENNIA} and {@code ERAS} are supported. + * Other {@code ChronoUnit} values will throw an exception. + *

+ * If the unit is not a {@code ChronoUnit}, then the result of this method + * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)} + * passing {@code this} as the first argument and the input temporal as + * the second argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param endDateTime the end date-time, which must be a {@code LocalDateTime}, not null + * @param unit the unit to measure the period in, not null + * @return the amount of the period between this date-time and the end date-time + * @throws DateTimeException if the period cannot be calculated + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long periodUntil(Temporal endDateTime, TemporalUnit unit) { + if (endDateTime instanceof LocalDateTime == false) { + Objects.requireNonNull(endDateTime, "endDateTime"); + throw new DateTimeException("Unable to calculate period between objects of two different types"); + } + LocalDateTime end = (LocalDateTime) endDateTime; + if (unit instanceof ChronoUnit) { + ChronoUnit f = (ChronoUnit) unit; + if (f.isTimeUnit()) { + long amount = date.daysUntil(end.date); + switch (f) { + case NANOS: amount = Math.multiplyExact(amount, NANOS_PER_DAY); break; + case MICROS: amount = Math.multiplyExact(amount, MICROS_PER_DAY); break; + case MILLIS: amount = Math.multiplyExact(amount, MILLIS_PER_DAY); break; + case SECONDS: amount = Math.multiplyExact(amount, SECONDS_PER_DAY); break; + case MINUTES: amount = Math.multiplyExact(amount, MINUTES_PER_DAY); break; + case HOURS: amount = Math.multiplyExact(amount, HOURS_PER_DAY); break; + case HALF_DAYS: amount = Math.multiplyExact(amount, 2); break; + } + return Math.addExact(amount, time.periodUntil(end.time, unit)); + } + LocalDate endDate = end.date; + if (end.time.isBefore(time)) { + endDate = endDate.minusDays(1); + } + return date.periodUntil(endDate, unit); + } + return unit.between(this, endDateTime).getAmount(); + } + + //----------------------------------------------------------------------- + /** + * Returns an offset date-time formed from this date-time and the specified offset. + *

+ * This combines this date-time with the specified offset to form an {@code OffsetDateTime}. + * All possible combinations of date-time and offset are valid. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param offset the offset to combine with, not null + * @return the offset date-time formed from this date-time and the specified offset, not null + */ + public OffsetDateTime atOffset(ZoneOffset offset) { + return OffsetDateTime.of(this, offset); + } + + /** + * Returns a zoned date-time formed from this date-time and the specified time-zone. + *

+ * This creates a zoned date-time matching the input date-time as closely as possible. + * Time-zone rules, such as daylight savings, mean that not every local date-time + * is valid for the specified zone, thus the local date-time may be adjusted. + *

+ * The local date-time is resolved to a single instant on the time-line. + * This is achieved by finding a valid offset from UTC/Greenwich for the local + * date-time as defined by the {@link ZoneRules rules} of the zone ID. + *

+ * In most cases, there is only one valid offset for a local date-time. + * In the case of an overlap, where clocks are set back, there are two valid offsets. + * This method uses the earlier offset typically corresponding to "summer". + *

+ * In the case of a gap, where clocks jump forward, there is no valid offset. + * Instead, the local date-time is adjusted to be later by the length of the gap. + * For a typical one hour daylight savings change, the local date-time will be + * moved one hour later into the offset typically corresponding to "summer". + *

+ * To obtain the later offset during an overlap, call + * {@link ZonedDateTime#withLaterOffsetAtOverlap()} on the result of this method. + * To throw an exception when there is a gap or overlap, use + * {@link ZonedDateTime#ofStrict(LocalDateTime, ZoneOffset, ZoneId)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param zone the time-zone to use, not null + * @return the zoned date-time formed from this date-time, not null + */ + @Override + public ZonedDateTime atZone(ZoneId zone) { + return ZonedDateTime.of(this, zone); + } + + //----------------------------------------------------------------------- + /** + * Compares this date-time to another date-time. + *

+ * The comparison is primarily based on the date-time, from earliest to latest. + * It is "consistent with equals", as defined by {@link Comparable}. + *

+ * 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 + */ + @Override // override for Javadoc and performance + public int compareTo(ChronoLocalDateTime other) { + if (other instanceof LocalDateTime) { + return compareTo0((LocalDateTime) other); + } + return ChronoLocalDateTime.super.compareTo(other); + } + + private int compareTo0(LocalDateTime other) { + int cmp = date.compareTo0(other.getDate()); + if (cmp == 0) { + cmp = time.compareTo(other.getTime()); + } + return cmp; + } + + /** + * Checks if this date-time is after the specified date-time. + *

+ * This checks to see if this date-time represents a point on the + * local time-line after the other date-time. + *

+     *   LocalDate a = LocalDateTime.of(2012, 6, 30, 12, 00);
+     *   LocalDate b = LocalDateTime.of(2012, 7, 1, 12, 00);
+     *   a.isAfter(b) == false
+     *   a.isAfter(a) == false
+     *   b.isAfter(a) == true
+     * 
+ *

+ * This method only considers the position of the two date-times on the local time-line. + * It does not take into account the chronology, or calendar system. + * This is different from the comparison in {@link #compareTo(ChronoLocalDateTime)}, + * but is the same approach as {@link #DATE_TIME_COMPARATOR}. + * + * @param other the other date-time to compare to, not null + * @return true if this date-time is after the specified date-time + */ + @Override // override for Javadoc and performance + public boolean isAfter(ChronoLocalDateTime other) { + if (other instanceof LocalDateTime) { + return compareTo0((LocalDateTime) other) > 0; + } + return ChronoLocalDateTime.super.isAfter(other); + } + + /** + * Checks if this date-time is before the specified date-time. + *

+ * This checks to see if this date-time represents a point on the + * local time-line before the other date-time. + *

+     *   LocalDate a = LocalDateTime.of(2012, 6, 30, 12, 00);
+     *   LocalDate b = LocalDateTime.of(2012, 7, 1, 12, 00);
+     *   a.isBefore(b) == true
+     *   a.isBefore(a) == false
+     *   b.isBefore(a) == false
+     * 
+ *

+ * This method only considers the position of the two date-times on the local time-line. + * It does not take into account the chronology, or calendar system. + * This is different from the comparison in {@link #compareTo(ChronoLocalDateTime)}, + * but is the same approach as {@link #DATE_TIME_COMPARATOR}. + * + * @param other the other date-time to compare to, not null + * @return true if this date-time is before the specified date-time + */ + @Override // override for Javadoc and performance + public boolean isBefore(ChronoLocalDateTime other) { + if (other instanceof LocalDateTime) { + return compareTo0((LocalDateTime) other) < 0; + } + return ChronoLocalDateTime.super.isBefore(other); + } + + /** + * Checks if this date-time is equal to the specified date-time. + *

+ * This checks to see if this date-time represents the same point on the + * local time-line as the other date-time. + *

+     *   LocalDate a = LocalDateTime.of(2012, 6, 30, 12, 00);
+     *   LocalDate b = LocalDateTime.of(2012, 7, 1, 12, 00);
+     *   a.isEqual(b) == false
+     *   a.isEqual(a) == true
+     *   b.isEqual(a) == false
+     * 
+ *

+ * This method only considers the position of the two date-times on the local time-line. + * It does not take into account the chronology, or calendar system. + * This is different from the comparison in {@link #compareTo(ChronoLocalDateTime)}, + * but is the same approach as {@link #DATE_TIME_COMPARATOR}. + * + * @param other the other date-time to compare to, not null + * @return true if this date-time is equal to the specified date-time + */ + @Override // override for Javadoc and performance + public boolean isEqual(ChronoLocalDateTime other) { + if (other instanceof LocalDateTime) { + return compareTo0((LocalDateTime) other) == 0; + } + return ChronoLocalDateTime.super.isEqual(other); + } + + //----------------------------------------------------------------------- + /** + * Checks if this date-time is equal to another date-time. + *

+ * 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(); + } + + //----------------------------------------------------------------------- + /** + * Outputs this date-time as a {@code String}, such as {@code 2007-12-03T10:15:30}. + *

+ * The output will be one of the following ISO-8601 formats: + *

    + *
  • {@code yyyy-MM-dd'T'HH:mm}
  • + *
  • {@code yyyy-MM-dd'T'HH:mm:ss}
  • + *
  • {@code yyyy-MM-dd'T'HH:mm:ss.SSS}
  • + *
  • {@code yyyy-MM-dd'T'HH:mm:ss.SSSSSS}
  • + *
  • {@code yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS}
  • + *

+ * The format used will be the shortest that outputs the full value of + * the time where the omitted parts are implied to be zero. + * + * @return a string representation of this date-time, not null + */ + @Override + public String toString() { + return date.toString() + 'T' + time.toString(); + } + + /** + * Outputs this date-time as a {@code String} using the formatter. + *

+ * This date-time will be passed to the formatter + * {@link DateTimeFormatter#print(TemporalAccessor) print method}. + * + * @param formatter the formatter to use, not null + * @return the formatted date-time string, not null + * @throws DateTimeException if an error occurs during printing + */ + @Override // override for Javadoc + public String toString(DateTimeFormatter formatter) { + return ChronoLocalDateTime.super.toString(formatter); + } + + //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(5);  // identifies this as a LocalDateTime
+     *  // the date excluding the one byte header
+     *  // the time excluding the one byte header
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.LOCAL_DATE_TIME_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(DataOutput out) throws IOException { + date.writeExternal(out); + time.writeExternal(out); + } + + static LocalDateTime readExternal(DataInput in) throws IOException { + LocalDate date = LocalDate.readExternal(in); + LocalTime time = LocalTime.readExternal(in); + return LocalDateTime.of(date, time); + } + +} diff --git a/src/share/classes/java/time/LocalTime.java b/src/share/classes/java/time/LocalTime.java new file mode 100644 index 0000000000000000000000000000000000000000..01e74e6c51bd09f7ee6b656f9c2e3d081d55e19c --- /dev/null +++ b/src/share/classes/java/time/LocalTime.java @@ -0,0 +1,1630 @@ +/* + * 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 java.time; + +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.MICRO_OF_DAY; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.NANO_OF_DAY; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.SECOND_OF_DAY; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoUnit.NANOS; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.format.DateTimeBuilder; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.ChronoZonedDateTime; +import java.time.temporal.OffsetTime; +import java.time.temporal.Queries; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.TemporalUnit; +import java.time.temporal.ValueRange; +import java.util.Objects; + +/** + * A time without time-zone in the ISO-8601 calendar system, + * such as {@code 10:15:30}. + *

+ * {@code LocalTime} is an immutable date-time object that represents a time, + * often viewed as hour-minute-second. + * Time is represented to nanosecond precision. + * For example, the value "13:45.30.123456789" can be stored in a {@code LocalTime}. + *

+ * It does not store or represent a date or time-zone. + * Instead, it is a description of the local time as seen on a wall clock. + * It cannot represent an instant on the time-line without additional information + * such as an offset or time-zone. + *

+ * The ISO-8601 calendar system is the modern civil calendar system used today + * in most of the world. This API assumes that all calendar systems use the same + * representation, this class, for time-of-day. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class LocalTime + implements Temporal, TemporalAdjuster, Comparable, Serializable { + + /** + * 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, 0); + } + MIDNIGHT = HOURS[0]; + NOON = HOURS[12]; + MIN = HOURS[0]; + MAX = new LocalTime(23, 59, 59, 999_999_999); + } + + /** + * 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; + /** + * Nanos per second. + */ + static final long NANOS_PER_SECOND = 1000_000_000L; + /** + * Nanos per minute. + */ + static final long NANOS_PER_MINUTE = NANOS_PER_SECOND * SECONDS_PER_MINUTE; + /** + * Nanos per hour. + */ + static final long NANOS_PER_HOUR = NANOS_PER_MINUTE * MINUTES_PER_HOUR; + /** + * Nanos per day. + */ + static final long NANOS_PER_DAY = NANOS_PER_HOUR * HOURS_PER_DAY; + + /** + * Serialization version. + */ + private static final long serialVersionUID = 6414437269572265201L; + + /** + * The hour. + */ + private final byte hour; + /** + * The minute. + */ + private final byte minute; + /** + * The second. + */ + private final byte second; + /** + * The nanosecond. + */ + private final int nano; + + //----------------------------------------------------------------------- + /** + * Obtains the current time from the system clock in the default time-zone. + *

+ * This will query the {@link Clock#systemDefaultZone() system clock} in the default + * time-zone to obtain the current time. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @return the current time using the system clock and default time-zone, not null + */ + public static LocalTime now() { + return now(Clock.systemDefaultZone()); + } + + /** + * Obtains the current time from the system clock in the specified time-zone. + *

+ * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current time. + * Specifying the time-zone avoids dependence on the default time-zone. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @param zone the zone ID to use, not null + * @return the current time using the system clock, not null + */ + public static LocalTime now(ZoneId zone) { + return now(Clock.system(zone)); + } + + /** + * Obtains the current time from the specified clock. + *

+ * This will query the specified clock to obtain the current time. + * Using this method allows the use of an alternate clock for testing. + * The alternate clock may be introduced using {@link Clock dependency injection}. + * + * @param clock the clock to use, not null + * @return the current time, not null + */ + public static LocalTime now(Clock clock) { + Objects.requireNonNull(clock, "clock"); + // inline OffsetTime factory to avoid creating object and InstantProvider checks + final Instant now = clock.instant(); // called once + ZoneOffset offset = clock.getZone().getRules().getOffset(now); + long secsOfDay = now.getEpochSecond() % SECONDS_PER_DAY; + secsOfDay = (secsOfDay + offset.getTotalSeconds()) % SECONDS_PER_DAY; + if (secsOfDay < 0) { + secsOfDay += SECONDS_PER_DAY; + } + return LocalTime.ofSecondOfDay(secsOfDay, now.getNano()); + } + + //------------------------get----------------------------------------------- + /** + * Obtains an instance of {@code LocalTime} from an hour and minute. + *

+ * The second and nanosecond fields will be set to zero by this factory method. + *

+ * 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, 0); + } + + /** + * Obtains an instance of {@code LocalTime} from an hour, minute and second. + *

+ * The nanosecond field will be set to zero by this factory method. + *

+ * 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, 0); + } + + /** + * Obtains an instance of {@code LocalTime} from an hour, minute, second and nanosecond. + *

+ * 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 + * @param nanoOfSecond the nano-of-second to represent, from 0 to 999,999,999 + * @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, int nanoOfSecond) { + HOUR_OF_DAY.checkValidValue(hour); + MINUTE_OF_HOUR.checkValidValue(minute); + SECOND_OF_MINUTE.checkValidValue(second); + NANO_OF_SECOND.checkValidValue(nanoOfSecond); + return create(hour, minute, second, nanoOfSecond); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code LocalTime} from a second-of-day value. + *

+ * 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(long secondOfDay) { + SECOND_OF_DAY.checkValidValue(secondOfDay); + int hours = (int) (secondOfDay / SECONDS_PER_HOUR); + secondOfDay -= hours * SECONDS_PER_HOUR; + int minutes = (int) (secondOfDay / SECONDS_PER_MINUTE); + secondOfDay -= minutes * SECONDS_PER_MINUTE; + return create(hours, minutes, (int) secondOfDay, 0); + } + + /** + * Obtains an instance of {@code LocalTime} from a second-of-day value, with + * associated nanos of second. + *

+ * 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} + * @param nanoOfSecond the nano-of-second, from 0 to 999,999,999 + * @return the local time, not null + * @throws DateTimeException if the either input value is invalid + */ + public static LocalTime ofSecondOfDay(long secondOfDay, int nanoOfSecond) { + SECOND_OF_DAY.checkValidValue(secondOfDay); + NANO_OF_SECOND.checkValidValue(nanoOfSecond); + int hours = (int) (secondOfDay / SECONDS_PER_HOUR); + secondOfDay -= hours * SECONDS_PER_HOUR; + int minutes = (int) (secondOfDay / SECONDS_PER_MINUTE); + secondOfDay -= minutes * SECONDS_PER_MINUTE; + return create(hours, minutes, (int) secondOfDay, nanoOfSecond); + } + + /** + * Obtains an instance of {@code LocalTime} from a nanos-of-day value. + *

+ * This factory may return a cached value, but applications must not rely on this. + * + * @param nanoOfDay the nano of day, from {@code 0} to {@code 24 * 60 * 60 * 1,000,000,000 - 1} + * @return the local time, not null + * @throws DateTimeException if the nanos of day value is invalid + */ + public static LocalTime ofNanoOfDay(long nanoOfDay) { + NANO_OF_DAY.checkValidValue(nanoOfDay); + int hours = (int) (nanoOfDay / NANOS_PER_HOUR); + nanoOfDay -= hours * NANOS_PER_HOUR; + int minutes = (int) (nanoOfDay / NANOS_PER_MINUTE); + nanoOfDay -= minutes * NANOS_PER_MINUTE; + int seconds = (int) (nanoOfDay / NANOS_PER_SECOND); + nanoOfDay -= seconds * NANOS_PER_SECOND; + return create(hours, minutes, seconds, (int) nanoOfDay); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code LocalTime} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code LocalTime}. + *

+ * The conversion extracts the {@link ChronoField#NANO_OF_DAY NANO_OF_DAY} field. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used in queries via method reference, {@code LocalTime::from}. + * + * @param temporal the temporal object to convert, not null + * @return the local time, not null + * @throws DateTimeException if unable to convert to a {@code LocalTime} + */ + public static LocalTime from(TemporalAccessor temporal) { + if (temporal instanceof LocalTime) { + return (LocalTime) temporal; + } else if (temporal instanceof ChronoLocalDateTime) { + return ((ChronoLocalDateTime) temporal).getTime(); + } else if (temporal instanceof ChronoZonedDateTime) { + return ((ChronoZonedDateTime) temporal).getTime(); + } + // handle builder as a special case + if (temporal instanceof DateTimeBuilder) { + DateTimeBuilder builder = (DateTimeBuilder) temporal; + LocalTime time = builder.extract(LocalTime.class); + if (time != null) { + return time; + } + } + try { + return ofNanoOfDay(temporal.getLong(NANO_OF_DAY)); + } catch (DateTimeException ex) { + throw new DateTimeException("Unable to obtain LocalTime from TemporalAccessor: " + temporal.getClass(), ex); + } + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code LocalTime} from a text string such as {@code 10:15}. + *

+ * The string must represent a valid time and is parsed using + * {@link java.time.format.DateTimeFormatters#isoLocalTime()}. + * + * @param text the text to parse such as "10:15:30", not null + * @return the parsed local time, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static LocalTime parse(CharSequence text) { + return parse(text, DateTimeFormatters.isoLocalTime()); + } + + /** + * Obtains an instance of {@code LocalTime} from a text string using a specific formatter. + *

+ * The text is parsed using the formatter, returning a time. + * + * @param text the text to parse, not null + * @param formatter the formatter to use, not null + * @return the parsed local time, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static LocalTime parse(CharSequence text, DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.parse(text, LocalTime::from); + } + + //----------------------------------------------------------------------- + /** + * Creates a local time from the hour, minute, second and nanosecond fields. + *

+ * 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 + * @param nanoOfSecond the nano-of-second to represent, validated from 0 to 999,999,999 + * @return the local time, not null + */ + private static LocalTime create(int hour, int minute, int second, int nanoOfSecond) { + if ((minute | second | nanoOfSecond) == 0) { + return HOURS[hour]; + } + return new LocalTime(hour, minute, second, nanoOfSecond); + } + + /** + * 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 + * @param nanoOfSecond the nano-of-second to represent, validated from 0 to 999,999,999 + */ + private LocalTime(int hour, int minute, int second, int nanoOfSecond) { + this.hour = (byte) hour; + this.minute = (byte) minute; + this.second = (byte) second; + this.nano = nanoOfSecond; + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this time can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and + * {@link #get(TemporalField) get} methods will throw an exception. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The supported fields are: + *

    + *
  • {@code NANO_OF_SECOND} + *
  • {@code NANO_OF_DAY} + *
  • {@code MICRO_OF_SECOND} + *
  • {@code MICRO_OF_DAY} + *
  • {@code MILLI_OF_SECOND} + *
  • {@code MILLI_OF_DAY} + *
  • {@code SECOND_OF_MINUTE} + *
  • {@code SECOND_OF_DAY} + *
  • {@code MINUTE_OF_HOUR} + *
  • {@code MINUTE_OF_DAY} + *
  • {@code HOUR_OF_AMPM} + *
  • {@code CLOCK_HOUR_OF_AMPM} + *
  • {@code HOUR_OF_DAY} + *
  • {@code CLOCK_HOUR_OF_DAY} + *
  • {@code AMPM_OF_DAY} + *
+ * All other {@code ChronoField} instances will return false. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the field is supported is determined by the field. + * + * @param field the field to check, null returns false + * @return true if the field is supported on this time, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return ((ChronoField) field).isTimeField(); + } + return field != null && field.doIsSupported(this); + } + + /** + * Gets the range of valid values for the specified field. + *

+ * The range object expresses the minimum and maximum valid values for a field. + * This time is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return + * appropriate range instances. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + @Override // override for Javadoc + public ValueRange range(TemporalField field) { + return Temporal.super.range(field); + } + + /** + * Gets the value of the specified field from this time as an {@code int}. + *

+ * This queries this time for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this time, except {@code NANO_OF_DAY} and {@code MICRO_OF_DAY} + * which are too large to fit in an {@code int} and throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override // override for Javadoc and performance + public int get(TemporalField field) { + if (field instanceof ChronoField) { + return get0(field); + } + return Temporal.super.get(field); + } + + /** + * Gets the value of the specified field from this time as a {@code long}. + *

+ * This queries this time for the value for the specified field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this time. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + if (field == NANO_OF_DAY) { + return toNanoOfDay(); + } + if (field == MICRO_OF_DAY) { + return toNanoOfDay() / 1000; + } + return get0(field); + } + return field.doGet(this); + } + + private int get0(TemporalField field) { + switch ((ChronoField) field) { + case NANO_OF_SECOND: return nano; + case NANO_OF_DAY: throw new DateTimeException("Field too large for an int: " + field); + case MICRO_OF_SECOND: return nano / 1000; + case MICRO_OF_DAY: throw new DateTimeException("Field too large for an int: " + field); + case MILLI_OF_SECOND: return nano / 1000_000; + case MILLI_OF_DAY: return (int) (toNanoOfDay() / 1000_000); + case SECOND_OF_MINUTE: return second; + case SECOND_OF_DAY: return toSecondOfDay(); + case MINUTE_OF_HOUR: return minute; + case MINUTE_OF_DAY: return hour * 60 + minute; + case HOUR_OF_AMPM: return hour % 12; + case CLOCK_HOUR_OF_AMPM: int ham = hour % 12; return (ham % 12 == 0 ? 12 : ham); + case HOUR_OF_DAY: return hour; + case CLOCK_HOUR_OF_DAY: return (hour == 0 ? 24 : hour); + case AMPM_OF_DAY: return hour / 12; + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + + //----------------------------------------------------------------------- + /** + * 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; + } + + /** + * Gets the nano-of-second field. + * + * @return the nano-of-second, from 0 to 999,999,999 + */ + public int getNano() { + return nano; + } + + //----------------------------------------------------------------------- + /** + * Returns an adjusted copy of this time. + *

+ * This returns a new {@code LocalTime}, based on this one, with the time adjusted. + * The adjustment takes place using the specified adjuster strategy object. + * Read the documentation of the adjuster to understand what adjustment will be made. + *

+ * A simple adjuster might simply set the one of the fields, such as the hour field. + * A more complex adjuster might set the time to the last hour of the day. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalAdjuster#adjustInto(Temporal)} method on the + * specified adjuster passing {@code this} as the argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adjuster the adjuster to use, not null + * @return a {@code LocalTime} based on {@code this} with the adjustment made, not null + * @throws DateTimeException if the adjustment cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public LocalTime with(TemporalAdjuster adjuster) { + // optimizations + if (adjuster instanceof LocalTime) { + return (LocalTime) adjuster; + } + return (LocalTime) adjuster.adjustInto(this); + } + + /** + * Returns a copy of this time with the specified field set to a new value. + *

+ * This returns a new {@code LocalTime}, based on this one, with the value + * for the specified field changed. + * This can be used to change any supported field, such as the hour, minute or second. + * If it is not possible to set the value, because the field is not supported or for + * some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the adjustment is implemented here. + * The supported fields behave as follows: + *

    + *
  • {@code NANO_OF_SECOND} - + * Returns a {@code LocalTime} with the specified nano-of-second. + * The hour, minute and second will be unchanged. + *
  • {@code NANO_OF_DAY} - + * Returns a {@code LocalTime} with the specified nano-of-day. + * This completely replaces the time and is equivalent to {@link #ofNanoOfDay(long)}. + *
  • {@code MICRO_OF_SECOND} - + * Returns a {@code LocalTime} with the nano-of-second replaced by the specified + * micro-of-second multiplied by 1,000. + * The hour, minute and second will be unchanged. + *
  • {@code MICRO_OF_DAY} - + * Returns a {@code LocalTime} with the specified micro-of-day. + * This completely replaces the time and is equivalent to using {@link #ofNanoOfDay(long)} + * with the micro-of-day multiplied by 1,000. + *
  • {@code MILLI_OF_SECOND} - + * Returns a {@code LocalTime} with the nano-of-second replaced by the specified + * milli-of-second multiplied by 1,000,000. + * The hour, minute and second will be unchanged. + *
  • {@code MILLI_OF_DAY} - + * Returns a {@code LocalTime} with the specified milli-of-day. + * This completely replaces the time and is equivalent to using {@link #ofNanoOfDay(long)} + * with the milli-of-day multiplied by 1,000,000. + *
  • {@code SECOND_OF_MINUTE} - + * Returns a {@code LocalTime} with the specified second-of-minute. + * The hour, minute and nano-of-second will be unchanged. + *
  • {@code SECOND_OF_DAY} - + * Returns a {@code LocalTime} with the specified second-of-day. + * The nano-of-second will be unchanged. + *
  • {@code MINUTE_OF_HOUR} - + * Returns a {@code LocalTime} with the specified minute-of-hour. + * The hour, second-of-minute and nano-of-second will be unchanged. + *
  • {@code MINUTE_OF_DAY} - + * Returns a {@code LocalTime} with the specified minute-of-day. + * The second-of-minute and nano-of-second will be unchanged. + *
  • {@code HOUR_OF_AMPM} - + * Returns a {@code LocalTime} with the specified hour-of-am-pm. + * The AM/PM, minute-of-hour, second-of-minute and nano-of-second will be unchanged. + *
  • {@code CLOCK_HOUR_OF_AMPM} - + * Returns a {@code LocalTime} with the specified clock-hour-of-am-pm. + * The AM/PM, minute-of-hour, second-of-minute and nano-of-second will be unchanged. + *
  • {@code HOUR_OF_DAY} - + * Returns a {@code LocalTime} with the specified hour-of-day. + * The minute-of-hour, second-of-minute and nano-of-second will be unchanged. + *
  • {@code CLOCK_HOUR_OF_DAY} - + * Returns a {@code LocalTime} with the specified clock-hour-of-day. + * The minute-of-hour, second-of-minute and nano-of-second will be unchanged. + *
  • {@code AMPM_OF_DAY} - + * Returns a {@code LocalTime} with the specified AM/PM. + * The hour-of-am-pm, minute-of-hour, second-of-minute and nano-of-second will be unchanged. + *
+ *

+ * In all cases, if the new value is outside the valid range of values for the field + * then a {@code DateTimeException} will be thrown. + *

+ * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doWith(Temporal, long)} + * passing {@code this} as the argument. In this case, the field determines + * whether and how to adjust the instant. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param field the field to set in the result, not null + * @param newValue the new value of the field in the result + * @return a {@code LocalTime} based on {@code this} with the specified field set, not null + * @throws DateTimeException if the field cannot be set + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public LocalTime with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + f.checkValidValue(newValue); + switch (f) { + case NANO_OF_SECOND: return withNano((int) newValue); + case NANO_OF_DAY: return LocalTime.ofNanoOfDay(newValue); + case MICRO_OF_SECOND: return withNano((int) newValue * 1000); + case MICRO_OF_DAY: return plusNanos((newValue - toNanoOfDay() / 1000) * 1000); + case MILLI_OF_SECOND: return withNano((int) newValue * 1000_000); + case MILLI_OF_DAY: return plusNanos((newValue - toNanoOfDay() / 1000_000) * 1000_000); + case SECOND_OF_MINUTE: return withSecond((int) newValue); + case SECOND_OF_DAY: return plusSeconds(newValue - toSecondOfDay()); + case MINUTE_OF_HOUR: return withMinute((int) newValue); + case MINUTE_OF_DAY: return plusMinutes(newValue - (hour * 60 + minute)); + case HOUR_OF_AMPM: return plusHours(newValue - (hour % 12)); + case CLOCK_HOUR_OF_AMPM: return plusHours((newValue == 12 ? 0 : newValue) - (hour % 12)); + case HOUR_OF_DAY: return withHour((int) newValue); + case CLOCK_HOUR_OF_DAY: return withHour((int) (newValue == 24 ? 0 : newValue)); + case AMPM_OF_DAY: return plusHours((newValue - (hour / 12)) * 12); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doWith(this, newValue); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code LocalTime} with the hour-of-day value altered. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hour the hour-of-day to set in the result, from 0 to 23 + * @return a {@code LocalTime} based on this time with the requested hour, not null + * @throws DateTimeException if the hour value is invalid + */ + public LocalTime withHour(int hour) { + if (this.hour == hour) { + return this; + } + HOUR_OF_DAY.checkValidValue(hour); + return create(hour, minute, second, nano); + } + + /** + * Returns a copy of this {@code LocalTime} with the minute-of-hour value altered. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param minute the minute-of-hour to set in the result, from 0 to 59 + * @return a {@code LocalTime} based on this time with the requested minute, not null + * @throws DateTimeException if the minute value is invalid + */ + public LocalTime withMinute(int minute) { + if (this.minute == minute) { + return this; + } + MINUTE_OF_HOUR.checkValidValue(minute); + return create(hour, minute, second, nano); + } + + /** + * Returns a copy of this {@code LocalTime} with the second-of-minute value altered. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param second the second-of-minute to set in the result, from 0 to 59 + * @return a {@code LocalTime} based on this time with the requested second, not null + * @throws DateTimeException if the second value is invalid + */ + public LocalTime withSecond(int second) { + if (this.second == second) { + return this; + } + SECOND_OF_MINUTE.checkValidValue(second); + return create(hour, minute, second, nano); + } + + /** + * Returns a copy of this {@code LocalTime} with the nano-of-second value altered. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanoOfSecond the nano-of-second to set in the result, from 0 to 999,999,999 + * @return a {@code LocalTime} based on this time with the requested nanosecond, not null + * @throws DateTimeException if the nanos value is invalid + */ + public LocalTime withNano(int nanoOfSecond) { + if (this.nano == nanoOfSecond) { + return this; + } + NANO_OF_SECOND.checkValidValue(nanoOfSecond); + return create(hour, minute, second, nanoOfSecond); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code LocalTime} with the time truncated. + *

+ * Truncating the time returns a copy of the original time with fields + * smaller than the specified unit set to zero. + * For example, truncating with the {@link ChronoUnit#MINUTES minutes} unit + * will set the second-of-minute and nano-of-second field to zero. + *

+ * Not all units are accepted. The {@link ChronoUnit#DAYS days} unit and time + * units with an exact duration can be used, other units throw an exception. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param unit the unit to truncate to, not null + * @return a {@code LocalTime} based on this time with the time truncated, not null + * @throws DateTimeException if unable to truncate + */ + public LocalTime truncatedTo(TemporalUnit unit) { + if (unit == ChronoUnit.NANOS) { + return this; + } else if (unit == ChronoUnit.DAYS) { + return MIDNIGHT; + } else if (unit.isDurationEstimated()) { + throw new DateTimeException("Unit must not have an estimated duration"); + } + long nod = toNanoOfDay(); + long dur = unit.getDuration().toNanos(); + if (dur >= NANOS_PER_DAY) { + throw new DateTimeException("Unit must not be a date unit"); + } + nod = (nod / dur) * dur; + return ofNanoOfDay(nod); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date with the specified period added. + *

+ * This method returns a new time based on this time with the specified period added. + * The adder is typically {@link Period} but may be any other type implementing + * the {@link TemporalAdder} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #plus(long, TemporalUnit)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adder the adder to use, not null + * @return a {@code LocalTime} based on this time with the addition made, not null + * @throws DateTimeException if the addition cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public LocalTime plus(TemporalAdder adder) { + return (LocalTime) adder.addTo(this); + } + + /** + * Returns a copy of this time with the specified period added. + *

+ * This method returns a new time based on this time with the specified period added. + * This can be used to add any period that is defined by a unit, for example to add hours, minutes or seconds. + * The unit is responsible for the details of the calculation, including the resolution + * of any edge cases in the calculation. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToAdd the amount of the unit to add to the result, may be negative + * @param unit the unit of the period to add, not null + * @return a {@code LocalTime} based on this time with the specified period added, not null + * @throws DateTimeException if the unit cannot be added to this type + */ + @Override + public LocalTime plus(long amountToAdd, TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + ChronoUnit f = (ChronoUnit) unit; + switch (f) { + case NANOS: return plusNanos(amountToAdd); + case MICROS: return plusNanos((amountToAdd % MICROS_PER_DAY) * 1000); + case MILLIS: return plusNanos((amountToAdd % MILLIS_PER_DAY) * 1000_000); + case SECONDS: return plusSeconds(amountToAdd); + case MINUTES: return plusMinutes(amountToAdd); + case HOURS: return plusHours(amountToAdd); + case HALF_DAYS: return plusHours((amountToAdd % 2) * 12); + case DAYS: return this; + } + throw new DateTimeException("Unsupported unit: " + unit.getName()); + } + return unit.doPlus(this, amountToAdd); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code LocalTime} with the specified period in hours added. + *

+ * This adds the specified number of hours to this time, returning a new time. + * The calculation wraps around midnight. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hoursToAdd the hours to add, may be negative + * @return a {@code LocalTime} based on this time with the hours added, not null + */ + public LocalTime plusHours(long hoursToAdd) { + if (hoursToAdd == 0) { + return this; + } + int newHour = ((int) (hoursToAdd % HOURS_PER_DAY) + hour + HOURS_PER_DAY) % HOURS_PER_DAY; + return create(newHour, minute, second, nano); + } + + /** + * Returns a copy of this {@code LocalTime} with the specified period in minutes added. + *

+ * This adds the specified number of minutes to this time, returning a new time. + * The calculation wraps around midnight. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param minutesToAdd the minutes to add, may be negative + * @return a {@code LocalTime} based on this time with the minutes added, not null + */ + public LocalTime plusMinutes(long minutesToAdd) { + if (minutesToAdd == 0) { + return this; + } + int mofd = hour * MINUTES_PER_HOUR + minute; + int newMofd = ((int) (minutesToAdd % MINUTES_PER_DAY) + mofd + MINUTES_PER_DAY) % MINUTES_PER_DAY; + if (mofd == newMofd) { + return this; + } + int newHour = newMofd / MINUTES_PER_HOUR; + int newMinute = newMofd % MINUTES_PER_HOUR; + return create(newHour, newMinute, second, nano); + } + + /** + * Returns a copy of this {@code LocalTime} with the specified period in seconds added. + *

+ * This adds the specified number of seconds to this time, returning a new time. + * The calculation wraps around midnight. + *

+ * 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, nano); + } + + /** + * Returns a copy of this {@code LocalTime} with the specified period in nanoseconds added. + *

+ * This adds the specified number of nanoseconds to this time, returning a new time. + * The calculation wraps around midnight. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanosToAdd the nanos to add, may be negative + * @return a {@code LocalTime} based on this time with the nanoseconds added, not null + */ + public LocalTime plusNanos(long nanosToAdd) { + if (nanosToAdd == 0) { + return this; + } + long nofd = toNanoOfDay(); + long newNofd = ((nanosToAdd % NANOS_PER_DAY) + nofd + NANOS_PER_DAY) % NANOS_PER_DAY; + if (nofd == newNofd) { + return this; + } + int newHour = (int) (newNofd / NANOS_PER_HOUR); + int newMinute = (int) ((newNofd / NANOS_PER_MINUTE) % MINUTES_PER_HOUR); + int newSecond = (int) ((newNofd / NANOS_PER_SECOND) % SECONDS_PER_MINUTE); + int newNano = (int) (newNofd % NANOS_PER_SECOND); + return create(newHour, newMinute, newSecond, newNano); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this time with the specified period subtracted. + *

+ * This method returns a new time based on this time with the specified period subtracted. + * The subtractor is typically {@link Period} but may be any other type implementing + * the {@link TemporalSubtractor} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #minus(long, TemporalUnit)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param subtractor the subtractor to use, not null + * @return a {@code LocalTime} based on this time with the subtraction made, not null + * @throws DateTimeException if the subtraction cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public LocalTime minus(TemporalSubtractor subtractor) { + return (LocalTime) subtractor.subtractFrom(this); + } + + /** + * Returns a copy of this time with the specified period subtracted. + *

+ * This method returns a new time based on this time with the specified period subtracted. + * This can be used to subtract any period that is defined by a unit, for example to subtract hours, minutes or seconds. + * The unit is responsible for the details of the calculation, including the resolution + * of any edge cases in the calculation. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToSubtract the amount of the unit to subtract from the result, may be negative + * @param unit the unit of the period to subtract, not null + * @return a {@code LocalTime} based on this time with the specified period subtracted, not null + * @throws DateTimeException if the unit cannot be added to this type + */ + @Override + public LocalTime minus(long amountToSubtract, TemporalUnit unit) { + return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code LocalTime} with the specified period in hours subtracted. + *

+ * This subtracts the specified number of hours from this time, returning a new time. + * The calculation wraps around midnight. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hoursToSubtract the hours to subtract, may be negative + * @return a {@code LocalTime} based on this time with the hours subtracted, not null + */ + public LocalTime minusHours(long hoursToSubtract) { + return plusHours(-(hoursToSubtract % HOURS_PER_DAY)); + } + + /** + * Returns a copy of this {@code LocalTime} with the specified period in minutes subtracted. + *

+ * This subtracts the specified number of minutes from this time, returning a new time. + * The calculation wraps around midnight. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param minutesToSubtract the minutes to subtract, may be negative + * @return a {@code LocalTime} based on this time with the minutes subtracted, not null + */ + public LocalTime minusMinutes(long minutesToSubtract) { + return plusMinutes(-(minutesToSubtract % MINUTES_PER_DAY)); + } + + /** + * Returns a copy of this {@code LocalTime} with the specified period in seconds subtracted. + *

+ * This subtracts the specified number of seconds from this time, returning a new time. + * The calculation wraps around midnight. + *

+ * 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)); + } + + /** + * Returns a copy of this {@code LocalTime} with the specified period in nanoseconds subtracted. + *

+ * This subtracts the specified number of nanoseconds from this time, returning a new time. + * The calculation wraps around midnight. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanosToSubtract the nanos to subtract, may be negative + * @return a {@code LocalTime} based on this time with the nanoseconds subtracted, not null + */ + public LocalTime minusNanos(long nanosToSubtract) { + return plusNanos(-(nanosToSubtract % NANOS_PER_DAY)); + } + + //----------------------------------------------------------------------- + /** + * Queries this time using the specified query. + *

+ * This queries this time using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.precision()) { + return (R) NANOS; + } + // inline TemporalAccessor.super.query(query) as an optimization + if (query == Queries.chrono() || query == Queries.zoneId() || query == Queries.zone() || query == Queries.offset()) { + return null; + } + return query.queryFrom(this); + } + + /** + * Adjusts the specified temporal object to have the same time as this object. + *

+ * This returns a temporal object of the same observable type as the input + * with the time changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * passing {@link ChronoField#NANO_OF_DAY} as the field. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisLocalTime.adjustInto(temporal);
+     *   temporal = temporal.with(thisLocalTime);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal adjustInto(Temporal temporal) { + return temporal.with(NANO_OF_DAY, toNanoOfDay()); + } + + /** + * Calculates the period between this time and another time in + * terms of the specified unit. + *

+ * This calculates the period between two times in terms of a single unit. + * The start and end points are {@code this} and the specified time. + * The result will be negative if the end is before the start. + * The {@code Temporal} passed to this method must be a {@code LocalTime}. + * For example, the period in hours between two times can be calculated + * using {@code startTime.periodUntil(endTime, HOURS)}. + *

+ * The calculation returns a whole number, representing the number of + * complete units between the two times. + * For example, the period in hours between 11:30 and 13:29 will only + * be one hour as it is one minute short of two hours. + *

+ * This method operates in association with {@link TemporalUnit#between}. + * The result of this method is a {@code long} representing the amount of + * the specified unit. By contrast, the result of {@code between} is an + * object that can be used directly in addition/subtraction: + *

+     *   long period = start.periodUntil(end, HOURS);   // this method
+     *   dateTime.plus(HOURS.between(start, end));      // use in plus/minus
+     * 
+ *

+ * The calculation is implemented in this method for {@link ChronoUnit}. + * The units {@code NANOS}, {@code MICROS}, {@code MILLIS}, {@code SECONDS}, + * {@code MINUTES}, {@code HOURS} and {@code HALF_DAYS} are supported. + * Other {@code ChronoUnit} values will throw an exception. + *

+ * If the unit is not a {@code ChronoUnit}, then the result of this method + * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)} + * passing {@code this} as the first argument and the input temporal as + * the second argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param endTime the end time, which must be a {@code LocalTime}, not null + * @param unit the unit to measure the period in, not null + * @return the amount of the period between this time and the end time + * @throws DateTimeException if the period cannot be calculated + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long periodUntil(Temporal endTime, TemporalUnit unit) { + if (endTime instanceof LocalTime == false) { + Objects.requireNonNull(endTime, "endTime"); + throw new DateTimeException("Unable to calculate period between objects of two different types"); + } + LocalTime end = (LocalTime) endTime; + if (unit instanceof ChronoUnit) { + long nanosUntil = end.toNanoOfDay() - toNanoOfDay(); // no overflow + switch ((ChronoUnit) unit) { + case NANOS: return nanosUntil; + case MICROS: return nanosUntil / 1000; + case MILLIS: return nanosUntil / 1000_000; + case SECONDS: return nanosUntil / NANOS_PER_SECOND; + case MINUTES: return nanosUntil / NANOS_PER_MINUTE; + case HOURS: return nanosUntil / NANOS_PER_HOUR; + case HALF_DAYS: return nanosUntil / (12 * NANOS_PER_HOUR); + } + throw new DateTimeException("Unsupported unit: " + unit.getName()); + } + return unit.between(this, endTime).getAmount(); + } + + //----------------------------------------------------------------------- + /** + * Returns a local date-time formed from this time at the specified date. + *

+ * This combines this time with the specified date to form a {@code LocalDateTime}. + * All possible combinations of date and time are valid. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param date the date to combine with, not null + * @return the local date-time formed from this time and the specified date, not null + */ + public LocalDateTime atDate(LocalDate date) { + return LocalDateTime.of(date, this); + } + + /** + * Returns an offset time formed from this time and the specified offset. + *

+ * This combines this time with the specified offset to form an {@code OffsetTime}. + * All possible combinations of time and offset are valid. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param offset the offset to combine with, not null + * @return the offset time formed from this time and the specified offset, not null + */ + public OffsetTime atOffset(ZoneOffset offset) { + return OffsetTime.of(this, offset); + } + + //----------------------------------------------------------------------- + /** + * 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; + } + + /** + * Extracts the time as nanos of day, + * from {@code 0} to {@code 24 * 60 * 60 * 1,000,000,000 - 1}. + * + * @return the nano of day equivalent to this time + */ + public long toNanoOfDay() { + long total = hour * NANOS_PER_HOUR; + total += minute * NANOS_PER_MINUTE; + total += second * NANOS_PER_SECOND; + total += nano; + return total; + } + + //----------------------------------------------------------------------- + /** + * Compares this {@code LocalTime} to another time. + *

+ * 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 + */ + @Override + 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); + if (cmp == 0) { + cmp = Integer.compare(nano, other.nano); + } + } + } + return cmp; + } + + /** + * Checks if this {@code LocalTime} is after the specified time. + *

+ * The comparison is based on the time-line position of the time within a day. + * + * @param other the other time to compare to, not null + * @return true if this is after the specified time + * @throws NullPointerException if {@code other} is null + */ + public boolean isAfter(LocalTime other) { + return compareTo(other) > 0; + } + + /** + * Checks if this {@code LocalTime} is before the specified time. + *

+ * The comparison is based on the time-line position of the time within a day. + * + * @param other the other time to compare to, not null + * @return true if this point is before the specified time + * @throws NullPointerException if {@code other} is null + */ + public boolean isBefore(LocalTime other) { + return compareTo(other) < 0; + } + + //----------------------------------------------------------------------- + /** + * Checks if this time is equal to another time. + *

+ * The comparison is based on the time-line position of the time within a day. + *

+ * 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 && nano == other.nano; + } + return false; + } + + /** + * A hash code for this time. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + long nod = toNanoOfDay(); + return (int) (nod ^ (nod >>> 32)); + } + + //----------------------------------------------------------------------- + /** + * Outputs this time as a {@code String}, such as {@code 10:15}. + *

+ * The output will be one of the following ISO-8601 formats: + *

    + *
  • {@code HH:mm}
  • + *
  • {@code HH:mm:ss}
  • + *
  • {@code HH:mm:ss.SSS}
  • + *
  • {@code HH:mm:ss.SSSSSS}
  • + *
  • {@code HH:mm:ss.SSSSSSSSS}
  • + *

+ * The format used will be the shortest that outputs the full value of + * the time where the omitted parts are implied to be zero. + * + * @return a string representation of this time, not null + */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder(18); + int hourValue = hour; + int minuteValue = minute; + int secondValue = second; + int nanoValue = nano; + buf.append(hourValue < 10 ? "0" : "").append(hourValue) + .append(minuteValue < 10 ? ":0" : ":").append(minuteValue); + if (secondValue > 0 || nanoValue > 0) { + buf.append(secondValue < 10 ? ":0" : ":").append(secondValue); + if (nanoValue > 0) { + buf.append('.'); + if (nanoValue % 1000_000 == 0) { + buf.append(Integer.toString((nanoValue / 1000_000) + 1000).substring(1)); + } else if (nanoValue % 1000 == 0) { + buf.append(Integer.toString((nanoValue / 1000) + 1000_000).substring(1)); + } else { + buf.append(Integer.toString((nanoValue) + 1000_000_000).substring(1)); + } + } + } + return buf.toString(); + } + + /** + * Outputs this time as a {@code String} using the formatter. + *

+ * This time will be passed to the formatter + * {@link DateTimeFormatter#print(TemporalAccessor) print method}. + * + * @param formatter the formatter to use, not null + * @return the formatted time string, not null + * @throws DateTimeException if an error occurs during printing + */ + public String toString(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.print(this); + } + + //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(4);  // identifies this as a LocalTime
+     *  if (nano == 0) {
+     *    if (second == 0) {
+     *      if (minute == 0) {
+     *        out.writeByte(~hour);
+     *      } else {
+     *        out.writeByte(hour);
+     *        out.writeByte(~minute);
+     *      }
+     *    } else {
+     *      out.writeByte(hour);
+     *      out.writeByte(minute);
+     *      out.writeByte(~second);
+     *    }
+     *  } else {
+     *    out.writeByte(hour);
+     *    out.writeByte(minute);
+     *    out.writeByte(second);
+     *    out.writeInt(nano);
+     *  }
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.LOCAL_TIME_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(DataOutput out) throws IOException { + if (nano == 0) { + if (second == 0) { + if (minute == 0) { + out.writeByte(~hour); + } else { + out.writeByte(hour); + out.writeByte(~minute); + } + } else { + out.writeByte(hour); + out.writeByte(minute); + out.writeByte(~second); + } + } else { + out.writeByte(hour); + out.writeByte(minute); + out.writeByte(second); + out.writeInt(nano); + } + } + + static LocalTime readExternal(DataInput in) throws IOException { + int hour = in.readByte(); + int minute = 0; + int second = 0; + int nano = 0; + if (hour < 0) { + hour = ~hour; + } else { + minute = in.readByte(); + if (minute < 0) { + minute = ~minute; + } else { + second = in.readByte(); + if (second < 0) { + second = ~second; + } else { + nano = in.readInt(); + } + } + } + return LocalTime.of(hour, minute, second, nano); + } + +} diff --git a/src/share/classes/java/time/Month.java b/src/share/classes/java/time/Month.java new file mode 100644 index 0000000000000000000000000000000000000000..db8d0533296bce27c029f207e489026aa948152e --- /dev/null +++ b/src/share/classes/java/time/Month.java @@ -0,0 +1,608 @@ +/* + * 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 java.time; + +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoUnit.MONTHS; + +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.TextStyle; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoField; +import java.time.temporal.ISOChrono; +import java.time.temporal.Queries; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.ValueRange; +import java.util.Locale; + +/** + * A month-of-year, such as 'July'. + *

+ * {@code Month} is an enum representing the 12 months of the year - + * January, February, March, April, May, June, July, August, September, October, + * November and December. + *

+ * In addition to the textual enum name, each month-of-year has an {@code int} value. + * The {@code int} value follows normal usage and the ISO-8601 standard, + * from 1 (January) to 12 (December). It is recommended that applications use the enum + * rather than the {@code int} value to ensure code clarity. + *

+ * Do not use {@code ordinal()} to obtain the numeric representation of {@code Month}. + * Use {@code getValue()} instead. + *

+ * This enum represents a common concept that is found in many calendar systems. + * As such, this enum may be used by any calendar system that has the month-of-year + * concept defined exactly equivalent to the ISO-8601 calendar system. + * + *

Specification for implementors

+ * This is an immutable and thread-safe enum. + * + * @since 1.8 + */ +public enum Month implements TemporalAccessor, TemporalAdjuster { + + /** + * The singleton instance for the month of January with 31 days. + * This has the numeric value of {@code 1}. + */ + JANUARY, + /** + * The singleton instance for the month of February with 28 days, or 29 in a leap year. + * This has the numeric value of {@code 2}. + */ + FEBRUARY, + /** + * The singleton instance for the month of March with 31 days. + * This has the numeric value of {@code 3}. + */ + MARCH, + /** + * The singleton instance for the month of April with 30 days. + * This has the numeric value of {@code 4}. + */ + APRIL, + /** + * The singleton instance for the month of May with 31 days. + * This has the numeric value of {@code 5}. + */ + MAY, + /** + * The singleton instance for the month of June with 30 days. + * This has the numeric value of {@code 6}. + */ + JUNE, + /** + * The singleton instance for the month of July with 31 days. + * This has the numeric value of {@code 7}. + */ + JULY, + /** + * The singleton instance for the month of August with 31 days. + * This has the numeric value of {@code 8}. + */ + AUGUST, + /** + * The singleton instance for the month of September with 30 days. + * This has the numeric value of {@code 9}. + */ + SEPTEMBER, + /** + * The singleton instance for the month of October with 31 days. + * This has the numeric value of {@code 10}. + */ + OCTOBER, + /** + * The singleton instance for the month of November with 30 days. + * This has the numeric value of {@code 11}. + */ + NOVEMBER, + /** + * The singleton instance for the month of December with 31 days. + * This has the numeric value of {@code 12}. + */ + DECEMBER; + /** + * Private cache of all the constants. + */ + private static final Month[] ENUMS = Month.values(); + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Month} from an {@code int} value. + *

+ * {@code Month} is an enum representing the 12 months of the year. + * This factory allows the enum to be obtained from the {@code int} value. + * The {@code int} value follows the ISO-8601 standard, from 1 (January) to 12 (December). + * + * @param month the month-of-year to represent, from 1 (January) to 12 (December) + * @return the month-of-year, not null + * @throws DateTimeException if the month-of-year is invalid + */ + public static Month of(int month) { + if (month < 1 || month > 12) { + throw new DateTimeException("Invalid value for MonthOfYear: " + month); + } + return ENUMS[month - 1]; + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Month} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code Month}. + *

+ * The conversion extracts the {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} field. + * The extraction is only permitted if the temporal object has an ISO + * chronology, or can be converted to a {@code LocalDate}. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used in queries via method reference, {@code Month::from}. + * + * @param temporal the temporal object to convert, not null + * @return the month-of-year, not null + * @throws DateTimeException if unable to convert to a {@code Month} + */ + public static Month from(TemporalAccessor temporal) { + if (temporal instanceof Month) { + return (Month) temporal; + } + try { + if (ISOChrono.INSTANCE.equals(Chrono.from(temporal)) == false) { + temporal = LocalDate.from(temporal); + } + return of(temporal.get(MONTH_OF_YEAR)); + } catch (DateTimeException ex) { + throw new DateTimeException("Unable to obtain Month from TemporalAccessor: " + temporal.getClass(), ex); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the month-of-year {@code int} value. + *

+ * The values are numbered following the ISO-8601 standard, + * from 1 (January) to 12 (December). + * + * @return the month-of-year, from 1 (January) to 12 (December) + */ + public int getValue() { + return ordinal() + 1; + } + + //----------------------------------------------------------------------- + /** + * Gets the textual representation, such as 'Jan' or 'December'. + *

+ * This returns the textual name used to identify the month-of-year. + * The parameters control the length of the returned text and the locale. + *

+ * If no textual mapping is found then the {@link #getValue() numeric value} is returned. + * + * @param style the length of the text required, not null + * @param locale the locale to use, not null + * @return the text value of the month-of-year, not null + */ + public String getText(TextStyle style, Locale locale) { + return new DateTimeFormatterBuilder().appendText(MONTH_OF_YEAR, style).toFormatter(locale).print(this); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this month-of-year can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and + * {@link #get(TemporalField) get} methods will throw an exception. + *

+ * If the field is {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} then + * this method returns true. + * All other {@code ChronoField} instances will return false. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the field is supported is determined by the field. + * + * @param field the field to check, null returns false + * @return true if the field is supported on this month-of-year, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == MONTH_OF_YEAR; + } + return field != null && field.doIsSupported(this); + } + + /** + * Gets the range of valid values for the specified field. + *

+ * The range object expresses the minimum and maximum valid values for a field. + * This month is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} then the + * range of the month-of-year, from 1 to 12, will be returned. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + @Override + public ValueRange range(TemporalField field) { + if (field == MONTH_OF_YEAR) { + return field.range(); + } + return TemporalAccessor.super.range(field); + } + + /** + * Gets the value of the specified field from this month-of-year as an {@code int}. + *

+ * This queries this month for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} then the + * value of the month-of-year, from 1 to 12, will be returned. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field, within the valid range of values + * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if the range of valid values for the field exceeds an {@code int} + * @throws DateTimeException if the value is outside the range of valid values for the field + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public int get(TemporalField field) { + if (field == MONTH_OF_YEAR) { + return getValue(); + } + return TemporalAccessor.super.get(field); + } + + /** + * Gets the value of the specified field from this month-of-year as a {@code long}. + *

+ * This queries this month for the value for the specified field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} then the + * value of the month-of-year, from 1 to 12, will be returned. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long getLong(TemporalField field) { + if (field == MONTH_OF_YEAR) { + return getValue(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doGet(this); + } + + //----------------------------------------------------------------------- + /** + * Returns the month-of-year that is the specified number of quarters after this one. + *

+ * The calculation rolls around the end of the year from December to January. + * The specified period may be negative. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param months the months to add, positive or negative + * @return the resulting month, not null + */ + public Month plus(long months) { + int amount = (int) (months % 12); + return ENUMS[(ordinal() + (amount + 12)) % 12]; + } + + /** + * Returns the month-of-year that is the specified number of months before this one. + *

+ * The calculation rolls around the start of the year from January to December. + * The specified period may be negative. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param months the months to subtract, positive or negative + * @return the resulting month, not null + */ + public Month minus(long months) { + return plus(-(months % 12)); + } + + //----------------------------------------------------------------------- + /** + * Gets the length of this month in days. + *

+ * This takes a flag to determine whether to return the length for a leap year or not. + *

+ * February has 28 days in a standard year and 29 days in a leap year. + * April, June, September and November have 30 days. + * All other months have 31 days. + * + * @param leapYear true if the length is required for a leap year + * @return the length of this month in days, from 28 to 31 + */ + public int length(boolean leapYear) { + switch (this) { + case FEBRUARY: + return (leapYear ? 29 : 28); + case APRIL: + case JUNE: + case SEPTEMBER: + case NOVEMBER: + return 30; + default: + return 31; + } + } + + /** + * Gets the minimum length of this month in days. + *

+ * February has a minimum length of 28 days. + * April, June, September and November have 30 days. + * All other months have 31 days. + * + * @return the minimum length of this month in days, from 28 to 31 + */ + public int minLength() { + switch (this) { + case FEBRUARY: + return 28; + case APRIL: + case JUNE: + case SEPTEMBER: + case NOVEMBER: + return 30; + default: + return 31; + } + } + + /** + * Gets the maximum length of this month in days. + *

+ * February has a maximum length of 29 days. + * April, June, September and November have 30 days. + * All other months have 31 days. + * + * @return the maximum length of this month in days, from 29 to 31 + */ + public int maxLength() { + switch (this) { + case FEBRUARY: + return 29; + case APRIL: + case JUNE: + case SEPTEMBER: + case NOVEMBER: + return 30; + default: + return 31; + } + } + + //----------------------------------------------------------------------- + /** + * Gets the day-of-year corresponding to the first day of this month. + *

+ * This returns the day-of-year that this month begins on, using the leap + * year flag to determine the length of February. + * + * @param leapYear true if the length is required for a leap year + * @return the day of year corresponding to the first day of this month, from 1 to 336 + */ + public int firstDayOfYear(boolean leapYear) { + int leap = leapYear ? 1 : 0; + switch (this) { + case JANUARY: + return 1; + case FEBRUARY: + return 32; + case MARCH: + return 60 + leap; + case APRIL: + return 91 + leap; + case MAY: + return 121 + leap; + case JUNE: + return 152 + leap; + case JULY: + return 182 + leap; + case AUGUST: + return 213 + leap; + case SEPTEMBER: + return 244 + leap; + case OCTOBER: + return 274 + leap; + case NOVEMBER: + return 305 + leap; + case DECEMBER: + default: + return 335 + leap; + } + } + + /** + * Gets the month corresponding to the first month of this quarter. + *

+ * The year can be divided into four quarters. + * This method returns the first month of the quarter for the base month. + * January, February and March return January. + * April, May and June return April. + * July, August and September return July. + * October, November and December return October. + * + * @return the first month of the quarter corresponding to this month, not null + */ + public Month firstMonthOfQuarter() { + return ENUMS[(ordinal() / 3) * 3]; + } + + //----------------------------------------------------------------------- + /** + * Queries this month-of-year using the specified query. + *

+ * This queries this month-of-year using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.chrono()) { + return (R) ISOChrono.INSTANCE; + } else if (query == Queries.precision()) { + return (R) MONTHS; + } + return TemporalAccessor.super.query(query); + } + + /** + * Adjusts the specified temporal object to have this month-of-year. + *

+ * This returns a temporal object of the same observable type as the input + * with the month-of-year changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * passing {@link ChronoField#MONTH_OF_YEAR} as the field. + * If the specified temporal object does not use the ISO calendar system then + * a {@code DateTimeException} is thrown. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisMonth.adjustInto(temporal);
+     *   temporal = temporal.with(thisMonth);
+     * 
+ *

+ * For example, given a date in May, the following are output: + *

+     *   dateInMay.with(JANUARY);    // four months earlier
+     *   dateInMay.with(APRIL);      // one months earlier
+     *   dateInMay.with(MAY);        // same date
+     *   dateInMay.with(JUNE);       // one month later
+     *   dateInMay.with(DECEMBER);   // seven months later
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal adjustInto(Temporal temporal) { + if (Chrono.from(temporal).equals(ISOChrono.INSTANCE) == false) { + throw new DateTimeException("Adjustment only supported on ISO date-time"); + } + return temporal.with(MONTH_OF_YEAR, getValue()); + } + +} diff --git a/src/share/classes/java/time/Period.java b/src/share/classes/java/time/Period.java new file mode 100644 index 0000000000000000000000000000000000000000..a04aab6a52ccc099217ee384f30fd1368a0422be --- /dev/null +++ b/src/share/classes/java/time/Period.java @@ -0,0 +1,1187 @@ +/* + * 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 java.time; + +import static java.time.LocalTime.NANOS_PER_DAY; +import static java.time.LocalTime.NANOS_PER_HOUR; +import static java.time.LocalTime.NANOS_PER_MINUTE; +import static java.time.LocalTime.NANOS_PER_SECOND; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.EPOCH_MONTH; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.NANO_OF_DAY; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.YEARS; + +import java.io.Serializable; +import java.time.format.DateTimeParseException; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.TemporalUnit; +import java.time.temporal.ValueRange; +import java.util.Objects; + +/** + * A period of time, measured using the most common units, such as '3 Months, 4 Days and 7 Hours'. + *

+ * A {@code Period} represents an amount of time measured in terms of the most commonly used units: + *

    + *
  • {@link ChronoUnit#YEARS YEARS}
  • + *
  • {@link ChronoUnit#MONTHS MONTHS}
  • + *
  • {@link ChronoUnit#DAYS DAYS}
  • + *
  • time units with an {@linkplain TemporalUnit#isDurationEstimated() exact duration}
  • + *

+ * The period may be used with any calendar system with the exception is methods with an "ISO" suffix. + * The meaning of a "year" or a "month" is only applied when the object is added to a date. + *

+ * The period is modeled as a directed amount of time, meaning that individual parts of the + * period may be negative. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * The maximum number of hours that can be stored is about 2.5 million, limited by storing + * a single {@code long} nanoseconds for all time units internally. + * + * @since 1.8 + */ +public final class Period + implements TemporalAdder, TemporalSubtractor, Serializable { + // maximum hours is 2,562,047 + + /** + * A constant for a period of zero. + */ + public static final Period ZERO = new Period(0, 0, 0, 0); + /** + * Serialization version. + */ + private static final long serialVersionUID = -8290556941213247973L; + + /** + * The number of years. + */ + private final int years; + /** + * The number of months. + */ + private final int months; + /** + * The number of days. + */ + private final int days; + /** + * The number of nanoseconds. + */ + private final long nanos; + + //----------------------------------------------------------------------- + /** + * Obtains a {@code Period} from date-based and time-based fields. + *

+ * This creates an instance based on years, months, days, hours, minutes and seconds. + * Within a period, the time fields are always normalized. + * + * @param years the amount of years, may be negative + * @param months the amount of months, may be negative + * @param days the amount of days, may be negative + * @param hours the amount of hours, may be negative + * @param minutes the amount of minutes, may be negative + * @param seconds the amount of seconds, may be negative + * @return the period, not null + */ + public static Period of(int years, int months, int days, int hours, int minutes, int seconds) { + return of(years, months, days, hours, minutes, seconds, 0); + } + + /** + * Obtains a {@code Period} from date-based and time-based fields. + *

+ * This creates an instance based on years, months, days, hours, minutes, seconds and nanoseconds. + * Within a period, the time fields are always normalized. + * + * @param years the amount of years, may be negative + * @param months the amount of months, may be negative + * @param days the amount of days, may be negative + * @param hours the amount of hours, may be negative + * @param minutes the amount of minutes, may be negative + * @param seconds the amount of seconds, may be negative + * @param nanos the amount of nanos, may be negative + * @return the period, not null + */ + public static Period of(int years, int months, int days, int hours, int minutes, int seconds, long nanos) { + if ((years | months | days | hours | minutes | seconds | nanos) == 0) { + return ZERO; + } + long totSecs = Math.addExact(hours * 3600L, minutes * 60L) + seconds; + long totNanos = Math.addExact(Math.multiplyExact(totSecs, 1_000_000_000L), nanos); + return create(years, months, days, totNanos); + } + + //----------------------------------------------------------------------- + /** + * Obtains a {@code Period} from date-based fields. + *

+ * This creates an instance based on years, months and days. + * + * @param years the amount of years, may be negative + * @param months the amount of months, may be negative + * @param days the amount of days, may be negative + * @return the period, not null + */ + public static Period ofDate(int years, int months, int days) { + return of(years, months, days, 0, 0, 0, 0); + } + + //----------------------------------------------------------------------- + /** + * Obtains a {@code Period} from time-based fields. + *

+ * This creates an instance based on hours, minutes and seconds. + * Within a period, the time fields are always normalized. + * + * @param hours the amount of hours, may be negative + * @param minutes the amount of minutes, may be negative + * @param seconds the amount of seconds, may be negative + * @return the period, not null + */ + public static Period ofTime(int hours, int minutes, int seconds) { + return of(0, 0, 0, hours, minutes, seconds, 0); + } + + /** + * Obtains a {@code Period} from time-based fields. + *

+ * This creates an instance based on hours, minutes, seconds and nanoseconds. + * Within a period, the time fields are always normalized. + * + * @param hours the amount of hours, may be negative + * @param minutes the amount of minutes, may be negative + * @param seconds the amount of seconds, may be negative + * @param nanos the amount of nanos, may be negative + * @return the period, not null + */ + public static Period ofTime(int hours, int minutes, int seconds, long nanos) { + return of(0, 0, 0, hours, minutes, seconds, nanos); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Period} from a period in the specified unit. + *

+ * The parameters represent the two parts of a phrase like '6 Days'. For example: + *

+     *  Period.of(3, SECONDS);
+     *  Period.of(5, YEARS);
+     * 
+ * The specified unit must be one of the supported units from {@link ChronoUnit}, + * {@code YEARS}, {@code MONTHS} or {@code DAYS} or be a time unit with an + * {@linkplain TemporalUnit#isDurationEstimated() exact duration}. + * Other units throw an exception. + * + * @param amount the amount of the period, measured in terms of the unit, positive or negative + * @param unit the unit that the period is measured in, must have an exact duration, not null + * @return the period, not null + * @throws DateTimeException if the period unit is invalid + * @throws ArithmeticException if a numeric overflow occurs + */ + public static Period of(long amount, TemporalUnit unit) { + return ZERO.plus(amount, unit); + } + + //----------------------------------------------------------------------- + /** + * Obtains a {@code Period} from a {@code Duration}. + *

+ * This converts the duration to a period. + * Within a period, the time fields are always normalized. + * The years, months and days fields will be zero. + *

+ * To populate the days field, call {@link #normalizedHoursToDays()} on the created period. + * + * @param duration the duration to convert, not null + * @return the period, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public static Period of(Duration duration) { + Objects.requireNonNull(duration, "duration"); + if (duration.isZero()) { + return ZERO; + } + return new Period(0, 0, 0, duration.toNanos()); + } + + //----------------------------------------------------------------------- + /** + * Returns a {@code Period} consisting of the number of years, months, days, + * hours, minutes, seconds, and nanoseconds between two {@code TemporalAccessor} instances. + *

+ * The start date is included, but the end date is not. Only whole years count. + * For example, from {@code 2010-01-15} to {@code 2011-03-18} is one year, two months and three days. + *

+ * This method examines the {@link ChronoField fields} {@code YEAR}, {@code MONTH_OF_YEAR}, + * {@code DAY_OF_MONTH} and {@code NANO_OF_DAY} + * The difference between each of the fields is calculated independently from the others. + * At least one of the four fields must be present. + *

+ * The four units are typically retained without normalization. + * However, years and months are normalized if the range of months is fixed, as it is with ISO. + *

+ * The result of this method can be a negative period if the end is before the start. + * The negative sign can be different in each of the four major units. + * + * @param start the start date, inclusive, not null + * @param end the end date, exclusive, not null + * @return the period between the date-times, not null + * @throws DateTimeException if the two date-times do have similar available fields + * @throws ArithmeticException if numeric overflow occurs + */ + public static Period between(TemporalAccessor start, TemporalAccessor end) { + if (Chrono.from(start).equals(Chrono.from(end)) == false) { + throw new DateTimeException("Unable to calculate period as date-times have different chronologies"); + } + int years = 0; + int months = 0; + int days = 0; + long nanos = 0; + boolean valid = false; + if (start.isSupported(YEAR)) { + years = Math.toIntExact(Math.subtractExact(end.getLong(YEAR), start.getLong(YEAR))); + valid = true; + } + if (start.isSupported(MONTH_OF_YEAR)) { + months = Math.toIntExact(Math.subtractExact(end.getLong(MONTH_OF_YEAR), start.getLong(MONTH_OF_YEAR))); + ValueRange startRange = Chrono.from(start).range(MONTH_OF_YEAR); + ValueRange endRange = Chrono.from(end).range(MONTH_OF_YEAR); + if (startRange.isFixed() && startRange.isIntValue() && startRange.equals(endRange)) { + int monthCount = (int) (startRange.getMaximum() - startRange.getMinimum() + 1); + long totMonths = ((long) months) + years * monthCount; + months = (int) (totMonths % monthCount); + years = Math.toIntExact(totMonths / monthCount); + } + valid = true; + } + if (start.isSupported(DAY_OF_MONTH)) { + days = Math.toIntExact(Math.subtractExact(end.getLong(DAY_OF_MONTH), start.getLong(DAY_OF_MONTH))); + valid = true; + } + if (start.isSupported(NANO_OF_DAY)) { + nanos = Math.subtractExact(end.getLong(NANO_OF_DAY), start.getLong(NANO_OF_DAY)); + valid = true; + } + if (valid == false) { + throw new DateTimeException("Unable to calculate period as date-times do not have any valid fields"); + } + return create(years, months, days, nanos); + } + + //----------------------------------------------------------------------- + /** + * Obtains a {@code Period} consisting of the number of years, months, + * and days between two dates. + *

+ * The start date is included, but the end date is not. + * The period is calculated by removing complete months, then calculating + * the remaining number of days, adjusting to ensure that both have the same sign. + * The number of months is then split into years and months based on a 12 month year. + * A month is considered if the end day-of-month is greater than or equal to the start day-of-month. + * For example, from {@code 2010-01-15} to {@code 2011-03-18} is one year, two months and three days. + *

+ * The result of this method can be a negative period if the end is before the start. + * The negative sign will be the same in each of year, month and day. + * + * @param startDate the start date, inclusive, not null + * @param endDate the end date, exclusive, not null + * @return the period between the dates, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public static Period betweenISO(LocalDate startDate, LocalDate endDate) { + long startMonth = startDate.getLong(EPOCH_MONTH); + long endMonth = endDate.getLong(EPOCH_MONTH); + long totalMonths = endMonth - startMonth; // safe + int days = endDate.getDayOfMonth() - startDate.getDayOfMonth(); + if (totalMonths > 0 && days < 0) { + totalMonths--; + LocalDate calcDate = startDate.plusMonths(totalMonths); + days = (int) (endDate.toEpochDay() - calcDate.toEpochDay()); // safe + } else if (totalMonths < 0 && days > 0) { + totalMonths++; + days -= endDate.lengthOfMonth(); + } + long years = totalMonths / 12; // safe + int months = (int) (totalMonths % 12); // safe + return ofDate(Math.toIntExact(years), months, days); + } + + //----------------------------------------------------------------------- + /** + * Obtains a {@code Period} consisting of the number of hours, minutes, + * seconds and nanoseconds between two times. + *

+ * The start time is included, but the end time is not. + * The period is calculated from the difference between the nano-of-day values + * of the two times. For example, from {@code 13:45:00} to {@code 14:50:30.123456789} + * is {@code P1H5M30.123456789S}. + *

+ * The result of this method can be a negative period if the end is before the start. + * + * @param startTime the start time, inclusive, not null + * @param endTime the end time, exclusive, not null + * @return the period between the times, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public static Period betweenISO(LocalTime startTime, LocalTime endTime) { + return create(0, 0, 0, endTime.toNanoOfDay() - startTime.toNanoOfDay()); + } + + //----------------------------------------------------------------------- + /** + * Obtains a {@code Period} from a text string such as {@code PnYnMnDTnHnMn.nS}. + *

+ * This will parse the string produced by {@code toString()} which is + * a subset of the ISO-8601 period format {@code PnYnMnDTnHnMn.nS}. + *

+ * The string consists of a series of numbers with a suffix identifying their meaning. + * The values, and suffixes, must be in the sequence year, month, day, hour, minute, second. + * Any of the number/suffix pairs may be omitted providing at least one is present. + * If the period is zero, the value is normally represented as {@code PT0S}. + * The numbers must consist of ASCII digits. + * Any of the numbers may be negative. Negative zero is not accepted. + * The number of nanoseconds is expressed as an optional fraction of the seconds. + * There must be at least one digit before any decimal point. + * There must be between 1 and 9 inclusive digits after any decimal point. + * The letters will all be accepted in upper or lower case. + * The decimal point may be either a dot or a comma. + * + * @param text the text to parse, not null + * @return the parsed period, not null + * @throws DateTimeParseException if the text cannot be parsed to a period + */ + public static Period parse(final CharSequence text) { + Objects.requireNonNull(text, "text"); + return new PeriodParser(text).parse(); + } + + //----------------------------------------------------------------------- + /** + * Creates an instance. + * + * @param years the amount + * @param months the amount + * @param days the amount + * @param nanos the amount + */ + private static Period create(int years, int months, int days, long nanos) { + if ((years | months | days | nanos) == 0) { + return ZERO; + } + return new Period(years, months, days, nanos); + } + + /** + * Constructor. + * + * @param years the amount + * @param months the amount + * @param days the amount + * @param nanos the amount + */ + private Period(int years, int months, int days, long nanos) { + this.years = years; + this.months = months; + this.days = days; + this.nanos = nanos; + } + + /** + * Resolves singletons. + * + * @return the resolved instance + */ + private Object readResolve() { + if ((years | months | days | nanos) == 0) { + return ZERO; + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Checks if this period is zero-length. + * + * @return true if this period is zero-length + */ + public boolean isZero() { + return (this == ZERO); + } + + /** + * Checks if this period is fully positive, excluding zero. + *

+ * This checks whether all the amounts in the period are positive, + * defined as greater than zero. + * + * @return true if this period is fully positive excluding zero + */ + public boolean isPositive() { + return ((years | months | days | nanos) > 0); + } + + //----------------------------------------------------------------------- + /** + * Gets the amount of years of this period. + * + * @return the amount of years of this period + */ + public int getYears() { + return years; + } + + /** + * Gets the amount of months of this period. + * + * @return the amount of months of this period + */ + public int getMonths() { + return months; + } + + /** + * Gets the amount of days of this period. + * + * @return the amount of days of this period + */ + public int getDays() { + return days; + } + + /** + * Gets the amount of hours of this period. + *

+ * Within a period, the time fields are always normalized. + * + * @return the amount of hours of this period + */ + public int getHours() { + return (int) (nanos / NANOS_PER_HOUR); + } + + /** + * Gets the amount of minutes within an hour of this period. + *

+ * Within a period, the time fields are always normalized. + * + * @return the amount of minutes within an hour of this period + */ + public int getMinutes() { + return (int) ((nanos / NANOS_PER_MINUTE) % 60); + } + + /** + * Gets the amount of seconds within a minute of this period. + *

+ * Within a period, the time fields are always normalized. + * + * @return the amount of seconds within a minute of this period + */ + public int getSeconds() { + return (int) ((nanos / NANOS_PER_SECOND) % 60); + } + + /** + * Gets the amount of nanoseconds within a second of this period. + *

+ * Within a period, the time fields are always normalized. + * + * @return the amount of nanoseconds within a second of this period + */ + public int getNanos() { + return (int) (nanos % NANOS_PER_SECOND); // safe from overflow + } + + /** + * Gets the total amount of the time units of this period, measured in nanoseconds. + *

+ * Within a period, the time fields are always normalized. + * + * @return the total amount of time unit nanoseconds of this period + */ + public long getTimeNanos() { + return nanos; + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this period with the specified amount of years. + *

+ * This method will only affect the years field. + * All other units are unaffected. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param years the years to represent + * @return a {@code Period} based on this period with the requested years, not null + */ + public Period withYears(int years) { + if (years == this.years) { + return this; + } + return create(years, months, days, nanos); + } + + /** + * Returns a copy of this period with the specified amount of months. + *

+ * This method will only affect the months field. + * All other units are unaffected. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param months the months to represent + * @return a {@code Period} based on this period with the requested months, not null + */ + public Period withMonths(int months) { + if (months == this.months) { + return this; + } + return create(years, months, days, nanos); + } + + /** + * Returns a copy of this period with the specified amount of days. + *

+ * This method will only affect the days field. + * All other units are unaffected. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param days the days to represent + * @return a {@code Period} based on this period with the requested days, not null + */ + public Period withDays(int days) { + if (days == this.days) { + return this; + } + return create(years, months, days, nanos); + } + + /** + * Returns a copy of this period with the specified total amount of time units + * expressed in nanoseconds. + *

+ * Within a period, the time fields are always normalized. + * This method will affect all the time units - hours, minutes, seconds and nanos. + * The date units are unaffected. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanos the nanoseconds to represent + * @return a {@code Period} based on this period with the requested nanoseconds, not null + */ + public Period withTimeNanos(long nanos) { + if (nanos == this.nanos) { + return this; + } + return create(years, months, days, nanos); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this period with the specified period added. + *

+ * This operates separately on the years, months, days and the normalized time. + * There is no further normalization beyond the normalized time. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param other the period to add, not null + * @return a {@code Period} based on this period with the requested period added, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Period plus(Period other) { + return create( + Math.addExact(years, other.years), + Math.addExact(months, other.months), + Math.addExact(days, other.days), + Math.addExact(nanos, other.nanos)); + } + + /** + * Returns a copy of this period with the specified period added. + *

+ * The specified unit must be one of the supported units from {@link ChronoUnit}, + * {@code YEARS}, {@code MONTHS} or {@code DAYS} or be a time unit with an + * {@linkplain TemporalUnit#isDurationEstimated() exact duration}. + * Other units throw an exception. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount to add, positive or negative + * @param unit the unit that the amount is expressed in, not null + * @return a {@code Period} based on this period with the requested amount added, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Period plus(long amount, TemporalUnit unit) { + Objects.requireNonNull(unit, "unit"); + if (unit instanceof ChronoUnit) { + if (unit == YEARS || unit == MONTHS || unit == DAYS || unit.isDurationEstimated() == false) { + if (amount == 0) { + return this; + } + switch((ChronoUnit) unit) { + case NANOS: return plusNanos(amount); + case MICROS: return plusNanos(Math.multiplyExact(amount, 1000L)); + case MILLIS: return plusNanos(Math.multiplyExact(amount, 1000_000L)); + case SECONDS: return plusSeconds(amount); + case MINUTES: return plusMinutes(amount); + case HOURS: return plusHours(amount); + case HALF_DAYS: return plusNanos(Math.multiplyExact(amount, 12 * NANOS_PER_HOUR)); + case DAYS: return plusDays(amount); + case MONTHS: return plusMonths(amount); + case YEARS: return plusYears(amount); + default: throw new DateTimeException("Unsupported unit: " + unit.getName()); + } + } + } + if (unit.isDurationEstimated()) { + throw new DateTimeException("Unsupported unit: " + unit.getName()); + } + return plusNanos(Duration.of(amount, unit).toNanos()); + } + + public Period plusYears(long amount) { + return create(Math.toIntExact(Math.addExact(years, amount)), months, days, nanos); + } + + public Period plusMonths(long amount) { + return create(years, Math.toIntExact(Math.addExact(months, amount)), days, nanos); + } + + public Period plusDays(long amount) { + return create(years, months, Math.toIntExact(Math.addExact(days, amount)), nanos); + } + + public Period plusHours(long amount) { + return plusNanos(Math.multiplyExact(amount, NANOS_PER_HOUR)); + } + + public Period plusMinutes(long amount) { + return plusNanos(Math.multiplyExact(amount, NANOS_PER_MINUTE)); + } + + public Period plusSeconds(long amount) { + return plusNanos(Math.multiplyExact(amount, NANOS_PER_SECOND)); + } + + public Period plusNanos(long amount) { + return create(years, months, days, Math.addExact(nanos, amount)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this period with the specified period subtracted. + *

+ * This operates separately on the years, months, days and the normalized time. + * There is no further normalization beyond the normalized time. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param other the period to subtract, not null + * @return a {@code Period} based on this period with the requested period subtracted, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Period minus(Period other) { + return create( + Math.subtractExact(years, other.years), + Math.subtractExact(months, other.months), + Math.subtractExact(days, other.days), + Math.subtractExact(nanos, other.nanos)); + } + + /** + * Returns a copy of this period with the specified period subtracted. + *

+ * The specified unit must be one of the supported units from {@link ChronoUnit}, + * {@code YEARS}, {@code MONTHS} or {@code DAYS} or be a time unit with an + * {@linkplain TemporalUnit#isDurationEstimated() exact duration}. + * Other units throw an exception. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount to subtract, positive or negative + * @param unit the unit that the amount is expressed in, not null + * @return a {@code Period} based on this period with the requested amount subtracted, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Period minus(long amount, TemporalUnit unit) { + return (amount == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amount, unit)); + } + + public Period minusYears(long amount) { + return (amount == Long.MIN_VALUE ? plusYears(Long.MAX_VALUE).plusYears(1) : plusYears(-amount)); + } + + public Period minusMonths(long amount) { + return (amount == Long.MIN_VALUE ? plusMonths(Long.MAX_VALUE).plusMonths(1) : plusMonths(-amount)); + } + + public Period minusDays(long amount) { + return (amount == Long.MIN_VALUE ? plusDays(Long.MAX_VALUE).plusDays(1) : plusDays(-amount)); + } + + public Period minusHours(long amount) { + return (amount == Long.MIN_VALUE ? plusHours(Long.MAX_VALUE).plusHours(1) : plusHours(-amount)); + } + + public Period minusMinutes(long amount) { + return (amount == Long.MIN_VALUE ? plusMinutes(Long.MAX_VALUE).plusMinutes(1) : plusMinutes(-amount)); + } + + public Period minusSeconds(long amount) { + return (amount == Long.MIN_VALUE ? plusSeconds(Long.MAX_VALUE).plusSeconds(1) : plusSeconds(-amount)); + } + + public Period minusNanos(long amount) { + return (amount == Long.MIN_VALUE ? plusNanos(Long.MAX_VALUE).plusNanos(1) : plusNanos(-amount)); + } + + //----------------------------------------------------------------------- + /** + * Returns a new instance with each element in this period multiplied + * by the specified scalar. + *

+ * This simply multiplies each field, years, months, days and normalized time, + * by the scalar. No normalization is performed. + * + * @param scalar the scalar to multiply by, not null + * @return a {@code Period} based on this period with the amounts multiplied by the scalar, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Period multipliedBy(int scalar) { + if (this == ZERO || scalar == 1) { + return this; + } + return create( + Math.multiplyExact(years, scalar), + Math.multiplyExact(months, scalar), + Math.multiplyExact(days, scalar), + Math.multiplyExact(nanos, scalar)); + } + + /** + * Returns a new instance with each amount in this period negated. + * + * @return a {@code Period} based on this period with the amounts negated, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Period negated() { + return multipliedBy(-1); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this period with the days and hours normalized using a 24 hour day. + *

+ * This normalizes the days and hours units, leaving years and months unchanged. + * The hours unit is adjusted to have an absolute value less than 23, + * with the days unit being adjusted to compensate. + * For example, a period of {@code P1DT27H} will be normalized to {@code P2DT3H}. + *

+ * The sign of the days and hours units will be the same after normalization. + * For example, a period of {@code P1DT-51H} will be normalized to {@code P-1DT-3H}. + * Since all time units are always normalized, if the hours units changes sign then + * other time units will also be affected. + *

+ * This instance is immutable and unaffected by this method call. + * + * @return a {@code Period} based on this period with excess hours normalized to days, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Period normalizedHoursToDays() { + // logic uses if statements to normalize signs to avoid unnecessary overflows + long totalDays = (nanos / NANOS_PER_DAY) + days; // no overflow + long splitNanos = nanos % NANOS_PER_DAY; + if (totalDays > 0 && splitNanos < 0) { + splitNanos += NANOS_PER_DAY; + totalDays--; + } else if (totalDays < 0 && splitNanos > 0) { + splitNanos -= NANOS_PER_DAY; + totalDays++; + } + if (totalDays == days && splitNanos == nanos) { + return this; + } + return create(years, months, Math.toIntExact(totalDays), splitNanos); + } + + /** + * Returns a copy of this period with any days converted to hours using a 24 hour day. + *

+ * The days unit is reduced to zero, with the hours unit increased by 24 times the + * days unit to compensate. Other units are unaffected. + * For example, a period of {@code P2DT4H} will be normalized to {@code PT52H}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @return a {@code Period} based on this period with days normalized to hours, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Period normalizedDaysToHours() { + if (days == 0) { + return this; + } + return create(years, months, 0, Math.addExact(Math.multiplyExact(days, NANOS_PER_DAY), nanos)); + } + + /** + * Returns a copy of this period with the years and months normalized using a 12 month year. + *

+ * This normalizes the years and months units, leaving other units unchanged. + * The months unit is adjusted to have an absolute value less than 11, + * with the years unit being adjusted to compensate. + * For example, a period of {@code P1Y15M} will be normalized to {@code P2Y3M}. + *

+ * The sign of the years and months units will be the same after normalization. + * For example, a period of {@code P1Y-25M} will be normalized to {@code P-1Y-1M}. + *

+ * This normalization uses a 12 month year it is not valid for all calendar systems. + *

+ * This instance is immutable and unaffected by this method call. + * + * @return a {@code Period} based on this period with years and months normalized, not null + * @throws ArithmeticException if numeric overflow occurs + */ + public Period normalizedMonthsISO() { + long totalMonths = years * 12L + months; // no overflow + long splitYears = totalMonths / 12; + int splitMonths = (int) (totalMonths % 12); // no overflow + if (splitYears == years && splitMonths == months) { + return this; + } + return create(Math.toIntExact(splitYears), splitMonths, days, nanos); + } + + //------------------------------------------------------------------------- + /** + * Converts this period to one that only has date units. + *

+ * The resulting period will have the same years, months and days as this period + * but the time units will all be zero. No normalization occurs in the calculation. + * For example, a period of {@code P1Y3MT12H} will be converted to {@code P1Y3M}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @return a {@code Period} based on this period with the time units set to zero, not null + */ + public Period toDateOnly() { + if (nanos == 0) { + return this; + } + return create(years, months, days, 0); + } + + //------------------------------------------------------------------------- + /** + * Adds this period to the specified temporal object. + *

+ * This returns a temporal object of the same observable type as the input + * with this period added. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#plus(TemporalAdder)}. + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   dateTime = thisPeriod.addTo(dateTime);
+     *   dateTime = dateTime.plus(thisPeriod);
+     * 
+ *

+ * The calculation will add the years, then months, then days, then nanos. + * Only non-zero amounts will be added. + * If the date-time has a calendar system with a fixed number of months in a + * year, then the years and months will be combined before being added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the temporal object to adjust, not null + * @return an object of the same type with the adjustment made, not null + * @throws DateTimeException if unable to add + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal addTo(Temporal temporal) { + Objects.requireNonNull(temporal, "temporal"); + if ((years | months) != 0) { + ValueRange startRange = Chrono.from(temporal).range(MONTH_OF_YEAR); + if (startRange.isFixed() && startRange.isIntValue()) { + long monthCount = startRange.getMaximum() - startRange.getMinimum() + 1; + temporal = temporal.plus(years * monthCount + months, MONTHS); + } else { + if (years != 0) { + temporal = temporal.plus(years, YEARS); + } + if (months != 0) { + temporal = temporal.plus(months, MONTHS); + } + } + } + if (days != 0) { + temporal = temporal.plus(days, DAYS); + } + if (nanos != 0) { + temporal = temporal.plus(nanos, NANOS); + } + return temporal; + } + + /** + * Subtracts this period from the specified temporal object. + *

+ * This returns a temporal object of the same observable type as the input + * with this period subtracted. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#minus(TemporalSubtractor)}. + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   dateTime = thisPeriod.subtractFrom(dateTime);
+     *   dateTime = dateTime.minus(thisPeriod);
+     * 
+ *

+ * The calculation will subtract the years, then months, then days, then nanos. + * Only non-zero amounts will be subtracted. + * If the date-time has a calendar system with a fixed number of months in a + * year, then the years and months will be combined before being subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the temporal object to adjust, not null + * @return an object of the same type with the adjustment made, not null + * @throws DateTimeException if unable to subtract + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal subtractFrom(Temporal temporal) { + Objects.requireNonNull(temporal, "temporal"); + if ((years | months) != 0) { + ValueRange startRange = Chrono.from(temporal).range(MONTH_OF_YEAR); + if (startRange.isFixed() && startRange.isIntValue()) { + long monthCount = startRange.getMaximum() - startRange.getMinimum() + 1; + temporal = temporal.minus(years * monthCount + months, MONTHS); + } else { + if (years != 0) { + temporal = temporal.minus(years, YEARS); + } + if (months != 0) { + temporal = temporal.minus(months, MONTHS); + } + } + } + if (days != 0) { + temporal = temporal.minus(days, DAYS); + } + if (nanos != 0) { + temporal = temporal.minus(nanos, NANOS); + } + return temporal; + } + + //----------------------------------------------------------------------- + /** + * Converts this period to one that only has time units. + *

+ * The resulting period will have the same time units as this period + * but the date units will all be zero. No normalization occurs in the calculation. + * For example, a period of {@code P1Y3MT12H} will be converted to {@code PT12H}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @return a {@code Period} based on this period with the date units set to zero, not null + */ + public Period toTimeOnly() { + if ((years | months | days) == 0) { + return this; + } + return create(0, 0, 0, nanos); + } + + //------------------------------------------------------------------------- + /** + * Calculates the duration of this period. + *

+ * The calculation uses the hours, minutes, seconds and nanoseconds fields. + * If years, months or days are present an exception is thrown. + * See {@link #toTimeOnly()} for a way to remove the date units and + * {@link #normalizedDaysToHours()} for a way to convert days to hours. + * + * @return a {@code Duration} equivalent to this period, not null + * @throws DateTimeException if the period cannot be converted as it contains years, months or days + */ + public Duration toDuration() { + if ((years | months | days) != 0) { + throw new DateTimeException("Unable to convert period to duration as years/months/days are present: " + this); + } + return Duration.ofNanos(nanos); + } + + //----------------------------------------------------------------------- + /** + * Checks if this period is equal to another period. + *

+ * The comparison is based on the amounts held in the period. + * To be equal, the years, months, days and normalized time fields must be equal. + * + * @param obj the object to check, null returns false + * @return true if this is equal to the other period + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Period) { + Period other = (Period) obj; + return years == other.years && months == other.months && + days == other.days && nanos == other.nanos; + } + return false; + } + + /** + * A hash code for this period. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + // ordered such that overflow from one field doesn't immediately affect the next field + return ((years << 27) | (years >>> 5)) ^ + ((days << 21) | (days >>> 11)) ^ + ((months << 17) | (months >>> 15)) ^ + ((int) (nanos ^ (nanos >>> 32))); + } + + //----------------------------------------------------------------------- + /** + * Outputs this period as a {@code String}, such as {@code P6Y3M1DT12H}. + *

+ * The output will be in the ISO-8601 period format. + * + * @return a string representation of this period, not null + */ + @Override + public String toString() { + if (this == ZERO) { + return "PT0S"; + } else { + StringBuilder buf = new StringBuilder(); + buf.append('P'); + if (years != 0) { + buf.append(years).append('Y'); + } + if (months != 0) { + buf.append(months).append('M'); + } + if (days != 0) { + buf.append(days).append('D'); + } + if (nanos != 0) { + buf.append('T'); + if (getHours() != 0) { + buf.append(getHours()).append('H'); + } + if (getMinutes() != 0) { + buf.append(getMinutes()).append('M'); + } + int secondPart = getSeconds(); + int nanoPart = getNanos(); + int secsNanosOr = secondPart | nanoPart; + if (secsNanosOr != 0) { // if either non-zero + if ((secsNanosOr & Integer.MIN_VALUE) != 0) { // if either less than zero + buf.append('-'); + secondPart = Math.abs(secondPart); + nanoPart = Math.abs(nanoPart); + } + buf.append(secondPart); + if (nanoPart != 0) { + int dotPos = buf.length(); + nanoPart += 1000_000_000; + while (nanoPart % 10 == 0) { + nanoPart /= 10; + } + buf.append(nanoPart); + buf.setCharAt(dotPos, '.'); + } + buf.append('S'); + } + } + return buf.toString(); + } + } + +} diff --git a/src/share/classes/java/time/PeriodParser.java b/src/share/classes/java/time/PeriodParser.java new file mode 100644 index 0000000000000000000000000000000000000000..6a424ae778261a799ebfc0e913b29fe94f24e586 --- /dev/null +++ b/src/share/classes/java/time/PeriodParser.java @@ -0,0 +1,307 @@ +/* + * 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 java.time; + +import java.time.format.DateTimeParseException; + +/** + * A period parser that creates an instance of {@code Period} from a string. + * This parses the ISO-8601 period format {@code PnYnMnDTnHnMn.nS}. + *

+ * This class is mutable and intended for use by a single thread. + * + * @since 1.8 + */ +final class PeriodParser { + + /** + * Used to validate the correct sequence of tokens. + */ + private static final String TOKEN_SEQUENCE = "PYMDTHMS"; + /** + * The standard string representing a zero period. + */ + private static final String ZERO = "PT0S"; + + /** + * The number of years. + */ + private int years; + /** + * The number of months. + */ + private int months; + /** + * The number of days. + */ + private int days; + /** + * The number of hours. + */ + private int hours; + /** + * The number of minutes. + */ + private int minutes; + /** + * The number of seconds. + */ + private int seconds; + /** + * The number of nanoseconds. + */ + private long nanos; + /** + * Whether the seconds were negative. + */ + private boolean negativeSecs; + /** + * Parser position index. + */ + private int index; + /** + * Original text. + */ + private CharSequence text; + + /** + * Constructor. + * + * @param text the text to parse, not null + */ + PeriodParser(CharSequence text) { + this.text = text; + } + + //----------------------------------------------------------------------- + /** + * Performs the parse. + *

+ * This parses the text set in the constructor in the format PnYnMnDTnHnMn.nS. + * + * @return the created Period, not null + * @throws DateTimeParseException if the text cannot be parsed to a Period + */ + Period parse() { + // force to upper case and coerce the comma to dot + + String s = text.toString().toUpperCase().replace(',', '.'); + // check for zero and skip parse + if (ZERO.equals(s)) { + return Period.ZERO; + } + if (s.length() < 3 || s.charAt(0) != 'P') { + throw new DateTimeParseException("Period could not be parsed: " + text, text, 0); + } + validateCharactersAndOrdering(s, text); + + // strip off the leading P + String[] datetime = s.substring(1).split("T"); + switch (datetime.length) { + case 2: + parseDate(datetime[0], 1); + parseTime(datetime[1], datetime[0].length() + 2); + break; + case 1: + parseDate(datetime[0], 1); + break; + } + return toPeriod(); + } + + private void parseDate(String s, int baseIndex) { + index = 0; + while (index < s.length()) { + String value = parseNumber(s); + if (index < s.length()) { + char c = s.charAt(index); + switch(c) { + case 'Y': years = parseInt(value, baseIndex) ; break; + case 'M': months = parseInt(value, baseIndex) ; break; + case 'D': days = parseInt(value, baseIndex) ; break; + default: + throw new DateTimeParseException("Period could not be parsed, unrecognized letter '" + + c + ": " + text, text, baseIndex + index); + } + index++; + } + } + } + + private void parseTime(String s, int baseIndex) { + index = 0; + s = prepareTime(s, baseIndex); + while (index < s.length()) { + String value = parseNumber(s); + if (index < s.length()) { + char c = s.charAt(index); + switch(c) { + case 'H': hours = parseInt(value, baseIndex) ; break; + case 'M': minutes = parseInt(value, baseIndex) ; break; + case 'S': seconds = parseInt(value, baseIndex) ; break; + case 'N': nanos = parseNanos(value, baseIndex); break; + default: + throw new DateTimeParseException("Period could not be parsed, unrecognized letter '" + + c + "': " + text, text, baseIndex + index); + } + index++; + } + } + } + + private long parseNanos(String s, int baseIndex) { + if (s.length() > 9) { + throw new DateTimeParseException("Period could not be parsed, nanosecond range exceeded: " + + text, text, baseIndex + index - s.length()); + } + // pad to the right to create 10**9, then trim + return Long.parseLong((s + "000000000").substring(0, 9)); + } + + private String prepareTime(String s, int baseIndex) { + if (s.contains(".")) { + int i = s.indexOf(".") + 1; + + // verify that the first character after the dot is a digit + if (Character.isDigit(s.charAt(i))) { + i++; + } else { + throw new DateTimeParseException("Period could not be parsed, invalid decimal number: " + + text, text, baseIndex + index); + } + + // verify that only digits follow the decimal point followed by an S + while (i < s.length()) { + // || !Character.isDigit(s.charAt(i)) + char c = s.charAt(i); + if (Character.isDigit(c) || c == 'S') { + i++; + } else { + throw new DateTimeParseException("Period could not be parsed, invalid decimal number: " + + text, text, baseIndex + index); + } + } + s = s.replace('S', 'N').replace('.', 'S'); + if (s.contains("-0S")) { + negativeSecs = true; + s = s.replace("-0S", "0S"); + } + } + return s; + } + + private int parseInt(String s, int baseIndex) { + try { + int value = Integer.parseInt(s); + if (s.charAt(0) == '-' && value == 0) { + throw new DateTimeParseException("Period could not be parsed, invalid number '" + + s + "': " + text, text, baseIndex + index - s.length()); + } + return value; + } catch (NumberFormatException ex) { + throw new DateTimeParseException("Period could not be parsed, invalid number '" + + s + "': " + text, text, baseIndex + index - s.length()); + } + } + + private String parseNumber(String s) { + int start = index; + while (index < s.length()) { + char c = s.charAt(index); + if ((c < '0' || c > '9') && c != '-') { + break; + } + index++; + } + return s.substring(start, index); + } + + private void validateCharactersAndOrdering(String s, CharSequence text) { + char[] chars = s.toCharArray(); + int tokenPos = 0; + boolean lastLetter = false; + for (int i = 0; i < chars.length; i++) { + if (tokenPos >= TOKEN_SEQUENCE.length()) { + throw new DateTimeParseException("Period could not be parsed, characters after last 'S': " + text, text, i); + } + char c = chars[i]; + if ((c < '0' || c > '9') && c != '-' && c != '.') { + tokenPos = TOKEN_SEQUENCE.indexOf(c, tokenPos); + if (tokenPos < 0) { + throw new DateTimeParseException("Period could not be parsed, invalid character '" + c + "': " + text, text, i); + } + tokenPos++; + lastLetter = true; + } else { + lastLetter = false; + } + } + if (lastLetter == false) { + throw new DateTimeParseException("Period could not be parsed, invalid last character: " + text, text, s.length() - 1); + } + } + + private Period toPeriod() { + return Period.of(years, months, days, hours, minutes, seconds, negativeSecs || seconds < 0 ? -nanos : nanos); + } + +} diff --git a/src/share/classes/java/time/Ser.java b/src/share/classes/java/time/Ser.java new file mode 100644 index 0000000000000000000000000000000000000000..d665ccf7580b5a52bf18cde95d4d7da1974844d7 --- /dev/null +++ b/src/share/classes/java/time/Ser.java @@ -0,0 +1,211 @@ +/* + * 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) 2011-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 java.time; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.Externalizable; +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.StreamCorruptedException; + +/** + * The shared serialization delegate for this package. + * + *

Implementation notes

+ * This class wraps the object being serialized, and takes a byte representing the type of the class to + * be serialized. This byte can also be used for versioning the serialization format. In this case another + * byte flag would be used in order to specify an alternative version of the type format. + * For example {@code LOCAL_DATE_TYPE_VERSION_2 = 21}. + *

+ * In order to serialise the object it writes its byte and then calls back to the appropriate class where + * the serialisation is performed. In order to deserialise the object it read in the type byte, switching + * in order to select which class to call back into. + *

+ * The serialisation format is determined on a per class basis. In the case of field based classes each + * of the fields is written out with an appropriate size format in descending order of the field's size. For + * example in the case of {@link LocalDate} year is written before month. Composite classes, such as + * {@link LocalDateTime} are serialised as one object. + *

+ * This class is mutable and should be created once per serialization. + * + * @serial include + * @since 1.8 + */ +final class Ser implements Externalizable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = -7683839454370182990L; + + static final byte DURATION_TYPE = 1; + static final byte INSTANT_TYPE = 2; + static final byte LOCAL_DATE_TYPE = 3; + static final byte LOCAL_TIME_TYPE = 4; + static final byte LOCAL_DATE_TIME_TYPE = 5; + static final byte ZONE_DATE_TIME_TYPE = 6; + static final byte ZONE_REGION_TYPE = 7; + static final byte ZONE_OFFSET_TYPE = 8; + + /** The type being serialized. */ + private byte type; + /** The object being serialized. */ + private Object object; + + /** + * Constructor for deserialization. + */ + public Ser() { + } + + /** + * Creates an instance for serialization. + * + * @param type the type + * @param object the object + */ + Ser(byte type, Object object) { + this.type = type; + this.object = object; + } + + //----------------------------------------------------------------------- + /** + * Implements the {@code Externalizable} interface to write the object. + * + * @param out the data stream to write to, not null + */ + public void writeExternal(ObjectOutput out) throws IOException { + writeInternal(type, object, out); + } + + static void writeInternal(byte type, Object object, DataOutput out) throws IOException { + out.writeByte(type); + switch (type) { + case DURATION_TYPE: + ((Duration) object).writeExternal(out); + break; + case INSTANT_TYPE: + ((Instant) object).writeExternal(out); + break; + case LOCAL_DATE_TYPE: + ((LocalDate) object).writeExternal(out); + break; + case LOCAL_DATE_TIME_TYPE: + ((LocalDateTime) object).writeExternal(out); + break; + case LOCAL_TIME_TYPE: + ((LocalTime) object).writeExternal(out); + break; + case ZONE_REGION_TYPE: + ((ZoneRegion) object).writeExternal(out); + break; + case ZONE_OFFSET_TYPE: + ((ZoneOffset) object).writeExternal(out); + break; + case ZONE_DATE_TIME_TYPE: + ((ZonedDateTime) object).writeExternal(out); + break; + default: + throw new InvalidClassException("Unknown serialized type"); + } + } + + //----------------------------------------------------------------------- + /** + * Implements the {@code Externalizable} interface to read the object. + * + * @param in the data to read, not null + */ + public void readExternal(ObjectInput in) throws IOException { + type = in.readByte(); + object = readInternal(type, in); + } + + static Object read(DataInput in) throws IOException { + byte type = in.readByte(); + return readInternal(type, in); + } + + private static Object readInternal(byte type, DataInput in) throws IOException { + switch (type) { + case DURATION_TYPE: return Duration.readExternal(in); + case INSTANT_TYPE: return Instant.readExternal(in); + case LOCAL_DATE_TYPE: return LocalDate.readExternal(in); + case LOCAL_DATE_TIME_TYPE: return LocalDateTime.readExternal(in); + case LOCAL_TIME_TYPE: return LocalTime.readExternal(in); + case ZONE_DATE_TIME_TYPE: return ZonedDateTime.readExternal(in); + case ZONE_OFFSET_TYPE: return ZoneOffset.readExternal(in); + case ZONE_REGION_TYPE: return ZoneRegion.readExternal(in); + default: + throw new StreamCorruptedException("Unknown serialized type"); + } + } + + /** + * Returns the object that will replace this one. + * + * @return the read object, should never be null + */ + private Object readResolve() { + return object; + } + +} diff --git a/src/share/classes/java/time/ZoneId.java b/src/share/classes/java/time/ZoneId.java new file mode 100644 index 0000000000000000000000000000000000000000..cc7d83c1465d24886cacf4eee39228b7f3206fab --- /dev/null +++ b/src/share/classes/java/time/ZoneId.java @@ -0,0 +1,518 @@ +/* + * 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 java.time; + +import java.io.DataOutput; +import java.io.IOException; +import java.io.Serializable; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.TextStyle; +import java.time.temporal.Queries; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.zone.ZoneRules; +import java.time.zone.ZoneRulesProvider; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.TimeZone; + +/** + * A time-zone ID, such as {@code Europe/Paris}. + *

+ * A {@code ZoneId} is used to identify the rules used to convert between + * an {@link Instant} and a {@link LocalDateTime}. + * There are two distinct types of ID: + *

    + *
  • Fixed offsets - a fully resolved offset from UTC/Greenwich, that uses + * the same offset for all local date-times + *
  • Geographical regions - an area where a specific set of rules for finding + * the offset from UTC/Greenwich apply + *

+ * Most fixed offsets are represented by {@link ZoneOffset}. + *

+ * The actual rules, describing when and how the offset changes, are defined by {@link ZoneRules}. + * This class is simply an ID used to obtain the underlying rules. + * This approach is taken because rules are defined by governments and change + * frequently, whereas the ID is stable. + *

+ * The distinction has other effects. Serializing the {@code ZoneId} will only send + * the ID, whereas serializing the rules sends the entire data set. + * Similarly, a comparison of two IDs only examines the ID, whereas + * a comparison of two rules examines the entire data set. + *

+ * The code supports loading a {@code ZoneId} on a JVM which does not have available rules + * for that ID. This allows the date-time object, such as {@link ZonedDateTime}, + * to still be queried. + * + *

Time-zone IDs

+ * The ID is unique within the system. + * The formats for offset and region IDs differ. + *

+ * An ID is parsed as an offset ID if it starts with 'UTC', 'GMT', '+' or '-', or + * is a single letter. + * For example, 'Z', '+02:00', '-05:00', 'UTC+05' and 'GMT-6' are all valid offset IDs. + * Note that some IDs, such as 'D' or '+ABC' meet the criteria, but are invalid. + *

+ * All other IDs are considered to be region IDs. + *

+ * Region IDs are defined by configuration, which can be thought of as a {@code Map} + * from region ID to {@code ZoneRules}, see {@link ZoneRulesProvider}. + *

+ * Time-zones are defined by governments and change frequently. There are a number of + * organizations, known here as groups, that monitor time-zone changes and collate them. + * The default group is the IANA Time Zone Database (TZDB). + * Other organizations include IATA (the airline industry body) and Microsoft. + *

+ * Each group defines its own format for region ID. + * The TZDB group defines IDs such as 'Europe/London' or 'America/New_York'. + * TZDB IDs take precedence over other groups. + *

+ * It is strongly recommended that the group name is included in all Ids supplied by + * groups other than TZDB to avoid conflicts. For example, IATA airline time-zone + * region IDs are typically the same as the three letter airport code. + * However, the airport of Utrecht has the code 'UTC', which is obviously a conflict. + * The recommended format for region IDs from groups other than TZDB is 'group~region'. + * Thus if IATA data were defined, Utrecht airport would be 'IATA~UTC'. + * + *

Specification for implementors

+ * This abstract class has two implementations, both of which are immutable and thread-safe. + * One implementation models region-based IDs, the other is {@code ZoneOffset} modelling + * offset-based IDs. + * + * @since 1.8 + */ +public abstract class ZoneId implements Serializable { + + /** + * A map of zone overrides to enable the older US time-zone names to be used. + *

+ * This maps as follows: + *

    + *
  • EST - America/Indianapolis
  • + *
  • MST - America/Phoenix
  • + *
  • HST - Pacific/Honolulu
  • + *
  • ACT - Australia/Darwin
  • + *
  • AET - Australia/Sydney
  • + *
  • AGT - America/Argentina/Buenos_Aires
  • + *
  • ART - Africa/Cairo
  • + *
  • AST - America/Anchorage
  • + *
  • BET - America/Sao_Paulo
  • + *
  • BST - Asia/Dhaka
  • + *
  • CAT - Africa/Harare
  • + *
  • CNT - America/St_Johns
  • + *
  • CST - America/Chicago
  • + *
  • CTT - Asia/Shanghai
  • + *
  • EAT - Africa/Addis_Ababa
  • + *
  • ECT - Europe/Paris
  • + *
  • IET - America/Indiana/Indianapolis
  • + *
  • IST - Asia/Kolkata
  • + *
  • JST - Asia/Tokyo
  • + *
  • MIT - Pacific/Apia
  • + *
  • NET - Asia/Yerevan
  • + *
  • NST - Pacific/Auckland
  • + *
  • PLT - Asia/Karachi
  • + *
  • PNT - America/Phoenix
  • + *
  • PRT - America/Puerto_Rico
  • + *
  • PST - America/Los_Angeles
  • + *
  • SST - Pacific/Guadalcanal
  • + *
  • VST - Asia/Ho_Chi_Minh
  • + *

+ * The map is unmodifiable. + */ + public static final Map OLD_IDS_PRE_2005; + /** + * A map of zone overrides to enable the older US time-zone names to be used. + *

+ * This maps as follows: + *

    + *
  • EST - -05:00
  • + *
  • HST - -10:00
  • + *
  • MST - -07:00
  • + *
  • ACT - Australia/Darwin
  • + *
  • AET - Australia/Sydney
  • + *
  • AGT - America/Argentina/Buenos_Aires
  • + *
  • ART - Africa/Cairo
  • + *
  • AST - America/Anchorage
  • + *
  • BET - America/Sao_Paulo
  • + *
  • BST - Asia/Dhaka
  • + *
  • CAT - Africa/Harare
  • + *
  • CNT - America/St_Johns
  • + *
  • CST - America/Chicago
  • + *
  • CTT - Asia/Shanghai
  • + *
  • EAT - Africa/Addis_Ababa
  • + *
  • ECT - Europe/Paris
  • + *
  • IET - America/Indiana/Indianapolis
  • + *
  • IST - Asia/Kolkata
  • + *
  • JST - Asia/Tokyo
  • + *
  • MIT - Pacific/Apia
  • + *
  • NET - Asia/Yerevan
  • + *
  • NST - Pacific/Auckland
  • + *
  • PLT - Asia/Karachi
  • + *
  • PNT - America/Phoenix
  • + *
  • PRT - America/Puerto_Rico
  • + *
  • PST - America/Los_Angeles
  • + *
  • SST - Pacific/Guadalcanal
  • + *
  • VST - Asia/Ho_Chi_Minh
  • + *

+ * The map is unmodifiable. + */ + public static final Map OLD_IDS_POST_2005; + static { + Map base = new HashMap<>(); + base.put("ACT", "Australia/Darwin"); + base.put("AET", "Australia/Sydney"); + base.put("AGT", "America/Argentina/Buenos_Aires"); + base.put("ART", "Africa/Cairo"); + base.put("AST", "America/Anchorage"); + base.put("BET", "America/Sao_Paulo"); + base.put("BST", "Asia/Dhaka"); + base.put("CAT", "Africa/Harare"); + base.put("CNT", "America/St_Johns"); + base.put("CST", "America/Chicago"); + base.put("CTT", "Asia/Shanghai"); + base.put("EAT", "Africa/Addis_Ababa"); + base.put("ECT", "Europe/Paris"); + base.put("IET", "America/Indiana/Indianapolis"); + base.put("IST", "Asia/Kolkata"); + base.put("JST", "Asia/Tokyo"); + base.put("MIT", "Pacific/Apia"); + base.put("NET", "Asia/Yerevan"); + base.put("NST", "Pacific/Auckland"); + base.put("PLT", "Asia/Karachi"); + base.put("PNT", "America/Phoenix"); + base.put("PRT", "America/Puerto_Rico"); + base.put("PST", "America/Los_Angeles"); + base.put("SST", "Pacific/Guadalcanal"); + base.put("VST", "Asia/Ho_Chi_Minh"); + Map pre = new HashMap<>(base); + pre.put("EST", "America/Indianapolis"); + pre.put("MST", "America/Phoenix"); + pre.put("HST", "Pacific/Honolulu"); + OLD_IDS_PRE_2005 = Collections.unmodifiableMap(pre); + Map post = new HashMap<>(base); + post.put("EST", "-05:00"); + post.put("MST", "-07:00"); + post.put("HST", "-10:00"); + OLD_IDS_POST_2005 = Collections.unmodifiableMap(post); + } + /** + * Serialization version. + */ + private static final long serialVersionUID = 8352817235686L; + + //----------------------------------------------------------------------- + /** + * Gets the system default time-zone. + *

+ * This queries {@link TimeZone#getDefault()} to find the default time-zone + * and converts it to a {@code ZoneId}. If the system default time-zone is changed, + * then the result of this method will also change. + * + * @return the zone ID, not null + * @throws DateTimeException if the converted zone ID has an invalid format + * @throws java.time.zone.ZoneRulesException if the converted zone region ID cannot be found + */ + public static ZoneId systemDefault() { + return ZoneId.of(TimeZone.getDefault().getID(), OLD_IDS_POST_2005); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code ZoneId} using its ID using a map + * of aliases to supplement the standard zone IDs. + *

+ * Many users of time-zones use short abbreviations, such as PST for + * 'Pacific Standard Time' and PDT for 'Pacific Daylight Time'. + * These abbreviations are not unique, and so cannot be used as IDs. + * This method allows a map of string to time-zone to be setup and reused + * within an application. + * + * @param zoneId the time-zone ID, not null + * @param aliasMap a map of alias zone IDs (typically abbreviations) to real zone IDs, not null + * @return the zone ID, not null + * @throws DateTimeException if the zone ID has an invalid format + * @throws java.time.zone.ZoneRulesException if the zone region ID cannot be found + */ + public static ZoneId of(String zoneId, Map aliasMap) { + Objects.requireNonNull(zoneId, "zoneId"); + Objects.requireNonNull(aliasMap, "aliasMap"); + String id = aliasMap.get(zoneId); + id = (id != null ? id : zoneId); + return of(id); + } + + /** + * Obtains an instance of {@code ZoneId} from an ID ensuring that the + * ID is valid and available for use. + *

+ * This method parses the ID, applies any appropriate normalization, and validates it + * against the known set of IDs for which rules are available. + *

+ * An ID is parsed as though it is an offset ID if it starts with 'UTC', 'GMT', '+' + * or '-', or if it has less then two letters. + * The offset of {@link ZoneOffset#UTC zero} may be represented in multiple ways, + * including 'Z', 'UTC', 'GMT', 'UTC0' 'GMT0', '+00:00', '-00:00' and 'UTC+00:00'. + *

+ * Eight forms of ID are recognized, where '{offset}' means to parse using {@link ZoneOffset#of(String)}: + *

    + *
  • {offset} - a {@link ZoneOffset} ID, such as 'Z' or '+02:00' + *
  • UTC - alternate form of a {@code ZoneOffset} ID equal to 'Z' + *
  • UTC0 - alternate form of a {@code ZoneOffset} ID equal to 'Z' + *
  • UTC{offset} - alternate form of a {@code ZoneOffset} ID equal to '{offset}' + *
  • GMT - alternate form of a {@code ZoneOffset} ID equal to 'Z' + *
  • GMT0 - alternate form of a {@code ZoneOffset} ID equal to 'Z' + *
  • GMT{offset} - alternate form of a {@code ZoneOffset} ID equal to '{offset}'r + *
  • {regionID} - full region ID, loaded from configuration + *

+ * Region IDs must match the regular expression [A-Za-z][A-Za-z0-9~/._+-]+. + *

+ * The detailed format of the region ID depends on the group supplying the data. + * The default set of data is supplied by the IANA Time Zone Database (TZDB) + * This has region IDs of the form '{area}/{city}', such as 'Europe/Paris' or 'America/New_York'. + * This is compatible with most IDs from {@link java.util.TimeZone}. + * + * @param zoneId the time-zone ID, not null + * @return the zone ID, not null + * @throws DateTimeException if the zone ID has an invalid format + * @throws java.time.zone.ZoneRulesException if the zone region ID cannot be found + */ + public static ZoneId of(String zoneId) { + Objects.requireNonNull(zoneId, "zoneId"); + if (zoneId.length() <= 1 || zoneId.startsWith("+") || zoneId.startsWith("-")) { + return ZoneOffset.of(zoneId); + } else if (zoneId.startsWith("UTC") || zoneId.startsWith("GMT")) { + if (zoneId.length() == 3 || (zoneId.length() == 4 && zoneId.charAt(3) == '0')) { + return ZoneOffset.UTC; + } + return ZoneOffset.of(zoneId.substring(3)); + } + return ZoneRegion.ofId(zoneId, true); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code ZoneId} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code ZoneId}. + *

+ * The conversion will try to obtain the zone in a way that favours region-based + * zones over offset-based zones using {@link Queries#zone()}. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used in queries via method reference, {@code ZoneId::from}. + * + * @param temporal the temporal object to convert, not null + * @return the zone ID, not null + * @throws DateTimeException if unable to convert to a {@code ZoneId} + */ + public static ZoneId from(TemporalAccessor temporal) { + ZoneId obj = temporal.query(Queries.zone()); + if (obj == null) { + throw new DateTimeException("Unable to obtain ZoneId from TemporalAccessor: " + temporal.getClass()); + } + return obj; + } + + //----------------------------------------------------------------------- + /** + * Constructor only accessible within the package. + */ + ZoneId() { + if (getClass() != ZoneOffset.class && getClass() != ZoneRegion.class) { + throw new AssertionError("Invalid subclass"); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the unique time-zone ID. + *

+ * This ID uniquely defines this object. + * The format of an offset based ID is defined by {@link ZoneOffset#getId()}. + * + * @return the time-zone unique ID, not null + */ + public abstract String getId(); + + //----------------------------------------------------------------------- + /** + * Gets the time-zone rules for this ID allowing calculations to be performed. + *

+ * The rules provide the functionality associated with a time-zone, + * such as finding the offset for a given instant or local date-time. + *

+ * A time-zone can be invalid if it is deserialized in a JVM which does not + * have the same rules loaded as the JVM that stored it. In this case, calling + * this method will throw an exception. + *

+ * The rules are supplied by {@link ZoneRulesProvider}. An advanced provider may + * support dynamic updates to the rules without restarting the JVM. + * If so, then the result of this method may change over time. + * Each individual call will be still remain thread-safe. + *

+ * {@link ZoneOffset} will always return a set of rules where the offset never changes. + * + * @return the rules, not null + * @throws DateTimeException if no rules are available for this ID + */ + public abstract ZoneRules getRules(); + + //----------------------------------------------------------------------- + /** + * Gets the textual representation of the zone, such as 'British Time' or + * '+02:00'. + *

+ * This returns a textual description for the time-zone ID. + *

+ * If no textual mapping is found then the {@link #getId() full ID} is returned. + * + * @param style the length of the text required, not null + * @param locale the locale to use, not null + * @return the text value of the zone, not null + */ + public String getText(TextStyle style, Locale locale) { + return new DateTimeFormatterBuilder().appendZoneText(style).toFormatter(locale).print(new TemporalAccessor() { + @Override + public boolean isSupported(TemporalField field) { + return false; + } + @Override + public long getLong(TemporalField field) { + throw new DateTimeException("Unsupported field: " + field); + } + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.zoneId()) { + return (R) ZoneId.this; + } + return TemporalAccessor.super.query(query); + } + }); + } + + //----------------------------------------------------------------------- + /** + * Checks if this time-zone ID is equal to another time-zone ID. + *

+ * The comparison is based on the ID. + * + * @param obj the object to check, null returns false + * @return true if this is equal to the other time-zone ID + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof ZoneId) { + ZoneId other = (ZoneId) obj; + return getId().equals(other.getId()); + } + return false; + } + + /** + * A hash code for this time-zone ID. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return getId().hashCode(); + } + + //----------------------------------------------------------------------- + /** + * Outputs this zone as a {@code String}, using the ID. + * + * @return a string representation of this time-zone ID, not null + */ + @Override + public String toString() { + return getId(); + } + + //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(7);  // identifies this as a ZoneId (not ZoneOffset)
+     *  out.writeUTF(zoneId);
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + // this is here for serialization Javadoc + private Object writeReplace() { + return new Ser(Ser.ZONE_REGION_TYPE, this); + } + + abstract void write(DataOutput out) throws IOException; + +} diff --git a/src/share/classes/java/time/ZoneOffset.java b/src/share/classes/java/time/ZoneOffset.java new file mode 100644 index 0000000000000000000000000000000000000000..5436a71f44c9fb4433bb5414bb47877b3981547f --- /dev/null +++ b/src/share/classes/java/time/ZoneOffset.java @@ -0,0 +1,789 @@ +/* + * 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 java.time; + +import static java.time.temporal.ChronoField.OFFSET_SECONDS; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.temporal.ChronoField; +import java.time.temporal.Queries; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.ValueRange; +import java.time.zone.ZoneRules; +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}. + *

+ * 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. + *

+ * Different parts of the world have different time-zone offsets. + * The rules for how offsets vary by place and time of year are captured in the + * {@link ZoneId} class. + *

+ * For example, Paris is one hour ahead of Greenwich/UTC in winter and two hours + * ahead in summer. The {@code ZoneId} instance for Paris will reference two + * {@code ZoneOffset} instances - a {@code +01:00} instance for winter, + * and a {@code +02:00} instance for summer. + *

+ * In 2008, time-zone offsets around the world extended from -12:00 to +14:00. + * To prevent any problems with that range being extended, yet still provide + * validation, the range of offsets is restricted to -18:00 to 18:00 inclusive. + *

+ * This class is designed for use with the ISO calendar system. + * The fields of hours, minutes and seconds make assumptions that are valid for the + * standard ISO definitions of those fields. This class may be used with other + * calendar systems providing the definition of the time fields matches those + * of the ISO calendar system. + *

+ * Instances of {@code ZoneOffset} must be compared using {@link #equals}. + * Implementations may choose to cache certain common offsets, however + * applications must not rely on such caching. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class ZoneOffset + extends ZoneId + implements TemporalAccessor, TemporalAdjuster, Comparable, Serializable { + + /** Cache of time-zone offset by offset in seconds. */ + private static final ConcurrentMap SECONDS_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4); + /** Cache of time-zone offset by ID. */ + private static final ConcurrentMap 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. + *

+ * 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: + *

    + *
  • {@code Z} - for UTC + *
  • {@code +h} + *
  • {@code +hh} + *
  • {@code +hh:mm} + *
  • {@code -hh:mm} + *
  • {@code +hhmm} + *
  • {@code -hhmm} + *
  • {@code +hh:mm:ss} + *
  • {@code -hh:mm:ss} + *
  • {@code +hhmmss} + *
  • {@code -hhmmss} + *

+ * Note that ± means either the plus or minus symbol. + *

+ * The ID of the returned offset will be normalized to one of the formats + * described by {@link #getId()}. + *

+ * 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. + *

+ * 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. + *

+ * 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); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code ZoneOffset} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code ZoneOffset}. + *

+ * The conversion extracts the {@link ChronoField#OFFSET_SECONDS offset-seconds} field. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used in queries via method reference, {@code ZoneOffset::from}. + * + * @param temporal the temporal object to convert, not null + * @return the zone-offset, not null + * @throws DateTimeException if unable to convert to an {@code ZoneOffset} + */ + public static ZoneOffset from(TemporalAccessor temporal) { + if (temporal instanceof ZoneOffset) { + return (ZoneOffset) temporal; + } + try { + return ofTotalSeconds(temporal.get(OFFSET_SECONDS)); + } catch (DateTimeException ex) { + throw new DateTimeException("Unable to obtain ZoneOffset from TemporalAccessor: " + temporal.getClass(), ex); + } + } + + //----------------------------------------------------------------------- + /** + * 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 + *

+ * 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. + *

+ * 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. + *

+ * The ID is minor variation to the standard ISO-8601 formatted string + * for the offset. There are three formats: + *

    + *
  • {@code Z} - for UTC (ISO-8601) + *
  • {@code +hh:mm} or {@code -hh:mm} - if the seconds are zero (ISO-8601) + *
  • {@code +hh:mm:ss} or {@code -hh:mm:ss} - if the seconds are non-zero (not ISO-8601) + *

+ * + * @return the zone offset ID, not null + */ + @Override + public String getId() { + return id; + } + + /** + * Gets the associated time-zone rules. + *

+ * The rules will always return this offset when queried. + * The implementation class is immutable, thread-safe and serializable. + * + * @return the rules, not null + */ + @Override + public ZoneRules getRules() { + return ZoneRules.of(this); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this offset can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and + * {@link #get(TemporalField) get} methods will throw an exception. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@code OFFSET_SECONDS} field returns true. + * All other {@code ChronoField} instances will return false. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the field is supported is determined by the field. + * + * @param field the field to check, null returns false + * @return true if the field is supported on this offset, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == OFFSET_SECONDS; + } + return field != null && field.doIsSupported(this); + } + + /** + * Gets the range of valid values for the specified field. + *

+ * The range object expresses the minimum and maximum valid values for a field. + * This offset is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return + * appropriate range instances. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + @Override // override for Javadoc + public ValueRange range(TemporalField field) { + return TemporalAccessor.super.range(field); + } + + /** + * Gets the value of the specified field from this offset as an {@code int}. + *

+ * This queries this offset for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@code OFFSET_SECONDS} field returns the value of the offset. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override // override for Javadoc and performance + public int get(TemporalField field) { + if (field == OFFSET_SECONDS) { + return totalSeconds; + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return range(field).checkValidIntValue(getLong(field), field); + } + + /** + * Gets the value of the specified field from this offset as a {@code long}. + *

+ * This queries this offset for the value for the specified field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@code OFFSET_SECONDS} field returns the value of the offset. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long getLong(TemporalField field) { + if (field == OFFSET_SECONDS) { + return totalSeconds; + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doGet(this); + } + + //----------------------------------------------------------------------- + /** + * Queries this offset using the specified query. + *

+ * This queries this offset using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.offset() || query == Queries.zone()) { + return (R) this; + } + return TemporalAccessor.super.query(query); + } + + /** + * Adjusts the specified temporal object to have the same offset as this object. + *

+ * This returns a temporal object of the same observable type as the input + * with the offset changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * passing {@link ChronoField#OFFSET_SECONDS} as the field. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisOffset.adjustInto(temporal);
+     *   temporal = temporal.with(thisOffset);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal adjustInto(Temporal temporal) { + return temporal.with(OFFSET_SECONDS, totalSeconds); + } + + //----------------------------------------------------------------------- + /** + * Compares this offset to another offset in descending order. + *

+ * 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}. + *

+ * 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. + *

+ * 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; + } + + // ----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(8);  // identifies this as a ZoneOffset
+     *  int offsetByte = totalSeconds % 900 == 0 ? totalSeconds / 900 : 127;
+     *  out.writeByte(offsetByte);
+     *  if (offsetByte == 127) {
+     *    out.writeInt(totalSeconds);
+     *  }
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.ZONE_OFFSET_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + @Override + void write(DataOutput out) throws IOException { + out.writeByte(Ser.ZONE_OFFSET_TYPE); + writeExternal(out); + } + + void writeExternal(DataOutput out) throws IOException { + final int offsetSecs = totalSeconds; + int offsetByte = offsetSecs % 900 == 0 ? offsetSecs / 900 : 127; // compress to -72 to +72 + out.writeByte(offsetByte); + if (offsetByte == 127) { + out.writeInt(offsetSecs); + } + } + + static ZoneOffset readExternal(DataInput in) throws IOException { + int offsetByte = in.readByte(); + return (offsetByte == 127 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds(offsetByte * 900)); + } + +} diff --git a/src/share/classes/java/time/ZoneRegion.java b/src/share/classes/java/time/ZoneRegion.java new file mode 100644 index 0000000000000000000000000000000000000000..ad93e3517900e22aa95203039ac52be804b9df06 --- /dev/null +++ b/src/share/classes/java/time/ZoneRegion.java @@ -0,0 +1,222 @@ +/* + * 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) 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 java.time; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.zone.ZoneRules; +import java.time.zone.ZoneRulesException; +import java.time.zone.ZoneRulesProvider; +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * A geographical region where the same time-zone rules apply. + *

+ * Time-zone information is categorized as a set of rules defining when and + * how the offset from UTC/Greenwich changes. These rules are accessed using + * identifiers based on geographical regions, such as countries or states. + * The most common region classification is the Time Zone Database (TZDB), + * which defines regions such as 'Europe/Paris' and 'Asia/Tokyo'. + *

+ * The region identifier, modeled by this class, is distinct from the + * underlying rules, modeled by {@link ZoneRules}. + * The rules are defined by governments and change frequently. + * By contrast, the region identifier is well-defined and long-lived. + * This separation also allows rules to be shared between regions if appropriate. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +final class ZoneRegion extends ZoneId implements Serializable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = 8386373296231747096L; + /** + * The regex pattern for region IDs. + */ + private static final Pattern PATTERN = Pattern.compile("[A-Za-z][A-Za-z0-9~/._+-]+"); + + /** + * The time-zone ID, not null. + */ + private final String id; + /** + * The time-zone rules, null if zone ID was loaded leniently. + */ + private final transient ZoneRules rules; + + /** + * Obtains an instance of {@code ZoneRegion} from an identifier without checking + * if the time-zone has available rules. + *

+ * This method parses the ID and applies any appropriate normalization. + * It does not validate the ID against the known set of IDsfor which rules are available. + *

+ * This method is intended for advanced use cases. + * For example, consider a system that always retrieves time-zone rules from a remote server. + * Using this factory would allow a {@code ZoneRegion}, and thus a {@code ZonedDateTime}, + * to be created without loading the rules from the remote server. + * + * @param zoneId the time-zone ID, not null + * @return the zone ID, not null + * @throws DateTimeException if the ID format is invalid + */ + private static ZoneRegion ofLenient(String zoneId) { + return ofId(zoneId, false); + } + + /** + * Obtains an instance of {@code ZoneId} from an identifier. + * + * @param zoneId the time-zone ID, not null + * @param checkAvailable whether to check if the zone ID is available + * @return the zone ID, not null + * @throws DateTimeException if the ID format is invalid + * @throws DateTimeException if checking availability and the ID cannot be found + */ + static ZoneRegion ofId(String zoneId, boolean checkAvailable) { + Objects.requireNonNull(zoneId, "zoneId"); + if (zoneId.length() < 2 || zoneId.startsWith("UTC") || + zoneId.startsWith("GMT") || (PATTERN.matcher(zoneId).matches() == false)) { + throw new DateTimeException("ZoneId format is not a valid region format"); + } + ZoneRules rules = null; + try { + // always attempt load for better behavior after deserialization + rules = ZoneRulesProvider.getRules(zoneId); + } catch (ZoneRulesException ex) { + if (checkAvailable) { + throw ex; + } + } + return new ZoneRegion(zoneId, rules); + } + + //------------------------------------------------------------------------- + /** + * Constructor. + * + * @param id the time-zone ID, not null + * @param rules the rules, null for lazy lookup + */ + ZoneRegion(String id, ZoneRules rules) { + this.id = id; + this.rules = rules; + } + + //----------------------------------------------------------------------- + @Override + public String getId() { + return id; + } + + @Override + public ZoneRules getRules() { + // additional query for group provider when null allows for possibility + // that the provider was added after the ZoneId was created + return (rules != null ? rules : ZoneRulesProvider.getRules(id)); + } + + //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(7);  // identifies this as a ZoneId (not ZoneOffset)
+     *  out.writeUTF(zoneId);
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.ZONE_REGION_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + @Override + void write(DataOutput out) throws IOException { + out.writeByte(Ser.ZONE_REGION_TYPE); + writeExternal(out); + } + + void writeExternal(DataOutput out) throws IOException { + out.writeUTF(id); + } + + static ZoneId readExternal(DataInput in) throws IOException { + String id = in.readUTF(); + return ofLenient(id); + } + +} diff --git a/src/share/classes/java/time/ZonedDateTime.java b/src/share/classes/java/time/ZonedDateTime.java new file mode 100644 index 0000000000000000000000000000000000000000..c6c62fdebaace907cf9010d1097823252e4a5192 --- /dev/null +++ b/src/share/classes/java/time/ZonedDateTime.java @@ -0,0 +1,2071 @@ +/* + * 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 java.time; + +import static java.time.temporal.ChronoField.INSTANT_SECONDS; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.OFFSET_SECONDS; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.ChronoZonedDateTime; +import java.time.temporal.ISOChrono; +import java.time.temporal.OffsetDateTime; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.TemporalUnit; +import java.time.temporal.ValueRange; +import java.time.zone.ZoneOffsetTransition; +import java.time.zone.ZoneRules; +import java.util.List; +import java.util.Objects; + +/** + * A date-time with a time-zone in the ISO-8601 calendar system, + * such as {@code 2007-12-03T10:15:30+01:00 Europe/Paris}. + *

+ * {@code ZonedDateTime} is an immutable representation of a date-time with a time-zone. + * This class stores all date and time fields, to a precision of nanoseconds, + * and a time-zone, with a zone offset used to handle ambiguous local date-times. + * For example, the value + * "2nd October 2007 at 13:45.30.123456789 +02:00 in the Europe/Paris time-zone" + * can be stored in a {@code ZonedDateTime}. + *

+ * This class handles conversion from the local time-line of {@code LocalDateTime} + * to the instant time-line of {@code Instant}. + * The difference between the two time-lines is the offset from UTC/Greenwich, + * represented by a {@code ZoneOffset}. + *

+ * Converting between the two time-lines involves calculating the offset using the + * {@link ZoneRules rules} accessed from the {@code ZoneId}. + * Obtaining the offset for an instant is simple, as there is exactly one valid + * offset for each instant. By contrast, obtaining the offset for a local date-time + * is not straightforward. There are three cases: + *

    + *
  • Normal, with one valid offset. For the vast majority of the year, the normal + * case applies, where there is a single valid offset for the local date-time.
  • + *
  • Gap, with zero valid offsets. This is when clocks jump forward typically + * due to the spring daylight savings change from "winter" to "summer". + * In a gap there are local date-time values with no valid offset.
  • + *
  • Overlap, with two valid offsets. This is when clocks are set back typically + * due to the autumn daylight savings change from "summer" to "winter". + * In an overlap there are local date-time values with two valid offsets.
  • + *

+ *

+ * Any method that converts directly or implicitly from a local date-time to an + * instant by obtaining the offset has the potential to be complicated. + *

+ * For Gaps, the general strategy is that if the local date-time falls in the + * middle of a Gap, then the resulting zoned date-time will have a local date-time + * shifted forwards by the length of the Gap, resulting in a date-time in the later + * offset, typically "summer" time. + *

+ * For Overlaps, the general strategy is that if the local date-time falls in the + * middle of an Overlap, then the previous offset will be retained. If there is no + * previous offset, or the previous offset is invalid, then the earlier offset is + * used, typically "summer" time.. Two additional methods, + * {@link #withEarlierOffsetAtOverlap()} and {@link #withLaterOffsetAtOverlap()}, + * help manage the case of an overlap. + * + *

Specification for implementors

+ * A {@code ZonedDateTime} holds state equivalent to three separate objects, + * a {@code LocalDateTime}, a {@code ZoneId} and the resolved {@code ZoneOffset}. + * The offset and local date-time are used to define an instant when necessary. + * The zone ID is used to obtain the rules for how and when the offset changes. + * The offset cannot be freely set, as the zone controls which offsets are valid. + *

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class ZonedDateTime + implements Temporal, ChronoZonedDateTime, Serializable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = -6260982410461394882L; + + /** + * The local date-time. + */ + private final LocalDateTime dateTime; + /** + * The offset from UTC/Greenwich. + */ + private final ZoneOffset offset; + /** + * The time-zone. + */ + private final ZoneId zone; + + //----------------------------------------------------------------------- + /** + * Obtains the current date-time from the system clock in the default time-zone. + *

+ * This will query the {@link Clock#systemDefaultZone() system clock} in the default + * time-zone to obtain the current date-time. + * The zone and offset will be set based on the time-zone in the clock. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @return the current date-time using the system clock, not null + */ + public static ZonedDateTime now() { + return now(Clock.systemDefaultZone()); + } + + /** + * Obtains the current date-time from the system clock in the specified time-zone. + *

+ * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date-time. + * Specifying the time-zone avoids dependence on the default time-zone. + * The offset will be calculated from the specified time-zone. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @param zone the zone ID to use, not null + * @return the current date-time using the system clock, not null + */ + public static ZonedDateTime now(ZoneId zone) { + return now(Clock.system(zone)); + } + + /** + * Obtains the current date-time from the specified clock. + *

+ * This will query the specified clock to obtain the current date-time. + * The zone and offset will be set based on the time-zone in the clock. + *

+ * Using this method allows the use of an alternate clock for testing. + * The alternate clock may be introduced using {@link Clock dependency injection}. + * + * @param clock the clock to use, not null + * @return the current date-time, not null + */ + public static ZonedDateTime now(Clock clock) { + Objects.requireNonNull(clock, "clock"); + final Instant now = clock.instant(); // called once + return ofInstant(now, clock.getZone()); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code ZonedDateTime} from a local date-time. + *

+ * This creates a zoned date-time matching the input local date-time as closely as possible. + * Time-zone rules, such as daylight savings, mean that not every local date-time + * is valid for the specified zone, thus the local date-time may be adjusted. + *

+ * The local date-time is resolved to a single instant on the time-line. + * This is achieved by finding a valid offset from UTC/Greenwich for the local + * date-time as defined by the {@link ZoneRules rules} of the zone ID. + *

+ * In most cases, there is only one valid offset for a local date-time. + * In the case of an overlap, when clocks are set back, there are two valid offsets. + * This method uses the earlier offset typically corresponding to "summer". + *

+ * In the case of a gap, when clocks jump forward, there is no valid offset. + * Instead, the local date-time is adjusted to be later by the length of the gap. + * For a typical one hour daylight savings change, the local date-time will be + * moved one hour later into the offset typically corresponding to "summer". + * + * @param localDateTime the local date-time, not null + * @param zone the time-zone, not null + * @return the zoned date-time, not null + */ + public static ZonedDateTime of(LocalDateTime localDateTime, ZoneId zone) { + return ofLocal(localDateTime, zone, null); + } + + /** + * Obtains an instance of {@code ZonedDateTime} from a local date-time + * using the preferred offset if possible. + *

+ * The local date-time is resolved to a single instant on the time-line. + * This is achieved by finding a valid offset from UTC/Greenwich for the local + * date-time as defined by the {@link ZoneRules rules} of the zone ID. + *

+ * In most cases, there is only one valid offset for a local date-time. + * In the case of an overlap, where clocks are set back, there are two valid offsets. + * If the preferred offset is one of the valid offsets then it is used. + * Otherwise the earlier valid offset is used, typically corresponding to "summer". + *

+ * In the case of a gap, where clocks jump forward, there is no valid offset. + * Instead, the local date-time is adjusted to be later by the length of the gap. + * For a typical one hour daylight savings change, the local date-time will be + * moved one hour later into the offset typically corresponding to "summer". + * + * @param localDateTime the local date-time, not null + * @param zone the time-zone, not null + * @param preferredOffset the zone offset, null if no preference + * @return the zoned date-time, not null + */ + public static ZonedDateTime ofLocal(LocalDateTime localDateTime, ZoneId zone, ZoneOffset preferredOffset) { + Objects.requireNonNull(localDateTime, "localDateTime"); + Objects.requireNonNull(zone, "zone"); + if (zone instanceof ZoneOffset) { + return new ZonedDateTime(localDateTime, (ZoneOffset) zone, zone); + } + ZoneRules rules = zone.getRules(); + List validOffsets = rules.getValidOffsets(localDateTime); + ZoneOffset offset; + if (validOffsets.size() == 1) { + offset = validOffsets.get(0); + } else if (validOffsets.size() == 0) { + ZoneOffsetTransition trans = rules.getTransition(localDateTime); + localDateTime = localDateTime.plusSeconds(trans.getDuration().getSeconds()); + offset = trans.getOffsetAfter(); + } else { + if (preferredOffset != null && validOffsets.contains(preferredOffset)) { + offset = preferredOffset; + } else { + offset = Objects.requireNonNull(validOffsets.get(0), "offset"); // protect against bad ZoneRules + } + } + return new ZonedDateTime(localDateTime, offset, zone); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code ZonedDateTime} from an {@code Instant}. + *

+ * This creates a zoned date-time with the same instant as that specified. + * Calling {@link #toInstant()} will return an instant equal to the one used here. + *

+ * Converting an instant to a zoned date-time is simple as there is only one valid + * offset for each instant. + * + * @param instant the instant to create the date-time from, not null + * @param zone the time-zone, not null + * @return the zoned date-time, not null + * @throws DateTimeException if the result exceeds the supported range + */ + public static ZonedDateTime ofInstant(Instant instant, ZoneId zone) { + Objects.requireNonNull(instant, "instant"); + Objects.requireNonNull(zone, "zone"); + return create(instant.getEpochSecond(), instant.getNano(), zone); + } + + /** + * Obtains an instance of {@code ZonedDateTime} from the instant formed by combining + * the local date-time and offset. + *

+ * This creates a zoned date-time by {@link LocalDateTime#toInstant(ZoneOffset) combining} + * the {@code LocalDateTime} and {@code ZoneOffset}. + * This combination uniquely specifies an instant without ambiguity. + *

+ * Converting an instant to a zoned date-time is simple as there is only one valid + * offset for each instant. If the valid offset is different to the offset specified, + * the the date-time and offset of the zoned date-time will differ from those specified. + *

+ * If the {@code ZoneId} to be used is a {@code ZoneOffset}, this method is equivalent + * to {@link #of(LocalDateTime, ZoneId)}. + * + * @param localDateTime the local date-time, not null + * @param offset the zone offset, not null + * @param zone the time-zone, not null + * @return the zoned date-time, not null + */ + public static ZonedDateTime ofInstant(LocalDateTime localDateTime, ZoneOffset offset, ZoneId zone) { + Objects.requireNonNull(localDateTime, "localDateTime"); + Objects.requireNonNull(offset, "offset"); + Objects.requireNonNull(zone, "zone"); + return create(localDateTime.toEpochSecond(offset), localDateTime.getNano(), zone); + } + + /** + * Obtains an instance of {@code ZonedDateTime} using seconds from the + * epoch of 1970-01-01T00:00:00Z. + * + * @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 zone the time-zone, not null + * @return the zoned date-time, not null + * @throws DateTimeException if the result exceeds the supported range + */ + private static ZonedDateTime create(long epochSecond, int nanoOfSecond, ZoneId zone) { + ZoneRules rules = zone.getRules(); + Instant instant = Instant.ofEpochSecond(epochSecond, nanoOfSecond); // TODO: rules should be queryable by epochSeconds + ZoneOffset offset = rules.getOffset(instant); + LocalDateTime ldt = LocalDateTime.ofEpochSecond(epochSecond, nanoOfSecond, offset); + return new ZonedDateTime(ldt, offset, zone); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code ZonedDateTime} strictly validating the + * combination of local date-time, offset and zone ID. + *

+ * This creates a zoned date-time ensuring that the offset is valid for the + * local date-time according to the rules of the specified zone. + * If the offset is invalid, an exception is thrown. + * + * @param localDateTime the local date-time, not null + * @param offset the zone offset, not null + * @param zone the time-zone, not null + * @return the zoned date-time, not null + */ + public static ZonedDateTime ofStrict(LocalDateTime localDateTime, ZoneOffset offset, ZoneId zone) { + Objects.requireNonNull(localDateTime, "localDateTime"); + Objects.requireNonNull(offset, "offset"); + Objects.requireNonNull(zone, "zone"); + ZoneRules rules = zone.getRules(); + if (rules.isValidOffset(localDateTime, offset) == false) { + ZoneOffsetTransition trans = rules.getTransition(localDateTime); + if (trans != null && trans.isGap()) { + // error message says daylight savings for simplicity + // even though there are other kinds of gaps + throw new DateTimeException("LocalDateTime '" + localDateTime + + "' does not exist in zone '" + zone + + "' due to a gap in the local time-line, typically caused by daylight savings"); + } + throw new DateTimeException("ZoneOffset '" + offset + "' is not valid for LocalDateTime '" + + localDateTime + "' in zone '" + zone + "'"); + } + return new ZonedDateTime(localDateTime, offset, zone); + } + + /** + * Obtains an instance of {@code ZonedDateTime} leniently, for advanced use cases, + * allowing any combination of local date-time, offset and zone ID. + *

+ * This creates a zoned date-time with no checks other than no nulls. + * This means that the resulting zoned date-time may have an offset that is in conflict + * with the zone ID. + *

+ * This method is intended for advanced use cases. + * For example, consider the case where a zoned date-time with valid fields is created + * and then stored in a database or serialization-based store. At some later point, + * the object is then re-loaded. However, between those points in time, the government + * that defined the time-zone has changed the rules, such that the originally stored + * local date-time now does not occur. This method can be used to create the object + * in an "invalid" state, despite the change in rules. + * + * @param localDateTime the local date-time, not null + * @param offset the zone offset, not null + * @param zone the time-zone, not null + * @return the zoned date-time, not null + */ + private static ZonedDateTime ofLenient(LocalDateTime localDateTime, ZoneOffset offset, ZoneId zone) { + Objects.requireNonNull(localDateTime, "localDateTime"); + Objects.requireNonNull(offset, "offset"); + Objects.requireNonNull(zone, "zone"); + if (zone instanceof ZoneOffset && offset.equals(zone) == false) { + throw new IllegalArgumentException("ZoneId must match ZoneOffset"); + } + return new ZonedDateTime(localDateTime, offset, zone); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code ZonedDateTime} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code ZonedDateTime}. + *

+ * The conversion will first obtain a {@code ZoneId}. It will then try to obtain an instant. + * If that fails it will try to obtain a local date-time. + * The zoned date time will either be a combination of {@code ZoneId} and instant, + * or {@code ZoneId} and local date-time. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used in queries via method reference, {@code ZonedDateTime::from}. + * + * @param temporal the temporal object to convert, not null + * @return the zoned date-time, not null + * @throws DateTimeException if unable to convert to an {@code ZonedDateTime} + */ + public static ZonedDateTime from(TemporalAccessor temporal) { + if (temporal instanceof ZonedDateTime) { + return (ZonedDateTime) temporal; + } + try { + ZoneId zone = ZoneId.from(temporal); + try { + long epochSecond = temporal.getLong(INSTANT_SECONDS); + int nanoOfSecond = temporal.get(NANO_OF_SECOND); + return create(epochSecond, nanoOfSecond, zone); + + } catch (DateTimeException ex1) { + LocalDateTime ldt = LocalDateTime.from(temporal); + return of(ldt, zone); + } + } catch (DateTimeException ex) { + throw new DateTimeException("Unable to create ZonedDateTime from TemporalAccessor: " + temporal.getClass(), ex); + } + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code ZonedDateTime} from a text string such as + * {@code 2007-12-03T10:15:30+01:00[Europe/Paris]}. + *

+ * The string must represent a valid date-time and is parsed using + * {@link java.time.format.DateTimeFormatters#isoZonedDateTime()}. + * + * @param text the text to parse such as "2007-12-03T10:15:30+01:00[Europe/Paris]", not null + * @return the parsed zoned date-time, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static ZonedDateTime parse(CharSequence text) { + return parse(text, DateTimeFormatters.isoZonedDateTime()); + } + + /** + * Obtains an instance of {@code ZonedDateTime} from a text string using a specific formatter. + *

+ * The text is parsed using the formatter, returning a date-time. + * + * @param text the text to parse, not null + * @param formatter the formatter to use, not null + * @return the parsed zoned date-time, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static ZonedDateTime parse(CharSequence text, DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.parse(text, ZonedDateTime::from); + } + + //----------------------------------------------------------------------- + /** + * Constructor. + * + * @param dateTime the date-time, validated as not null + * @param offset the zone offset, validated as not null + * @param zone the time-zone, validated as not null + */ + private ZonedDateTime(LocalDateTime dateTime, ZoneOffset offset, ZoneId zone) { + this.dateTime = dateTime; + this.offset = offset; + this.zone = zone; + } + + /** + * Resolves the new local date-time using this zone ID, retaining the offset if possible. + * + * @param newDateTime the new local date-time, not null + * @return the zoned date-time, not null + */ + private ZonedDateTime resolveLocal(LocalDateTime newDateTime) { + return ofLocal(newDateTime, zone, offset); + } + + /** + * Resolves the new local date-time using the offset to identify the instant. + * + * @param newDateTime the new local date-time, not null + * @return the zoned date-time, not null + */ + private ZonedDateTime resolveInstant(LocalDateTime newDateTime) { + return ofInstant(newDateTime, offset, zone); + } + + /** + * Resolves the offset into this zoned date-time. + *

+ * This will use the new offset to find the instant, which is then looked up + * using the zone ID to find the actual offset to use. + * + * @param offset the offset, not null + * @return the zoned date-time, not null + */ + private ZonedDateTime resolveOffset(ZoneOffset offset) { + long epSec = dateTime.toEpochSecond(offset); + return create(epSec, dateTime.getNano(), zone); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this date-time can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and + * {@link #get(TemporalField) get} methods will throw an exception. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The supported fields are: + *

    + *
  • {@code NANO_OF_SECOND} + *
  • {@code NANO_OF_DAY} + *
  • {@code MICRO_OF_SECOND} + *
  • {@code MICRO_OF_DAY} + *
  • {@code MILLI_OF_SECOND} + *
  • {@code MILLI_OF_DAY} + *
  • {@code SECOND_OF_MINUTE} + *
  • {@code SECOND_OF_DAY} + *
  • {@code MINUTE_OF_HOUR} + *
  • {@code MINUTE_OF_DAY} + *
  • {@code HOUR_OF_AMPM} + *
  • {@code CLOCK_HOUR_OF_AMPM} + *
  • {@code HOUR_OF_DAY} + *
  • {@code CLOCK_HOUR_OF_DAY} + *
  • {@code AMPM_OF_DAY} + *
  • {@code DAY_OF_WEEK} + *
  • {@code ALIGNED_DAY_OF_WEEK_IN_MONTH} + *
  • {@code ALIGNED_DAY_OF_WEEK_IN_YEAR} + *
  • {@code DAY_OF_MONTH} + *
  • {@code DAY_OF_YEAR} + *
  • {@code EPOCH_DAY} + *
  • {@code ALIGNED_WEEK_OF_MONTH} + *
  • {@code ALIGNED_WEEK_OF_YEAR} + *
  • {@code MONTH_OF_YEAR} + *
  • {@code EPOCH_MONTH} + *
  • {@code YEAR_OF_ERA} + *
  • {@code YEAR} + *
  • {@code ERA} + *
  • {@code INSTANT_SECONDS} + *
  • {@code OFFSET_SECONDS} + *
+ * All other {@code ChronoField} instances will return false. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the field is supported is determined by the field. + * + * @param field the field to check, null returns false + * @return true if the field is supported on this date-time, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + return field instanceof ChronoField || (field != null && field.doIsSupported(this)); + } + + /** + * Gets the range of valid values for the specified field. + *

+ * The range object expresses the minimum and maximum valid values for a field. + * This date-time is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return + * appropriate range instances. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + @Override + public ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + if (field == INSTANT_SECONDS || field == OFFSET_SECONDS) { + return field.range(); + } + return dateTime.range(field); + } + return field.doRange(this); + } + + /** + * Gets the value of the specified field from this date-time as an {@code int}. + *

+ * This queries this date-time for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date-time, except {@code NANO_OF_DAY}, {@code MICRO_OF_DAY}, + * {@code EPOCH_DAY}, {@code EPOCH_MONTH} and {@code INSTANT_SECONDS} which are too + * large to fit in an {@code int} and throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override // override for Javadoc and performance + public int get(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case INSTANT_SECONDS: throw new DateTimeException("Field too large for an int: " + field); + case OFFSET_SECONDS: return getOffset().getTotalSeconds(); + } + return dateTime.get(field); + } + return ChronoZonedDateTime.super.get(field); + } + + /** + * Gets the value of the specified field from this date-time as a {@code long}. + *

+ * This queries this date-time for the value for the specified field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date-time. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case INSTANT_SECONDS: return toEpochSecond(); + case OFFSET_SECONDS: return getOffset().getTotalSeconds(); + } + return dateTime.getLong(field); + } + return field.doGet(this); + } + + //----------------------------------------------------------------------- + /** + * Gets the zone offset, such as '+01:00'. + *

+ * This is the offset of the local date-time from UTC/Greenwich. + * + * @return the zone offset, not null + */ + @Override + public ZoneOffset getOffset() { + return offset; + } + + /** + * Returns a copy of this date-time changing the zone offset to the + * earlier of the two valid offsets at a local time-line overlap. + *

+ * This method only has any effect when the local time-line overlaps, such as + * at an autumn daylight savings cutover. In this scenario, there are two + * valid offsets for the local date-time. Calling this method will return + * a zoned date-time with the earlier of the two selected. + *

+ * If this method is called when it is not an overlap, {@code this} + * is returned. + *

+ * This instance is immutable and unaffected by this method call. + * + * @return a {@code ZonedDateTime} based on this date-time with the earlier offset, not null + */ + @Override + public ZonedDateTime withEarlierOffsetAtOverlap() { + ZoneOffsetTransition trans = getZone().getRules().getTransition(dateTime); + if (trans != null && trans.isOverlap()) { + ZoneOffset earlierOffset = trans.getOffsetBefore(); + if (earlierOffset.equals(offset) == false) { + return new ZonedDateTime(dateTime, earlierOffset, zone); + } + } + return this; + } + + /** + * Returns a copy of this date-time changing the zone offset to the + * later of the two valid offsets at a local time-line overlap. + *

+ * This method only has any effect when the local time-line overlaps, such as + * at an autumn daylight savings cutover. In this scenario, there are two + * valid offsets for the local date-time. Calling this method will return + * a zoned date-time with the later of the two selected. + *

+ * If this method is called when it is not an overlap, {@code this} + * is returned. + *

+ * This instance is immutable and unaffected by this method call. + * + * @return a {@code ZonedDateTime} based on this date-time with the later offset, not null + */ + @Override + public ZonedDateTime withLaterOffsetAtOverlap() { + ZoneOffsetTransition trans = getZone().getRules().getTransition(getDateTime()); + if (trans != null) { + ZoneOffset laterOffset = trans.getOffsetAfter(); + if (laterOffset.equals(offset) == false) { + return new ZonedDateTime(dateTime, laterOffset, zone); + } + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Gets the time-zone, such as 'Europe/Paris'. + *

+ * This returns the zone ID. This identifies the time-zone {@link ZoneRules rules} + * that determine when and how the offset from UTC/Greenwich changes. + *

+ * The zone ID may be same as the {@linkplain #getOffset() offset}. + * If this is true, then any future calculations, such as addition or subtraction, + * have no complex edge cases due to time-zone rules. + * See also {@link #withFixedOffsetZone()}. + * + * @return the time-zone, not null + */ + @Override + public ZoneId getZone() { + return zone; + } + + /** + * Returns a copy of this date-time with a different time-zone, + * retaining the local date-time if possible. + *

+ * This method changes the time-zone and retains the local date-time. + * The local date-time is only changed if it is invalid for the new zone, + * determined using the same approach as + * {@link #ofLocal(LocalDateTime, ZoneId, ZoneOffset)}. + *

+ * To change the zone and adjust the local date-time, + * use {@link #withZoneSameInstant(ZoneId)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param zone the time-zone to change to, not null + * @return a {@code ZonedDateTime} based on this date-time with the requested zone, not null + */ + @Override + public ZonedDateTime withZoneSameLocal(ZoneId zone) { + Objects.requireNonNull(zone, "zone"); + return this.zone.equals(zone) ? this : ofLocal(dateTime, zone, offset); + } + + /** + * Returns a copy of this date-time with a different time-zone, + * retaining the instant. + *

+ * This method changes the time-zone and retains the instant. + * This normally results in a change to the local date-time. + *

+ * This method is based on retaining the same instant, thus gaps and overlaps + * in the local time-line have no effect on the result. + *

+ * To change the offset while keeping the local time, + * use {@link #withZoneSameLocal(ZoneId)}. + * + * @param zone the time-zone to change to, not null + * @return a {@code ZonedDateTime} based on this date-time with the requested zone, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + @Override + public ZonedDateTime withZoneSameInstant(ZoneId zone) { + Objects.requireNonNull(zone, "zone"); + return this.zone.equals(zone) ? this : + create(dateTime.toEpochSecond(offset), dateTime.getNano(), zone); + } + + /** + * Returns a copy of this date-time with the zone ID set to the offset. + *

+ * This returns a zoned date-time where the zone ID is the same as {@link #getOffset()}. + * The local date-time, offset and instant of the result will be the same as in this date-time. + *

+ * Setting the date-time to a fixed single offset means that any future + * calculations, such as addition or subtraction, have no complex edge cases + * due to time-zone rules. + * This might also be useful when sending a zoned date-time across a network, + * as most protocols, such as ISO-8601, only handle offsets, + * and not region-based zone IDs. + *

+ * This is equivalent to {@code ZonedDateTime.of(zdt.getDateTime(), zdt.getOffset())}. + * + * @return a {@code ZonedDateTime} with the zone ID set to the offset, not null + */ + public ZonedDateTime withFixedOffsetZone() { + return this.zone.equals(offset) ? this : new ZonedDateTime(dateTime, offset, offset); + } + + //----------------------------------------------------------------------- + /** + * Gets the {@code LocalDateTime} part of this date-time. + *

+ * This returns a {@code LocalDateTime} with the same year, month, day and time + * as this date-time. + * + * @return the local date-time part of this date-time, not null + */ + @Override // override for return type + public LocalDateTime getDateTime() { + return dateTime; + } + + //----------------------------------------------------------------------- + /** + * Gets the {@code LocalDate} part of this date-time. + *

+ * 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 + */ + @Override // override for return type + public LocalDate getDate() { + return dateTime.getDate(); + } + + /** + * Gets the year field. + *

+ * This method returns the primitive {@code int} value for the year. + *

+ * 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 dateTime.getYear(); + } + + /** + * Gets the month-of-year field from 1 to 12. + *

+ * This method returns the month as an {@code int} from 1 to 12. + * Application code is frequently clearer if the enum {@link Month} + * is used by calling {@link #getMonth()}. + * + * @return the month-of-year, from 1 to 12 + * @see #getMonth() + */ + public int getMonthValue() { + return dateTime.getMonthValue(); + } + + /** + * Gets the month-of-year field using the {@code Month} enum. + *

+ * This method returns the enum {@link Month} for the month. + * This avoids confusion as to what {@code int} values mean. + * If you need access to the primitive {@code int} value then the enum + * provides the {@link Month#getValue() int value}. + * + * @return the month-of-year, not null + * @see #getMonthValue() + */ + public Month getMonth() { + return dateTime.getMonth(); + } + + /** + * Gets the day-of-month field. + *

+ * 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 dateTime.getDayOfMonth(); + } + + /** + * Gets the day-of-year field. + *

+ * This method returns the primitive {@code int} value for the day-of-year. + * + * @return the day-of-year, from 1 to 365, or 366 in a leap year + */ + public int getDayOfYear() { + return dateTime.getDayOfYear(); + } + + /** + * Gets the day-of-week field, which is an enum {@code DayOfWeek}. + *

+ * This method returns the enum {@link DayOfWeek} for the day-of-week. + * This avoids confusion as to what {@code int} values mean. + * If you need access to the primitive {@code int} value then the enum + * provides the {@link DayOfWeek#getValue() int value}. + *

+ * Additional information can be obtained from the {@code DayOfWeek}. + * This includes textual names of the values. + * + * @return the day-of-week, not null + */ + public DayOfWeek getDayOfWeek() { + return dateTime.getDayOfWeek(); + } + + //----------------------------------------------------------------------- + /** + * Gets the {@code LocalTime} part of this date-time. + *

+ * 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 + */ + @Override // override for Javadoc and performance + public LocalTime getTime() { + return dateTime.getTime(); + } + + /** + * Gets the hour-of-day field. + * + * @return the hour-of-day, from 0 to 23 + */ + public int getHour() { + return dateTime.getHour(); + } + + /** + * Gets the minute-of-hour field. + * + * @return the minute-of-hour, from 0 to 59 + */ + public int getMinute() { + return dateTime.getMinute(); + } + + /** + * Gets the second-of-minute field. + * + * @return the second-of-minute, from 0 to 59 + */ + public int getSecond() { + return dateTime.getSecond(); + } + + /** + * Gets the nano-of-second field. + * + * @return the nano-of-second, from 0 to 999,999,999 + */ + public int getNano() { + return dateTime.getNano(); + } + + //----------------------------------------------------------------------- + /** + * Returns an adjusted copy of this date-time. + *

+ * This returns a new {@code ZonedDateTime}, based on this one, with the date-time adjusted. + * The adjustment takes place using the specified adjuster strategy object. + * Read the documentation of the adjuster to understand what adjustment will be made. + *

+ * A simple adjuster might simply set the one of the fields, such as the year field. + * A more complex adjuster might set the date to the last day of the month. + * A selection of common adjustments is provided in {@link java.time.temporal.Adjusters}. + * These include finding the "last day of the month" and "next Wednesday". + * Key date-time classes also implement the {@code TemporalAdjuster} interface, + * such as {@link Month} and {@link java.time.temporal.MonthDay MonthDay}. + * The adjuster is responsible for handling special cases, such as the varying + * lengths of month and leap years. + *

+ * For example this code returns a date on the last day of July: + *

+     *  import static java.time.Month.*;
+     *  import static java.time.temporal.Adjusters.*;
+     *
+     *  result = zonedDateTime.with(JULY).with(lastDayOfMonth());
+     * 
+ *

+ * The classes {@link LocalDate} and {@link LocalTime} implement {@code TemporalAdjuster}, + * thus this method can be used to change the date, time or offset: + *

+     *  result = zonedDateTime.with(date);
+     *  result = zonedDateTime.with(time);
+     * 
+ *

+ * {@link ZoneOffset} also implements {@code TemporalAdjuster} however it is less likely + * that setting the offset will have the effect you expect. When an offset is passed in, + * the local date-time is combined with the new offset to form an {@code Instant}. + * The instant and original zone are then used to create the result. + * This algorithm means that it is quite likely that the output has a different offset + * to the specified offset. It will however work correctly when passing in the offset + * applicable for the instant of the zoned date-time, and will work correctly if passing + * one of the two valid offsets during a daylight savings overlap when the same local time + * occurs twice. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalAdjuster#adjustInto(Temporal)} method on the + * specified adjuster passing {@code this} as the argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adjuster the adjuster to use, not null + * @return a {@code ZonedDateTime} based on {@code this} with the adjustment made, not null + * @throws DateTimeException if the adjustment cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public ZonedDateTime with(TemporalAdjuster adjuster) { + // optimizations + if (adjuster instanceof LocalDate) { + return resolveLocal(LocalDateTime.of((LocalDate) adjuster, dateTime.getTime())); + } else if (adjuster instanceof LocalTime) { + return resolveLocal(LocalDateTime.of(dateTime.getDate(), (LocalTime) adjuster)); + } else if (adjuster instanceof LocalDateTime) { + return resolveLocal((LocalDateTime) adjuster); + } else if (adjuster instanceof Instant) { + Instant instant = (Instant) adjuster; + return create(instant.getEpochSecond(), instant.getNano(), zone); + } else if (adjuster instanceof ZoneOffset) { + return resolveOffset((ZoneOffset) adjuster); + } + return (ZonedDateTime) adjuster.adjustInto(this); + } + + /** + * Returns a copy of this date-time with the specified field set to a new value. + *

+ * This returns a new {@code ZonedDateTime}, based on this one, with the value + * for the specified field changed. + * This can be used to change any supported field, such as the year, month or day-of-month. + * If it is not possible to set the value, because the field is not supported or for + * some other reason, an exception is thrown. + *

+ * In some cases, changing the specified field can cause the resulting date-time to become invalid, + * such as changing the month from 31st January to February would make the day-of-month invalid. + * In cases like this, the field is responsible for resolving the date. Typically it will choose + * the previous valid date, which would be the last valid day of February in this example. + *

+ * If the field is a {@link ChronoField} then the adjustment is implemented here. + *

+ * The {@code INSTANT_SECONDS} field will return a date-time with the specified instant. + * The zone and nano-of-second are unchanged. + * The result will have an offset derived from the new instant and original zone. + * If the new instant value is outside the valid range then a {@code DateTimeException} will be thrown. + *

+ * The {@code OFFSET_SECONDS} field will return a date-time calculated using the specified offset. + * The local date-time is combined with the new offset to form an {@code Instant}. + * The instant and original zone are then used to create the result. + * This algorithm means that it is quite likely that the output has a different offset + * to the specified offset. It will however work correctly when passing in the offset + * applicable for the instant of the zoned date-time, and will work correctly if passing + * one of the two valid offsets during a daylight savings overlap when the same local time + * occurs twice. If the new offset value is outside the valid range then a + * {@code DateTimeException} will be thrown. + *

+ * The other {@link #isSupported(TemporalField) supported fields} will behave as per + * the matching method on {@link LocalDateTime#with(TemporalField, long) LocalDateTime}. + * The zone is not part of the calculation and will be unchanged. + * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doWith(Temporal, long)} + * passing {@code this} as the argument. In this case, the field determines + * whether and how to adjust the instant. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param field the field to set in the result, not null + * @param newValue the new value of the field in the result + * @return a {@code ZonedDateTime} based on {@code this} with the specified field set, not null + * @throws DateTimeException if the field cannot be set + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public ZonedDateTime with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + switch (f) { + case INSTANT_SECONDS: return create(newValue, getNano(), zone); + case OFFSET_SECONDS: { + ZoneOffset offset = ZoneOffset.ofTotalSeconds(f.checkValidIntValue(newValue)); + return resolveOffset(offset); + } + } + return resolveLocal(dateTime.with(field, newValue)); + } + return field.doWith(this, newValue); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code ZonedDateTime} with the year value altered. + *

+ * This operates on the local time-line, + * {@link LocalDateTime#withYear(int) changing the year} of the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param year the year to set in the result, from MIN_YEAR to MAX_YEAR + * @return a {@code ZonedDateTime} based on this date-time with the requested year, not null + * @throws DateTimeException if the year value is invalid + */ + public ZonedDateTime withYear(int year) { + return resolveLocal(dateTime.withYear(year)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the month-of-year value altered. + *

+ * This operates on the local time-line, + * {@link LocalDateTime#withMonth(int) changing the month} of the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param month the month-of-year to set in the result, from 1 (January) to 12 (December) + * @return a {@code ZonedDateTime} based on this date-time with the requested month, not null + * @throws DateTimeException if the month-of-year value is invalid + */ + public ZonedDateTime withMonth(int month) { + return resolveLocal(dateTime.withMonth(month)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the day-of-month value altered. + *

+ * This operates on the local time-line, + * {@link LocalDateTime#withDayOfMonth(int) changing the day-of-month} of the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param dayOfMonth the day-of-month to set in the result, from 1 to 28-31 + * @return a {@code ZonedDateTime} based on this date-time with the requested day, not null + * @throws DateTimeException if the day-of-month value is invalid + * @throws DateTimeException if the day-of-month is invalid for the month-year + */ + public ZonedDateTime withDayOfMonth(int dayOfMonth) { + return resolveLocal(dateTime.withDayOfMonth(dayOfMonth)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the day-of-year altered. + *

+ * This operates on the local time-line, + * {@link LocalDateTime#withDayOfYear(int) changing the day-of-year} of the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param dayOfYear the day-of-year to set in the result, from 1 to 365-366 + * @return a {@code ZonedDateTime} based on this date with the requested day, not null + * @throws DateTimeException if the day-of-year value is invalid + * @throws DateTimeException if the day-of-year is invalid for the year + */ + public ZonedDateTime withDayOfYear(int dayOfYear) { + return resolveLocal(dateTime.withDayOfYear(dayOfYear)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code ZonedDateTime} with the hour-of-day value altered. + *

+ * This operates on the local time-line, + * {@linkplain LocalDateTime#withHour(int) changing the time} of the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hour the hour-of-day to set in the result, from 0 to 23 + * @return a {@code ZonedDateTime} based on this date-time with the requested hour, not null + * @throws DateTimeException if the hour value is invalid + */ + public ZonedDateTime withHour(int hour) { + return resolveLocal(dateTime.withHour(hour)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the minute-of-hour value altered. + *

+ * This operates on the local time-line, + * {@linkplain LocalDateTime#withMinute(int) changing the time} of the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param minute the minute-of-hour to set in the result, from 0 to 59 + * @return a {@code ZonedDateTime} based on this date-time with the requested minute, not null + * @throws DateTimeException if the minute value is invalid + */ + public ZonedDateTime withMinute(int minute) { + return resolveLocal(dateTime.withMinute(minute)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the second-of-minute value altered. + *

+ * This operates on the local time-line, + * {@linkplain LocalDateTime#withSecond(int) changing the time} of the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param second the second-of-minute to set in the result, from 0 to 59 + * @return a {@code ZonedDateTime} based on this date-time with the requested second, not null + * @throws DateTimeException if the second value is invalid + */ + public ZonedDateTime withSecond(int second) { + return resolveLocal(dateTime.withSecond(second)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the nano-of-second value altered. + *

+ * This operates on the local time-line, + * {@linkplain LocalDateTime#withNano(int) changing the time} of the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanoOfSecond the nano-of-second to set in the result, from 0 to 999,999,999 + * @return a {@code ZonedDateTime} based on this date-time with the requested nanosecond, not null + * @throws DateTimeException if the nano value is invalid + */ + public ZonedDateTime withNano(int nanoOfSecond) { + return resolveLocal(dateTime.withNano(nanoOfSecond)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code ZonedDateTime} with the time truncated. + *

+ * Truncation returns a copy of the original date-time with fields + * smaller than the specified unit set to zero. + * For example, truncating with the {@link ChronoUnit#MINUTES minutes} unit + * will set the second-of-minute and nano-of-second field to zero. + *

+ * Not all units are accepted. The {@link ChronoUnit#DAYS days} unit and time + * units with an exact duration can be used, other units throw an exception. + *

+ * This operates on the local time-line, + * {@link LocalDateTime#truncatedTo(java.time.temporal.TemporalUnit) truncating} + * the underlying local date-time. This is then converted back to a + * {@code ZonedDateTime}, using the zone ID to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param unit the unit to truncate to, not null + * @return a {@code ZonedDateTime} based on this date-time with the time truncated, not null + * @throws DateTimeException if unable to truncate + */ + public ZonedDateTime truncatedTo(TemporalUnit unit) { + return resolveLocal(dateTime.truncatedTo(unit)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date-time with the specified period added. + *

+ * This method returns a new date-time based on this time with the specified period added. + * The adder is typically {@link Period} but may be any other type implementing + * the {@link TemporalAdder} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #plus(long, TemporalUnit)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adder the adder to use, not null + * @return a {@code ZonedDateTime} based on this date-time with the addition made, not null + * @throws DateTimeException if the addition cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public ZonedDateTime plus(TemporalAdder adder) { + return (ZonedDateTime) adder.addTo(this); + } + + /** + * Returns a copy of this date-time with the specified period added. + *

+ * This method returns a new date-time based on this date-time with the specified period added. + * This can be used to add any period that is defined by a unit, for example to add years, months or days. + * The unit is responsible for the details of the calculation, including the resolution + * of any edge cases in the calculation. + *

+ * The calculation for date and time units differ. + *

+ * Date units operate on the local time-line. + * The period is first added to the local date-time, then converted back + * to a zoned date-time using the zone ID. + * The conversion uses {@link #ofLocal(LocalDateTime, ZoneId, ZoneOffset)} + * with the offset before the addition. + *

+ * Time units operate on the instant time-line. + * The period is first added to the local date-time, then converted back to + * a zoned date-time using the zone ID. + * The conversion uses {@link #ofInstant(LocalDateTime, ZoneOffset, ZoneId)} + * with the offset before the addition. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToAdd the amount of the unit to add to the result, may be negative + * @param unit the unit of the period to add, not null + * @return a {@code ZonedDateTime} based on this date-time with the specified period added, not null + * @throws DateTimeException if the unit cannot be added to this type + */ + @Override + public ZonedDateTime plus(long amountToAdd, TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + ChronoUnit u = (ChronoUnit) unit; + if (u.isDateUnit()) { + return resolveLocal(dateTime.plus(amountToAdd, unit)); + } else { + return resolveInstant(dateTime.plus(amountToAdd, unit)); + } + } + return unit.doPlus(this, amountToAdd); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in years added. + *

+ * This operates on the local time-line, + * {@link LocalDateTime#plusYears(long) adding years} to the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param years the years to add, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the years added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime plusYears(long years) { + return resolveLocal(dateTime.plusYears(years)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in months added. + *

+ * This operates on the local time-line, + * {@link LocalDateTime#plusMonths(long) adding months} to the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param months the months to add, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the months added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime plusMonths(long months) { + return resolveLocal(dateTime.plusMonths(months)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in weeks added. + *

+ * This operates on the local time-line, + * {@link LocalDateTime#plusWeeks(long) adding weeks} to the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param weeks the weeks to add, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the weeks added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime plusWeeks(long weeks) { + return resolveLocal(dateTime.plusWeeks(weeks)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in days added. + *

+ * This operates on the local time-line, + * {@link LocalDateTime#plusDays(long) adding days} to the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param days the days to add, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the days added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime plusDays(long days) { + return resolveLocal(dateTime.plusDays(days)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in hours added. + *

+ * This operates on the instant time-line, such that adding one hour will + * always be a duration of one hour later. + * This may cause the local date-time to change by an amount other than one hour. + * Note that this is a different approach to that used by days, months and years, + * thus adding one day is not the same as adding 24 hours. + *

+ * For example, consider a time-zone where the spring DST cutover means that the + * local times 01:00 to 01:59 occur twice changing from offset +02:00 to +01:00. + *

    + *
  • Adding one hour to 00:30+02:00 will result in 01:30+02:00 + *
  • Adding one hour to 01:30+02:00 will result in 01:30+01:00 + *
  • Adding one hour to 01:30+01:00 will result in 02:30+01:00 + *
  • Adding three hours to 00:30+02:00 will result in 02:30+01:00 + *

+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param hours the hours to add, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the hours added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime plusHours(long hours) { + return resolveInstant(dateTime.plusHours(hours)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in minutes added. + *

+ * This operates on the instant time-line, such that adding one minute will + * always be a duration of one minute later. + * This may cause the local date-time to change by an amount other than one minute. + * Note that this is a different approach to that used by days, months and years. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param minutes the minutes to add, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the minutes added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime plusMinutes(long minutes) { + return resolveInstant(dateTime.plusMinutes(minutes)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in seconds added. + *

+ * This operates on the instant time-line, such that adding one second will + * always be a duration of one second later. + * This may cause the local date-time to change by an amount other than one second. + * Note that this is a different approach to that used by days, months and years. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param seconds the seconds to add, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the seconds added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime plusSeconds(long seconds) { + return resolveInstant(dateTime.plusSeconds(seconds)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in nanoseconds added. + *

+ * This operates on the instant time-line, such that adding one nano will + * always be a duration of one nano later. + * This may cause the local date-time to change by an amount other than one nano. + * Note that this is a different approach to that used by days, months and years. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanos the nanos to add, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the nanoseconds added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime plusNanos(long nanos) { + return resolveInstant(dateTime.plusNanos(nanos)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date-time with the specified period subtracted. + *

+ * This method returns a new date-time based on this time with the specified period subtracted. + * The subtractor is typically {@link Period} but may be any other type implementing + * the {@link TemporalSubtractor} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #minus(long, TemporalUnit)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param subtractor the subtractor to use, not null + * @return a {@code ZonedDateTime} based on this date-time with the subtraction made, not null + * @throws DateTimeException if the subtraction cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public ZonedDateTime minus(TemporalSubtractor subtractor) { + return (ZonedDateTime) subtractor.subtractFrom(this); + } + + /** + * Returns a copy of this date-time with the specified period subtracted. + *

+ * This method returns a new date-time based on this date-time with the specified period subtracted. + * This can be used to subtract any period that is defined by a unit, for example to subtract years, months or days. + * The unit is responsible for the details of the calculation, including the resolution + * of any edge cases in the calculation. + *

+ * The calculation for date and time units differ. + *

+ * Date units operate on the local time-line. + * The period is first subtracted from the local date-time, then converted back + * to a zoned date-time using the zone ID. + * The conversion uses {@link #ofLocal(LocalDateTime, ZoneId, ZoneOffset)} + * with the offset before the subtraction. + *

+ * Time units operate on the instant time-line. + * The period is first subtracted from the local date-time, then converted back to + * a zoned date-time using the zone ID. + * The conversion uses {@link #ofInstant(LocalDateTime, ZoneOffset, ZoneId)} + * with the offset before the subtraction. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToSubtract the amount of the unit to subtract from the result, may be negative + * @param unit the unit of the period to subtract, not null + * @return a {@code ZonedDateTime} based on this date-time with the specified period subtracted, not null + * @throws DateTimeException if the unit cannot be added to this type + */ + @Override + public ZonedDateTime minus(long amountToSubtract, TemporalUnit unit) { + return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in years subtracted. + *

+ * This operates on the local time-line, + * {@link LocalDateTime#minusYears(long) subtracting years} to the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param years the years to subtract, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the years subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime minusYears(long years) { + return (years == Long.MIN_VALUE ? plusYears(Long.MAX_VALUE).plusYears(1) : plusYears(-years)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in months subtracted. + *

+ * This operates on the local time-line, + * {@link LocalDateTime#minusMonths(long) subtracting months} to the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param months the months to subtract, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the months subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime minusMonths(long months) { + return (months == Long.MIN_VALUE ? plusMonths(Long.MAX_VALUE).plusMonths(1) : plusMonths(-months)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in weeks subtracted. + *

+ * This operates on the local time-line, + * {@link LocalDateTime#minusWeeks(long) subtracting weeks} to the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param weeks the weeks to subtract, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the weeks subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime minusWeeks(long weeks) { + return (weeks == Long.MIN_VALUE ? plusWeeks(Long.MAX_VALUE).plusWeeks(1) : plusWeeks(-weeks)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in days subtracted. + *

+ * This operates on the local time-line, + * {@link LocalDateTime#minusDays(long) subtracting days} to the local date-time. + * This is then converted back to a {@code ZonedDateTime}, using the zone ID + * to obtain the offset. + *

+ * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, + * then the offset will be retained if possible, otherwise the earlier offset will be used. + * If in a gap, the local date-time will be adjusted forward by the length of the gap. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param days the days to subtract, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the days subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime minusDays(long days) { + return (days == Long.MIN_VALUE ? plusDays(Long.MAX_VALUE).plusDays(1) : plusDays(-days)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in hours subtracted. + *

+ * This operates on the instant time-line, such that subtracting one hour will + * always be a duration of one hour earlier. + * This may cause the local date-time to change by an amount other than one hour. + * Note that this is a different approach to that used by days, months and years, + * thus subtracting one day is not the same as adding 24 hours. + *

+ * For example, consider a time-zone where the spring DST cutover means that the + * local times 01:00 to 01:59 occur twice changing from offset +02:00 to +01:00. + *

    + *
  • Subtracting one hour from 02:30+01:00 will result in 01:30+02:00 + *
  • Subtracting one hour from 01:30+01:00 will result in 01:30+02:00 + *
  • Subtracting one hour from 01:30+02:00 will result in 00:30+01:00 + *
  • Subtracting three hours from 02:30+01:00 will result in 00:30+02:00 + *

+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param hours the hours to subtract, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the hours subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime minusHours(long hours) { + return (hours == Long.MIN_VALUE ? plusHours(Long.MAX_VALUE).plusHours(1) : plusHours(-hours)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in minutes subtracted. + *

+ * This operates on the instant time-line, such that subtracting one minute will + * always be a duration of one minute earlier. + * This may cause the local date-time to change by an amount other than one minute. + * Note that this is a different approach to that used by days, months and years. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param minutes the minutes to subtract, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the minutes subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime minusMinutes(long minutes) { + return (minutes == Long.MIN_VALUE ? plusMinutes(Long.MAX_VALUE).plusMinutes(1) : plusMinutes(-minutes)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in seconds subtracted. + *

+ * This operates on the instant time-line, such that subtracting one second will + * always be a duration of one second earlier. + * This may cause the local date-time to change by an amount other than one second. + * Note that this is a different approach to that used by days, months and years. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param seconds the seconds to subtract, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the seconds subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime minusSeconds(long seconds) { + return (seconds == Long.MIN_VALUE ? plusSeconds(Long.MAX_VALUE).plusSeconds(1) : plusSeconds(-seconds)); + } + + /** + * Returns a copy of this {@code ZonedDateTime} with the specified period in nanoseconds subtracted. + *

+ * This operates on the instant time-line, such that subtracting one nano will + * always be a duration of one nano earlier. + * This may cause the local date-time to change by an amount other than one nano. + * Note that this is a different approach to that used by days, months and years. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanos the nanos to subtract, may be negative + * @return a {@code ZonedDateTime} based on this date-time with the nanoseconds subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public ZonedDateTime minusNanos(long nanos) { + return (nanos == Long.MIN_VALUE ? plusNanos(Long.MAX_VALUE).plusNanos(1) : plusNanos(-nanos)); + } + + //----------------------------------------------------------------------- + /** + * Queries this date-time using the specified query. + *

+ * This queries this date-time using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link java.time.temporal.TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @Override // override for Javadoc + public R query(TemporalQuery query) { + return ChronoZonedDateTime.super.query(query); + } + + /** + * Calculates the period between this date-time and another date-time in + * terms of the specified unit. + *

+ * This calculates the period between two date-times in terms of a single unit. + * The start and end points are {@code this} and the specified date-time. + * The result will be negative if the end is before the start. + * For example, the period in days between two date-times can be calculated + * using {@code startDateTime.periodUntil(endDateTime, DAYS)}. + *

+ * The {@code Temporal} passed to this method must be a {@code ZonedDateTime}. + * If the time-zone differs between the two zoned date-times, the specified + * end date-time is normalized to have the same zone as this date-time. + *

+ * The calculation returns a whole number, representing the number of + * complete units between the two date-times. + * For example, the period in months between 2012-06-15T00:00Z and 2012-08-14T23:59Z + * will only be one month as it is one minute short of two months. + *

+ * This method operates in association with {@link TemporalUnit#between}. + * The result of this method is a {@code long} representing the amount of + * the specified unit. By contrast, the result of {@code between} is an + * object that can be used directly in addition/subtraction: + *

+     *   long period = start.periodUntil(end, MONTHS);   // this method
+     *   dateTime.plus(MONTHS.between(start, end));      // use in plus/minus
+     * 
+ *

+ * The calculation is implemented in this method for {@link ChronoUnit}. + * The units {@code NANOS}, {@code MICROS}, {@code MILLIS}, {@code SECONDS}, + * {@code MINUTES}, {@code HOURS} and {@code HALF_DAYS}, {@code DAYS}, + * {@code WEEKS}, {@code MONTHS}, {@code YEARS}, {@code DECADES}, + * {@code CENTURIES}, {@code MILLENNIA} and {@code ERAS} are supported. + * Other {@code ChronoUnit} values will throw an exception. + *

+ * The calculation for date and time units differ. + *

+ * Date units operate on the local time-line, using the local date-time. + * For example, the period from noon on day 1 to noon the following day + * in days will always be counted as exactly one day, irrespective of whether + * there was a daylight savings change or not. + *

+ * Time units operate on the instant time-line. + * The calculation effectively converts both zoned date-times to instants + * and then calculates the period between the instants. + * For example, the period from noon on day 1 to noon the following day + * in hours may be 23, 24 or 25 hours (or some other amount) depending on + * whether there was a daylight savings change or not. + *

+ * If the unit is not a {@code ChronoUnit}, then the result of this method + * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)} + * passing {@code this} as the first argument and the input temporal as + * the second argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param endDateTime the end date-time, which must be a {@code ZonedDateTime}, not null + * @param unit the unit to measure the period in, not null + * @return the amount of the period between this date-time and the end date-time + * @throws DateTimeException if the period cannot be calculated + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long periodUntil(Temporal endDateTime, TemporalUnit unit) { + if (endDateTime instanceof ZonedDateTime == false) { + Objects.requireNonNull(endDateTime, "endDateTime"); + throw new DateTimeException("Unable to calculate period between objects of two different types"); + } + if (unit instanceof ChronoUnit) { + ZonedDateTime end = (ZonedDateTime) endDateTime; + end = end.withZoneSameInstant(zone); + ChronoUnit u = (ChronoUnit) unit; + if (u.isDateUnit()) { + return dateTime.periodUntil(end.dateTime, unit); + } else { + return toOffsetDateTime().periodUntil(end.toOffsetDateTime(), unit); + } + } + return unit.between(this, endDateTime).getAmount(); + } + + //----------------------------------------------------------------------- + /** + * Converts this date-time to an {@code OffsetDateTime}. + *

+ * This creates an offset date-time using the local date-time and offset. + * The zone ID is ignored. + * + * @return an offset date-time representing the same local date-time and offset, not null + */ + public OffsetDateTime toOffsetDateTime() { + return OffsetDateTime.of(dateTime, offset); + } + + //----------------------------------------------------------------------- + /** + * Checks if this date-time is equal to another date-time. + *

+ * The comparison is based on the offset date-time and the zone. + * Only objects of type {@code ZonedDateTime} 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 ZonedDateTime) { + ZonedDateTime other = (ZonedDateTime) obj; + return dateTime.equals(other.dateTime) && + offset.equals(other.offset) && + zone.equals(other.zone); + } + return false; + } + + /** + * A hash code for this date-time. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return dateTime.hashCode() ^ offset.hashCode() ^ Integer.rotateLeft(zone.hashCode(), 3); + } + + //----------------------------------------------------------------------- + /** + * Outputs this date-time as a {@code String}, such as + * {@code 2007-12-03T10:15:30+01:00[Europe/Paris]}. + *

+ * The format consists of the {@code LocalDateTime} followed by the {@code ZoneOffset}. + * If the {@code ZoneId} is not the same as the offset, then the ID is output. + * The output is compatible with ISO-8601 if the offset and ID are the same. + * + * @return a string representation of this date-time, not null + */ + @Override // override for Javadoc + public String toString() { + String str = dateTime.toString() + offset.toString(); + if (offset != zone) { + str += '[' + zone.toString() + ']'; + } + return str; + } + + /** + * Outputs this date-time as a {@code String} using the formatter. + *

+ * This date will be passed to the formatter + * {@link DateTimeFormatter#print(TemporalAccessor) print method}. + * + * @param formatter the formatter to use, not null + * @return the formatted date-time string, not null + * @throws DateTimeException if an error occurs during printing + */ + @Override // override for Javadoc + public String toString(DateTimeFormatter formatter) { + return ChronoZonedDateTime.super.toString(formatter); + } + + //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(6);  // identifies this as a ZonedDateTime
+     *  // the date-time excluding the one byte header
+     *  // the offset excluding the one byte header
+     *  // the zone ID excluding the one byte header
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.ZONE_DATE_TIME_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(DataOutput out) throws IOException { + dateTime.writeExternal(out); + offset.writeExternal(out); + zone.write(out); + } + + static ZonedDateTime readExternal(DataInput in) throws IOException { + LocalDateTime dateTime = LocalDateTime.readExternal(in); + ZoneOffset offset = ZoneOffset.readExternal(in); + ZoneId zone = (ZoneId) Ser.read(in); + return ZonedDateTime.ofLenient(dateTime, offset, zone); + } + +} diff --git a/src/share/classes/java/time/calendar/ChronoDateImpl.java b/src/share/classes/java/time/calendar/ChronoDateImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..9ecd2a27103b735880c722dddd7425833fb04523 --- /dev/null +++ b/src/share/classes/java/time/calendar/ChronoDateImpl.java @@ -0,0 +1,378 @@ +/* + * 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 java.time.calendar; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; + +import java.io.Serializable; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.ChronoLocalDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalUnit; + +/** + * A date expressed in terms of a standard year-month-day calendar system. + *

+ * This class is used by applications seeking to handle dates in non-ISO calendar systems. + * For example, the Japanese, Minguo, Thai Buddhist and others. + *

+ * {@code ChronoLocalDate} is built on the generic concepts of year, month and day. + * The calendar system, represented by a {@link java.time.temporal.Chrono}, expresses the relationship between + * the fields and this class allows the resulting date to be manipulated. + *

+ * Note that not all calendar systems are suitable for use with this class. + * For example, the Mayan calendar uses a system that bears no relation to years, months and days. + *

+ * The API design encourages the use of {@code LocalDate} for the majority of the application. + * This includes code to read and write from a persistent data store, such as a database, + * and to send dates and times across a network. The {@code ChronoLocalDate} instance is then used + * at the user interface level to deal with localized input/output. + * + *

Example:

+ *
+ *        System.out.printf("Example()%n");
+ *        // Enumerate the list of available calendars and print today for each
+ *        Set<Chrono> chronos = Chrono.getAvailableChronologies();
+ *        for (Chrono chrono : chronos) {
+ *            ChronoLocalDate date = chrono.dateNow();
+ *            System.out.printf("   %20s: %s%n", chrono.getID(), date.toString());
+ *        }
+ *
+ *        // Print the Hijrah date and calendar
+ *        ChronoLocalDate date = Chrono.of("Hijrah").dateNow();
+ *        int day = date.get(ChronoField.DAY_OF_MONTH);
+ *        int dow = date.get(ChronoField.DAY_OF_WEEK);
+ *        int month = date.get(ChronoField.MONTH_OF_YEAR);
+ *        int year = date.get(ChronoField.YEAR);
+ *        System.out.printf("  Today is %s %s %d-%s-%d%n", date.getChrono().getID(),
+ *                dow, day, month, year);
+
+ *        // Print today's date and the last day of the year
+ *        ChronoLocalDate now1 = Chrono.of("Hijrah").dateNow();
+ *        ChronoLocalDate first = now1.with(ChronoField.DAY_OF_MONTH, 1)
+ *                .with(ChronoField.MONTH_OF_YEAR, 1);
+ *        ChronoLocalDate last = first.plus(1, ChronoUnit.YEARS)
+ *                .minus(1, ChronoUnit.DAYS);
+ *        System.out.printf("  Today is %s: start: %s; end: %s%n", last.getChrono().getID(),
+ *                first, last);
+ * 
+ * + *

Adding Calendars

+ *

The set of calendars is extensible by defining a subclass of {@link ChronoLocalDate} + * to represent a date instance and an implementation of {@code Chrono} + * to be the factory for the ChronoLocalDate subclass. + *

+ *

To permit the discovery of the additional calendar types the implementation of + * {@code Chrono} must be registered as a Service implementing the {@code Chrono} interface + * in the {@code META-INF/Services} file as per the specification of {@link java.util.ServiceLoader}. + * The subclass must function according to the {@code Chrono} class description and must provide its + * {@link java.time.temporal.Chrono#getId() chronlogy ID} and {@link Chrono#getCalendarType() calendar type}.

+ * + *

Specification for implementors

+ * This abstract class must be implemented with care to ensure other classes operate correctly. + * All implementations that can be instantiated must be final, immutable and thread-safe. + * Subclasses should be Serializable wherever possible. + * + * @param the chronology of this date + * @since 1.8 + */ +abstract class ChronoDateImpl> + implements ChronoLocalDate, Temporal, TemporalAdjuster, Serializable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = 6282433883239719096L; + + /** + * Creates an instance. + */ + ChronoDateImpl() { + } + + //----------------------------------------------------------------------- + @Override + public ChronoLocalDate plus(long amountToAdd, TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + ChronoUnit f = (ChronoUnit) unit; + switch (f) { + case DAYS: return plusDays(amountToAdd); + case WEEKS: return plusDays(Math.multiplyExact(amountToAdd, 7)); + case MONTHS: return plusMonths(amountToAdd); + case YEARS: return plusYears(amountToAdd); + case DECADES: return plusYears(Math.multiplyExact(amountToAdd, 10)); + case CENTURIES: return plusYears(Math.multiplyExact(amountToAdd, 100)); + case MILLENNIA: return plusYears(Math.multiplyExact(amountToAdd, 1000)); + case ERAS: return with(ERA, Math.addExact(getLong(ERA), amountToAdd)); + } + throw new DateTimeException("Unsupported unit: " + unit.getName()); + } + return ChronoLocalDate.super.plus(amountToAdd, unit); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date with the specified period in years added. + *

+ * This adds the specified period in years to the date. + * In some cases, adding years can cause the resulting date to become invalid. + * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure + * that the result is valid. Typically this will select the last valid day of the month. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param yearsToAdd the years to add, may be negative + * @return a date based on this one with the years added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + abstract ChronoDateImpl plusYears(long yearsToAdd); + + /** + * Returns a copy of this date with the specified period in months added. + *

+ * This adds the specified period in months to the date. + * In some cases, adding months can cause the resulting date to become invalid. + * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure + * that the result is valid. Typically this will select the last valid day of the month. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param monthsToAdd the months to add, may be negative + * @return a date based on this one with the months added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + abstract ChronoDateImpl plusMonths(long monthsToAdd); + + /** + * Returns a copy of this date with the specified period in weeks added. + *

+ * This adds the specified period in weeks to the date. + * In some cases, adding weeks can cause the resulting date to become invalid. + * If this occurs, then other fields will be adjusted to ensure that the result is valid. + *

+ * The default implementation uses {@link #plusDays(long)} using a 7 day week. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param weeksToAdd the weeks to add, may be negative + * @return a date based on this one with the weeks added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + ChronoDateImpl plusWeeks(long weeksToAdd) { + return plusDays(Math.multiplyExact(weeksToAdd, 7)); + } + + /** + * Returns a copy of this date with the specified number of days added. + *

+ * This adds the specified period in days to the date. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param daysToAdd the days to add, may be negative + * @return a date based on this one with the days added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + abstract ChronoDateImpl plusDays(long daysToAdd); + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date with the specified period in years subtracted. + *

+ * This subtracts the specified period in years to the date. + * In some cases, subtracting years can cause the resulting date to become invalid. + * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure + * that the result is valid. Typically this will select the last valid day of the month. + *

+ * The default implementation uses {@link #plusYears(long)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param yearsToSubtract the years to subtract, may be negative + * @return a date based on this one with the years subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + ChronoDateImpl minusYears(long yearsToSubtract) { + return (yearsToSubtract == Long.MIN_VALUE ? plusYears(Long.MAX_VALUE).plusYears(1) : plusYears(-yearsToSubtract)); + } + + /** + * Returns a copy of this date with the specified period in months subtracted. + *

+ * This subtracts the specified period in months to the date. + * In some cases, subtracting months can cause the resulting date to become invalid. + * If this occurs, then other fields, typically the day-of-month, will be adjusted to ensure + * that the result is valid. Typically this will select the last valid day of the month. + *

+ * The default implementation uses {@link #plusMonths(long)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param monthsToSubtract the months to subtract, may be negative + * @return a date based on this one with the months subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + ChronoDateImpl minusMonths(long monthsToSubtract) { + return (monthsToSubtract == Long.MIN_VALUE ? plusMonths(Long.MAX_VALUE).plusMonths(1) : plusMonths(-monthsToSubtract)); + } + + /** + * Returns a copy of this date with the specified period in weeks subtracted. + *

+ * This subtracts the specified period in weeks to the date. + * In some cases, subtracting weeks can cause the resulting date to become invalid. + * If this occurs, then other fields will be adjusted to ensure that the result is valid. + *

+ * The default implementation uses {@link #plusWeeks(long)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param weeksToSubtract the weeks to subtract, may be negative + * @return a date based on this one with the weeks subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + ChronoDateImpl minusWeeks(long weeksToSubtract) { + return (weeksToSubtract == Long.MIN_VALUE ? plusWeeks(Long.MAX_VALUE).plusWeeks(1) : plusWeeks(-weeksToSubtract)); + } + + /** + * Returns a copy of this date with the specified number of days subtracted. + *

+ * This subtracts the specified period in days to the date. + *

+ * The default implementation uses {@link #plusDays(long)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param daysToSubtract the days to subtract, may be negative + * @return a date based on this one with the days subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + ChronoDateImpl minusDays(long daysToSubtract) { + return (daysToSubtract == Long.MIN_VALUE ? plusDays(Long.MAX_VALUE).plusDays(1) : plusDays(-daysToSubtract)); + } + + @Override + public final ChronoLocalDateTime atTime(LocalTime localTime) { + return Chrono.dateTime(this, localTime); + } + + //----------------------------------------------------------------------- + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public long periodUntil(Temporal endDateTime, TemporalUnit unit) { + if (endDateTime instanceof ChronoLocalDate == false) { + throw new DateTimeException("Unable to calculate period between objects of two different types"); + } + ChronoLocalDate end = (ChronoLocalDate) endDateTime; + if (getChrono().equals(end.getChrono()) == false) { + throw new DateTimeException("Unable to calculate period between two different chronologies"); + } + if (unit instanceof ChronoUnit) { + return LocalDate.from(this).periodUntil(end, unit); // TODO: this is wrong + } + return unit.between(this, endDateTime).getAmount(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof ChronoLocalDate) { + return compareTo((ChronoLocalDate) obj) == 0; + } + return false; + } + + @Override + public int hashCode() { + long epDay = toEpochDay(); + return getChrono().hashCode() ^ ((int) (epDay ^ (epDay >>> 32))); + } + + @Override + public String toString() { + // getLong() reduces chances of exceptions in toString() + long yoe = getLong(YEAR_OF_ERA); + long moy = getLong(MONTH_OF_YEAR); + long dom = getLong(DAY_OF_MONTH); + StringBuilder buf = new StringBuilder(30); + buf.append(getChrono().toString()) + .append(" ") + .append(getEra()) + .append(" ") + .append(yoe) + .append(moy < 10 ? "-0" : "-").append(moy) + .append(dom < 10 ? "-0" : "-").append(dom); + return buf.toString(); + } + +} diff --git a/src/share/classes/java/time/calendar/HijrahChrono.java b/src/share/classes/java/time/calendar/HijrahChrono.java new file mode 100644 index 0000000000000000000000000000000000000000..cc2c8e403e3102bafe5da70a5ae77aea67bc0f8b --- /dev/null +++ b/src/share/classes/java/time/calendar/HijrahChrono.java @@ -0,0 +1,1341 @@ +/* + * 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 java.time.calendar; + +import static java.time.temporal.ChronoField.EPOCH_DAY; + +import java.io.IOException; +import java.io.Serializable; +import java.text.ParseException; +import java.time.DateTimeException; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.Era; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.ValueRange; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +/** + * The Hijrah calendar system. + *

+ * This chronology defines the rules of the Hijrah calendar system. + *

+ * The implementation follows the Freeman-Grenville algorithm (*1) and has following features. + *

    + *
  • A year has 12 months.
  • + *
  • Over a cycle of 30 years there are 11 leap years.
  • + *
  • There are 30 days in month number 1, 3, 5, 7, 9, and 11, + * and 29 days in month number 2, 4, 6, 8, 10, and 12.
  • + *
  • In a leap year month 12 has 30 days.
  • + *
  • In a 30 year cycle, year 2, 5, 7, 10, 13, 16, 18, 21, 24, + * 26, and 29 are leap years.
  • + *
  • Total of 10631 days in a 30 years cycle.
  • + *

+ *

+ * The table shows the features described above. + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Hijrah Calendar Months
# of monthName of monthNumber of days
1Muharram30
2Safar29
3Rabi'al-Awwal30
4Rabi'ath-Thani29
5Jumada l-Ula30
6Jumada t-Tania29
7Rajab30
8Sha`ban29
9Ramadan30
10Shawwal29
11Dhu 'l-Qa`da30
12Dhu 'l-Hijja29, but 30 days in years 2, 5, 7, 10,
+ * 13, 16, 18, 21, 24, 26, and 29
+ *
+ *

+ * (*1) The algorithm is taken from the book, + * The Muslim and Christian Calendars by G.S.P. Freeman-Grenville. + *

+ * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class HijrahChrono extends Chrono implements Serializable { + + /** + * The Hijrah Calendar id. + */ + private final String typeId; + + /** + * The Hijrah calendarType. + */ + private final String calendarType; + + /** + * The singleton instance for the era before the current one - Before Hijrah - + * which has the value 0. + */ + public static final Era ERA_BEFORE_AH = HijrahEra.BEFORE_AH; + /** + * The singleton instance for the current era - Hijrah - which has the value 1. + */ + public static final Era ERA_AH = HijrahEra.AH; + /** + * Serialization version. + */ + private static final long serialVersionUID = 3127340209035924785L; + /** + * The minimum valid year-of-era. + */ + public static final int MIN_YEAR_OF_ERA = 1; + /** + * The maximum valid year-of-era. + * This is currently set to 9999 but may be changed to increase the valid range + * in a future version of the specification. + */ + public static final int MAX_YEAR_OF_ERA = 9999; + + /** + * Number of Gregorian day of July 19, year 622 (Gregorian), which is epoch day + * of Hijrah calendar. + */ + private static final int HIJRAH_JAN_1_1_GREGORIAN_DAY = -492148; + /** + * 0-based, for number of day-of-year in the beginning of month in normal + * year. + */ + private static final int NUM_DAYS[] = + {0, 30, 59, 89, 118, 148, 177, 207, 236, 266, 295, 325}; + /** + * 0-based, for number of day-of-year in the beginning of month in leap year. + */ + private static final int LEAP_NUM_DAYS[] = + {0, 30, 59, 89, 118, 148, 177, 207, 236, 266, 295, 325}; + /** + * 0-based, for day-of-month in normal year. + */ + private static final int MONTH_LENGTH[] = + {30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29}; + /** + * 0-based, for day-of-month in leap year. + */ + private static final int LEAP_MONTH_LENGTH[] = + {30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 30}; + + /** + *
+     *                            Greatest       Least
+     * Field name        Minimum   Minimum     Maximum     Maximum
+     * ----------        -------   -------     -------     -------
+     * ERA                     0         0           1           1
+     * YEAR_OF_ERA             1         1        9999        9999
+     * MONTH_OF_YEAR           1         1          12          12
+     * DAY_OF_MONTH            1         1          29          30
+     * DAY_OF_YEAR             1         1         354         355
+     * 
+ * + * Minimum values. + */ + private static final int MIN_VALUES[] = + { + 0, + MIN_YEAR_OF_ERA, + 0, + 1, + 0, + 1, + 1 + }; + + /** + * Least maximum values. + */ + private static final int LEAST_MAX_VALUES[] = + { + 1, + MAX_YEAR_OF_ERA, + 11, + 51, + 5, + 29, + 354 + }; + + /** + * Maximum values. + */ + private static final int MAX_VALUES[] = + { + 1, + MAX_YEAR_OF_ERA, + 11, + 52, + 6, + 30, + 355 + }; + + /** + * Position of day-of-month. This value is used to get the min/max value + * from an array. + */ + private static final int POSITION_DAY_OF_MONTH = 5; + /** + * Position of day-of-year. This value is used to get the min/max value from + * an array. + */ + private static final int POSITION_DAY_OF_YEAR = 6; + /** + * Zero-based start date of cycle year. + */ + private static final int CYCLEYEAR_START_DATE[] = + { + 0, + 354, + 709, + 1063, + 1417, + 1772, + 2126, + 2481, + 2835, + 3189, + 3544, + 3898, + 4252, + 4607, + 4961, + 5315, + 5670, + 6024, + 6379, + 6733, + 7087, + 7442, + 7796, + 8150, + 8505, + 8859, + 9214, + 9568, + 9922, + 10277 + }; + + /** + * Holding the adjusted month days in year. The key is a year (Integer) and + * the value is the all the month days in year (int[]). + */ + private final HashMap ADJUSTED_MONTH_DAYS = new HashMap<>(); + /** + * Holding the adjusted month length in year. The key is a year (Integer) + * and the value is the all the month length in year (int[]). + */ + private final HashMap ADJUSTED_MONTH_LENGTHS = new HashMap<>(); + /** + * Holding the adjusted days in the 30 year cycle. The key is a cycle number + * (Integer) and the value is the all the starting days of the year in the + * cycle (int[]). + */ + private final HashMap ADJUSTED_CYCLE_YEARS = new HashMap<>(); + /** + * Holding the adjusted cycle in the 1 - 30000 year. The key is the cycle + * number (Integer) and the value is the starting days in the cycle in the + * term. + */ + private final long[] ADJUSTED_CYCLES; + /** + * Holding the adjusted min values. + */ + private final int[] ADJUSTED_MIN_VALUES; + /** + * Holding the adjusted max least max values. + */ + private final int[] ADJUSTED_LEAST_MAX_VALUES; + /** + * Holding adjusted max values. + */ + private final int[] ADJUSTED_MAX_VALUES; + /** + * Holding the non-adjusted month days in year for non leap year. + */ + private static final int[] DEFAULT_MONTH_DAYS; + /** + * Holding the non-adjusted month days in year for leap year. + */ + private static final int[] DEFAULT_LEAP_MONTH_DAYS; + /** + * Holding the non-adjusted month length for non leap year. + */ + private static final int[] DEFAULT_MONTH_LENGTHS; + /** + * Holding the non-adjusted month length for leap year. + */ + private static final int[] DEFAULT_LEAP_MONTH_LENGTHS; + /** + * Holding the non-adjusted 30 year cycle starting day. + */ + private static final int[] DEFAULT_CYCLE_YEARS; + /** + * number of 30-year cycles to hold the deviation data. + */ + private static final int MAX_ADJUSTED_CYCLE = 334; // to support year 9999 + + + /** + * Narrow names for eras. + */ + private static final HashMap ERA_NARROW_NAMES = new HashMap<>(); + /** + * Short names for eras. + */ + private static final HashMap ERA_SHORT_NAMES = new HashMap<>(); + /** + * Full names for eras. + */ + private static final HashMap ERA_FULL_NAMES = new HashMap<>(); + /** + * Fallback language for the era names. + */ + private static final String FALLBACK_LANGUAGE = "en"; + + /** + * Singleton instance of the Hijrah chronology. + * Must be initialized after the rest of the static initialization. + */ + public static final HijrahChrono INSTANCE; + + /** + * Name data. + */ + static { + ERA_NARROW_NAMES.put(FALLBACK_LANGUAGE, new String[]{"BH", "HE"}); + ERA_SHORT_NAMES.put(FALLBACK_LANGUAGE, new String[]{"B.H.", "H.E."}); + ERA_FULL_NAMES.put(FALLBACK_LANGUAGE, new String[]{"Before Hijrah", "Hijrah Era"}); + + DEFAULT_MONTH_DAYS = Arrays.copyOf(NUM_DAYS, NUM_DAYS.length); + + DEFAULT_LEAP_MONTH_DAYS = Arrays.copyOf(LEAP_NUM_DAYS, LEAP_NUM_DAYS.length); + + DEFAULT_MONTH_LENGTHS = Arrays.copyOf(MONTH_LENGTH, MONTH_LENGTH.length); + + DEFAULT_LEAP_MONTH_LENGTHS = Arrays.copyOf(LEAP_MONTH_LENGTH, LEAP_MONTH_LENGTH.length); + + DEFAULT_CYCLE_YEARS = Arrays.copyOf(CYCLEYEAR_START_DATE, CYCLEYEAR_START_DATE.length); + + INSTANCE = new HijrahChrono(); + + String extraCalendars = java.security.AccessController.doPrivileged( + new sun.security.action.GetPropertyAction("java.time.calendar.HijrahCalendars")); + if (extraCalendars != null) { + try { + // Split on whitespace + String[] splits = extraCalendars.split("\\s"); + for (String cal : splits) { + if (!cal.isEmpty()) { + // Split on the delimiter between typeId "-" calendarType + String[] type = cal.split("-"); + Chrono cal2 = new HijrahChrono(type[0], type.length > 1 ? type[1] : type[0]); + } + } + } catch (Exception ex) { + // Log the error + // ex.printStackTrace(); + } + } + } + + /** + * Restricted constructor. + */ + private HijrahChrono() { + this("Hijrah", "islamicc"); + } + /** + * Constructor for name and type HijrahChrono. + * @param id the id of the calendar + * @param calendarType the calendar type + */ + private HijrahChrono(String id, String calendarType) { + this.typeId = id; + this.calendarType = calendarType; + + ADJUSTED_CYCLES = new long[MAX_ADJUSTED_CYCLE]; + for (int i = 0; i < ADJUSTED_CYCLES.length; i++) { + ADJUSTED_CYCLES[i] = (10631L * i); + } + // Initialize min values, least max values and max values. + ADJUSTED_MIN_VALUES = Arrays.copyOf(MIN_VALUES, MIN_VALUES.length); + ADJUSTED_LEAST_MAX_VALUES = Arrays.copyOf(LEAST_MAX_VALUES, LEAST_MAX_VALUES.length); + ADJUSTED_MAX_VALUES = Arrays.copyOf(MAX_VALUES,MAX_VALUES.length); + + try { + // Implicitly reads deviation data for this HijrahChronology. + boolean any = HijrahDeviationReader.readDeviation(typeId, calendarType, this::addDeviationAsHijrah); + } catch (IOException | ParseException e) { + // do nothing. Log deviation config errors. + //e.printStackTrace(); + } + } + + /** + * Resolve singleton. + * + * @return the singleton instance, not null + */ + private Object readResolve() { + return INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the ID of the chronology - 'Hijrah'. + *

+ * The ID uniquely identifies the {@code Chrono}. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * + * @return the chronology ID - 'Hijrah' + * @see #getCalendarType() + */ + @Override + public String getId() { + return typeId; + } + + /** + * Gets the calendar type of the underlying calendar system - 'islamicc'. + *

+ * The calendar type is an identifier defined by the + * Unicode Locale Data Markup Language (LDML) specification. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * It can also be used as part of a locale, accessible via + * {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'. + * + * @return the calendar system type - 'islamicc' + * @see #getId() + */ + @Override + public String getCalendarType() { + return calendarType; + } + + //----------------------------------------------------------------------- + @Override + public ChronoLocalDate date(int prolepticYear, int month, int dayOfMonth) { + return HijrahDate.of(this, prolepticYear, month, dayOfMonth); + } + + @Override + public ChronoLocalDate dateYearDay(int prolepticYear, int dayOfYear) { + return HijrahDate.of(this, prolepticYear, 1, 1).plusDays(dayOfYear - 1); // TODO better + } + + @Override + public ChronoLocalDate date(TemporalAccessor temporal) { + if (temporal instanceof HijrahDate) { + return (HijrahDate) temporal; + } + return HijrahDate.ofEpochDay(this, temporal.getLong(EPOCH_DAY)); + } + + //----------------------------------------------------------------------- + @Override + public boolean isLeapYear(long prolepticYear) { + return isLeapYear0(prolepticYear); + } + /** + * Returns if the year is a leap year. + * @param prolepticYear he year to compute from + * @return {@code true} if the year is a leap year, otherwise {@code false} + */ + private static boolean isLeapYear0(long prolepticYear) { + return (14 + 11 * (prolepticYear > 0 ? prolepticYear : -prolepticYear)) % 30 < 11; + } + + @Override + public int prolepticYear(Era era, int yearOfEra) { + if (era instanceof HijrahEra == false) { + throw new DateTimeException("Era must be HijrahEra"); + } + return (era == HijrahEra.AH ? yearOfEra : 1 - yearOfEra); + } + + @Override + public Era eraOf(int eraValue) { + switch (eraValue) { + case 0: + return HijrahEra.BEFORE_AH; + case 1: + return HijrahEra.AH; + default: + throw new DateTimeException("invalid Hijrah era"); + } + } + + @Override + public List> eras() { + return Arrays.>asList(HijrahEra.values()); + } + + //----------------------------------------------------------------------- + @Override + public ValueRange range(ChronoField field) { + return field.range(); + } + + /** + * Check the validity of a yearOfEra. + * @param yearOfEra the year to check + */ + void checkValidYearOfEra(int yearOfEra) { + if (yearOfEra < MIN_YEAR_OF_ERA || + yearOfEra > MAX_YEAR_OF_ERA) { + throw new DateTimeException("Invalid year of Hijrah Era"); + } + } + + void checkValidDayOfYear(int dayOfYear) { + if (dayOfYear < 1 || + dayOfYear > getMaximumDayOfYear()) { + throw new DateTimeException("Invalid day of year of Hijrah date"); + } + } + + void checkValidMonth(int month) { + if (month < 1 || month > 12) { + throw new DateTimeException("Invalid month of Hijrah date"); + } + } + + void checkValidDayOfMonth(int dayOfMonth) { + if (dayOfMonth < 1 || + dayOfMonth > getMaximumDayOfMonth()) { + throw new DateTimeException("Invalid day of month of Hijrah date, day " + + dayOfMonth + " greater than " + getMaximumDayOfMonth() + " or less than 1"); + } + } + + //----------------------------------------------------------------------- + /** + * Returns the int array containing the following field from the julian day. + * + * int[0] = ERA + * int[1] = YEAR + * int[2] = MONTH + * int[3] = DATE + * int[4] = DAY_OF_YEAR + * int[5] = DAY_OF_WEEK + * + * @param gregorianDays a julian day. + */ + int[] getHijrahDateInfo(long gregorianDays) { + int era, year, month, date, dayOfWeek, dayOfYear; + + int cycleNumber, yearInCycle, dayOfCycle; + + long epochDay = gregorianDays - HIJRAH_JAN_1_1_GREGORIAN_DAY; + + if (epochDay >= 0) { + cycleNumber = getCycleNumber(epochDay); // 0 - 99. + dayOfCycle = getDayOfCycle(epochDay, cycleNumber); // 0 - 10631. + yearInCycle = getYearInCycle(cycleNumber, dayOfCycle); // 0 - 29. + dayOfYear = getDayOfYear(cycleNumber, dayOfCycle, yearInCycle); + // 0 - 354/355 + year = cycleNumber * 30 + yearInCycle + 1; // 1-based year. + month = getMonthOfYear(dayOfYear, year); // 0-based month-of-year + date = getDayOfMonth(dayOfYear, month, year); // 0-based date + ++date; // Convert from 0-based to 1-based + era = HijrahEra.AH.getValue(); + } else { + cycleNumber = (int) epochDay / 10631; // 0 or negative number. + dayOfCycle = (int) epochDay % 10631; // -10630 - 0. + if (dayOfCycle == 0) { + dayOfCycle = -10631; + cycleNumber++; + } + yearInCycle = getYearInCycle(cycleNumber, dayOfCycle); // 0 - 29. + dayOfYear = getDayOfYear(cycleNumber, dayOfCycle, yearInCycle); + year = cycleNumber * 30 - yearInCycle; // negative number. + year = 1 - year; + dayOfYear = (isLeapYear(year) ? (dayOfYear + 355) + : (dayOfYear + 354)); + month = getMonthOfYear(dayOfYear, year); + date = getDayOfMonth(dayOfYear, month, year); + ++date; // Convert from 0-based to 1-based + era = HijrahEra.BEFORE_AH.getValue(); + } + // Hijrah day zero is a Friday + dayOfWeek = (int) ((epochDay + 5) % 7); + dayOfWeek += (dayOfWeek <= 0) ? 7 : 0; + + int dateInfo[] = new int[6]; + dateInfo[0] = era; + dateInfo[1] = year; + dateInfo[2] = month + 1; // change to 1-based. + dateInfo[3] = date; + dateInfo[4] = dayOfYear + 1; // change to 1-based. + dateInfo[5] = dayOfWeek; + return dateInfo; + } + + /** + * Return Gregorian epoch day from Hijrah year, month, and day. + * + * @param prolepticYear the year to represent, caller calculated + * @param monthOfYear the month-of-year to represent, caller calculated + * @param dayOfMonth the day-of-month to represent, caller calculated + * @return a julian day + */ + long getGregorianEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) { + long day = yearToGregorianEpochDay(prolepticYear); + day += getMonthDays(monthOfYear - 1, prolepticYear); + day += dayOfMonth; + return day; + } + + /** + * Returns the Gregorian epoch day from the proleptic year + * @param prolepticYear the proleptic year + * @return the Epoch day + */ + private long yearToGregorianEpochDay(int prolepticYear) { + + int cycleNumber = (prolepticYear - 1) / 30; // 0-based. + int yearInCycle = (prolepticYear - 1) % 30; // 0-based. + + int dayInCycle = getAdjustedCycle(cycleNumber)[Math.abs(yearInCycle)] + ; + + if (yearInCycle < 0) { + dayInCycle = -dayInCycle; + } + + Long cycleDays; + + try { + cycleDays = ADJUSTED_CYCLES[cycleNumber]; + } catch (ArrayIndexOutOfBoundsException e) { + cycleDays = null; + } + + if (cycleDays == null) { + cycleDays = new Long(cycleNumber * 10631); + } + + return (cycleDays.longValue() + dayInCycle + HIJRAH_JAN_1_1_GREGORIAN_DAY - 1); + } + + /** + * Returns the 30 year cycle number from the epoch day. + * + * @param epochDay an epoch day + * @return a cycle number + */ + private int getCycleNumber(long epochDay) { + long[] days = ADJUSTED_CYCLES; + int cycleNumber; + try { + for (int i = 0; i < days.length; i++) { + if (epochDay < days[i]) { + return i - 1; + } + } + cycleNumber = (int) epochDay / 10631; + } catch (ArrayIndexOutOfBoundsException e) { + cycleNumber = (int) epochDay / 10631; + } + return cycleNumber; + } + + /** + * Returns day of cycle from the epoch day and cycle number. + * + * @param epochDay an epoch day + * @param cycleNumber a cycle number + * @return a day of cycle + */ + private int getDayOfCycle(long epochDay, int cycleNumber) { + Long day; + + try { + day = ADJUSTED_CYCLES[cycleNumber]; + } catch (ArrayIndexOutOfBoundsException e) { + day = null; + } + if (day == null) { + day = new Long(cycleNumber * 10631); + } + return (int) (epochDay - day.longValue()); + } + + /** + * Returns the year in cycle from the cycle number and day of cycle. + * + * @param cycleNumber a cycle number + * @param dayOfCycle day of cycle + * @return a year in cycle + */ + private int getYearInCycle(int cycleNumber, long dayOfCycle) { + int[] cycles = getAdjustedCycle(cycleNumber); + if (dayOfCycle == 0) { + return 0; + } + + if (dayOfCycle > 0) { + for (int i = 0; i < cycles.length; i++) { + if (dayOfCycle < cycles[i]) { + return i - 1; + } + } + return 29; + } else { + dayOfCycle = -dayOfCycle; + for (int i = 0; i < cycles.length; i++) { + if (dayOfCycle <= cycles[i]) { + return i - 1; + } + } + return 29; + } + } + + /** + * Returns adjusted 30 year cycle starting day as Integer array from the + * cycle number specified. + * + * @param cycleNumber a cycle number + * @return an Integer array + */ + int[] getAdjustedCycle(int cycleNumber) { + int[] cycles; + try { + cycles = ADJUSTED_CYCLE_YEARS.get(cycleNumber); + } catch (ArrayIndexOutOfBoundsException e) { + cycles = null; + } + if (cycles == null) { + cycles = DEFAULT_CYCLE_YEARS; + } + return cycles; + } + + /** + * Returns adjusted month days as Integer array form the year specified. + * + * @param year a year + * @return an Integer array + */ + int[] getAdjustedMonthDays(int year) { + int[] newMonths; + try { + newMonths = ADJUSTED_MONTH_DAYS.get(year); + } catch (ArrayIndexOutOfBoundsException e) { + newMonths = null; + } + if (newMonths == null) { + if (isLeapYear0(year)) { + newMonths = DEFAULT_LEAP_MONTH_DAYS; + } else { + newMonths = DEFAULT_MONTH_DAYS; + } + } + return newMonths; + } + + /** + * Returns adjusted month length as Integer array form the year specified. + * + * @param year a year + * @return an Integer array + */ + int[] getAdjustedMonthLength(int year) { + int[] newMonths; + try { + newMonths = ADJUSTED_MONTH_LENGTHS.get(year); + } catch (ArrayIndexOutOfBoundsException e) { + newMonths = null; + } + if (newMonths == null) { + if (isLeapYear0(year)) { + newMonths = DEFAULT_LEAP_MONTH_LENGTHS; + } else { + newMonths = DEFAULT_MONTH_LENGTHS; + } + } + return newMonths; + } + + /** + * Returns day-of-year. + * + * @param cycleNumber a cycle number + * @param dayOfCycle day of cycle + * @param yearInCycle year in cycle + * @return day-of-year + */ + private int getDayOfYear(int cycleNumber, int dayOfCycle, int yearInCycle) { + int[] cycles = getAdjustedCycle(cycleNumber); + + if (dayOfCycle > 0) { + return dayOfCycle - cycles[yearInCycle]; + } else { + return cycles[yearInCycle] + dayOfCycle; + } + } + + /** + * Returns month-of-year. 0-based. + * + * @param dayOfYear day-of-year + * @param year a year + * @return month-of-year + */ + private int getMonthOfYear(int dayOfYear, int year) { + + int[] newMonths = getAdjustedMonthDays(year); + + if (dayOfYear >= 0) { + for (int i = 0; i < newMonths.length; i++) { + if (dayOfYear < newMonths[i]) { + return i - 1; + } + } + return 11; + } else { + dayOfYear = (isLeapYear0(year) ? (dayOfYear + 355) + : (dayOfYear + 354)); + for (int i = 0; i < newMonths.length; i++) { + if (dayOfYear < newMonths[i]) { + return i - 1; + } + } + return 11; + } + } + + /** + * Returns day-of-month. + * + * @param dayOfYear day of year + * @param month month + * @param year year + * @return day-of-month + */ + private int getDayOfMonth(int dayOfYear, int month, int year) { + + int[] newMonths = getAdjustedMonthDays(year); + + if (dayOfYear >= 0) { + if (month > 0) { + return dayOfYear - newMonths[month]; + } else { + return dayOfYear; + } + } else { + dayOfYear = (isLeapYear0(year) ? (dayOfYear + 355) + : (dayOfYear + 354)); + if (month > 0) { + return dayOfYear - newMonths[month]; + } else { + return dayOfYear; + } + } + } + + + /** + * Returns month days from the beginning of year. + * + * @param month month (0-based) + * @parma year year + * @return month days from the beginning of year + */ + private int getMonthDays(int month, int year) { + int[] newMonths = getAdjustedMonthDays(year); + return newMonths[month]; + } + + /** + * Returns month length. + * + * @param month month (0-based) + * @param year year + * @return month length + */ + private int getMonthLength(int month, int year) { + int[] newMonths = getAdjustedMonthLength(year); + return newMonths[month]; + } + + /** + * Returns year length. + * + * @param year year + * @return year length + */ + int getYearLength(int year) { + + int cycleNumber = (year - 1) / 30; + int[] cycleYears; + try { + cycleYears = ADJUSTED_CYCLE_YEARS.get(cycleNumber); + } catch (ArrayIndexOutOfBoundsException e) { + cycleYears = null; + } + if (cycleYears != null) { + int yearInCycle = (year - 1) % 30; + if (yearInCycle == 29) { + return (int)(ADJUSTED_CYCLES[cycleNumber + 1] + - ADJUSTED_CYCLES[cycleNumber] + - cycleYears[yearInCycle]); + } + return cycleYears[yearInCycle + 1] + - cycleYears[yearInCycle]; + } else { + return isLeapYear0(year) ? 355 : 354; + } + } + + + /** + * Returns maximum day-of-month. + * + * @return maximum day-of-month + */ + int getMaximumDayOfMonth() { + return ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH]; + } + + /** + * Returns smallest maximum day-of-month. + * + * @return smallest maximum day-of-month + */ + int getSmallestMaximumDayOfMonth() { + return ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH]; + } + + /** + * Returns maximum day-of-year. + * + * @return maximum day-of-year + */ + int getMaximumDayOfYear() { + return ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR]; + } + + /** + * Returns smallest maximum day-of-year. + * + * @return smallest maximum day-of-year + */ + int getSmallestMaximumDayOfYear() { + return ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR]; + } + + // ----- Deviation handling -----// + + /** + * Adds deviation definition. The year and month specified should be the + * calculated Hijrah year and month. The month is 1 based. e.g. 9 for + * Ramadan (9th month) Addition of anything minus deviation days is + * calculated negatively in the case the user wants to subtract days from + * the calendar. For example, adding -1 days will subtract one day from the + * current date. + * + * @param startYear start year, 1 origin + * @param startMonth start month, 1 origin + * @param endYear end year, 1 origin + * @param endMonth end month, 1 origin + * @param offset offset -2, -1, +1, +2 + */ + private void addDeviationAsHijrah(Deviation entry) { + int startYear = entry.startYear; + int startMonth = entry.startMonth - 1 ; + int endYear = entry.endYear; + int endMonth = entry.endMonth - 1; + int offset = entry.offset; + + if (startYear < 1) { + throw new IllegalArgumentException("startYear < 1"); + } + if (endYear < 1) { + throw new IllegalArgumentException("endYear < 1"); + } + if (startMonth < 0 || startMonth > 11) { + throw new IllegalArgumentException( + "startMonth < 0 || startMonth > 11"); + } + if (endMonth < 0 || endMonth > 11) { + throw new IllegalArgumentException("endMonth < 0 || endMonth > 11"); + } + if (endYear > 9999) { + throw new IllegalArgumentException("endYear > 9999"); + } + if (endYear < startYear) { + throw new IllegalArgumentException("startYear > endYear"); + } + if (endYear == startYear && endMonth < startMonth) { + throw new IllegalArgumentException( + "startYear == endYear && endMonth < startMonth"); + } + + // Adjusting start year. + boolean isStartYLeap = isLeapYear0(startYear); + + // Adjusting the number of month. + int[] orgStartMonthNums = ADJUSTED_MONTH_DAYS.get(startYear); + if (orgStartMonthNums == null) { + if (isStartYLeap) { + orgStartMonthNums = Arrays.copyOf(LEAP_NUM_DAYS, LEAP_NUM_DAYS.length); + } else { + orgStartMonthNums = Arrays.copyOf(NUM_DAYS, NUM_DAYS.length); + } + } + + int[] newStartMonthNums = new int[orgStartMonthNums.length]; + + for (int month = 0; month < 12; month++) { + if (month > startMonth) { + newStartMonthNums[month] = (orgStartMonthNums[month] - offset); + } else { + newStartMonthNums[month] = (orgStartMonthNums[month]); + } + } + + ADJUSTED_MONTH_DAYS.put(startYear, newStartMonthNums); + + // Adjusting the days of month. + + int[] orgStartMonthLengths = ADJUSTED_MONTH_LENGTHS.get(startYear); + if (orgStartMonthLengths == null) { + if (isStartYLeap) { + orgStartMonthLengths = Arrays.copyOf(LEAP_MONTH_LENGTH, LEAP_MONTH_LENGTH.length); + } else { + orgStartMonthLengths = Arrays.copyOf(MONTH_LENGTH, MONTH_LENGTH.length); + } + } + + int[] newStartMonthLengths = new int[orgStartMonthLengths.length]; + + for (int month = 0; month < 12; month++) { + if (month == startMonth) { + newStartMonthLengths[month] = orgStartMonthLengths[month] - offset; + } else { + newStartMonthLengths[month] = orgStartMonthLengths[month]; + } + } + + ADJUSTED_MONTH_LENGTHS.put(startYear, newStartMonthLengths); + + if (startYear != endYear) { + // System.out.println("over year"); + // Adjusting starting 30 year cycle. + int sCycleNumber = (startYear - 1) / 30; + int sYearInCycle = (startYear - 1) % 30; // 0-based. + int[] startCycles = ADJUSTED_CYCLE_YEARS.get(sCycleNumber); + if (startCycles == null) { + startCycles = Arrays.copyOf(CYCLEYEAR_START_DATE, CYCLEYEAR_START_DATE.length); + } + + for (int j = sYearInCycle + 1; j < CYCLEYEAR_START_DATE.length; j++) { + startCycles[j] = startCycles[j] - offset; + } + + // System.out.println(sCycleNumber + ":" + sYearInCycle); + ADJUSTED_CYCLE_YEARS.put(sCycleNumber, startCycles); + + int sYearInMaxY = (startYear - 1) / 30; + int sEndInMaxY = (endYear - 1) / 30; + + if (sYearInMaxY != sEndInMaxY) { + // System.out.println("over 30"); + // Adjusting starting 30 * MAX_ADJUSTED_CYCLE year cycle. + // System.out.println(sYearInMaxY); + + for (int j = sYearInMaxY + 1; j < ADJUSTED_CYCLES.length; j++) { + ADJUSTED_CYCLES[j] = ADJUSTED_CYCLES[j] - offset; + } + + // Adjusting ending 30 * MAX_ADJUSTED_CYCLE year cycles. + for (int j = sEndInMaxY + 1; j < ADJUSTED_CYCLES.length; j++) { + ADJUSTED_CYCLES[j] = ADJUSTED_CYCLES[j] + offset; + } + } + + // Adjusting ending 30 year cycle. + int eCycleNumber = (endYear - 1) / 30; + int sEndInCycle = (endYear - 1) % 30; // 0-based. + int[] endCycles = ADJUSTED_CYCLE_YEARS.get(eCycleNumber); + if (endCycles == null) { + endCycles = Arrays.copyOf(CYCLEYEAR_START_DATE, CYCLEYEAR_START_DATE.length); + } + for (int j = sEndInCycle + 1; j < CYCLEYEAR_START_DATE.length; j++) { + endCycles[j] = endCycles[j] + offset; + } + ADJUSTED_CYCLE_YEARS.put(eCycleNumber, endCycles); + } + + // Adjusting ending year. + boolean isEndYLeap = isLeapYear0(endYear); + + int[] orgEndMonthDays = ADJUSTED_MONTH_DAYS.get(endYear); + + if (orgEndMonthDays == null) { + if (isEndYLeap) { + orgEndMonthDays = Arrays.copyOf(LEAP_NUM_DAYS, LEAP_NUM_DAYS.length); + } else { + orgEndMonthDays = Arrays.copyOf(NUM_DAYS, NUM_DAYS.length); + } + } + + int[] newEndMonthDays = new int[orgEndMonthDays.length]; + + for (int month = 0; month < 12; month++) { + if (month > endMonth) { + newEndMonthDays[month] = orgEndMonthDays[month] + offset; + } else { + newEndMonthDays[month] = orgEndMonthDays[month]; + } + } + + ADJUSTED_MONTH_DAYS.put(endYear, newEndMonthDays); + + // Adjusting the days of month. + int[] orgEndMonthLengths = ADJUSTED_MONTH_LENGTHS.get(endYear); + + if (orgEndMonthLengths == null) { + if (isEndYLeap) { + orgEndMonthLengths = Arrays.copyOf(LEAP_MONTH_LENGTH, LEAP_MONTH_LENGTH.length); + } else { + orgEndMonthLengths = Arrays.copyOf(MONTH_LENGTH, MONTH_LENGTH.length); + } + } + + int[] newEndMonthLengths = new int[orgEndMonthLengths.length]; + + for (int month = 0; month < 12; month++) { + if (month == endMonth) { + newEndMonthLengths[month] = orgEndMonthLengths[month] + offset; + } else { + newEndMonthLengths[month] = orgEndMonthLengths[month]; + } + } + + ADJUSTED_MONTH_LENGTHS.put(endYear, newEndMonthLengths); + + int[] startMonthLengths = ADJUSTED_MONTH_LENGTHS.get(startYear); + int[] endMonthLengths = ADJUSTED_MONTH_LENGTHS.get(endYear); + int[] startMonthDays = ADJUSTED_MONTH_DAYS.get(startYear); + int[] endMonthDays = ADJUSTED_MONTH_DAYS.get(endYear); + + int startMonthLength = startMonthLengths[startMonth]; + int endMonthLength = endMonthLengths[endMonth]; + int startMonthDay = startMonthDays[11] + startMonthLengths[11]; + int endMonthDay = endMonthDays[11] + endMonthLengths[11]; + + int maxMonthLength = ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH]; + int leastMaxMonthLength = ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH]; + + if (maxMonthLength < startMonthLength) { + maxMonthLength = startMonthLength; + } + if (maxMonthLength < endMonthLength) { + maxMonthLength = endMonthLength; + } + ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH] = maxMonthLength; + + if (leastMaxMonthLength > startMonthLength) { + leastMaxMonthLength = startMonthLength; + } + if (leastMaxMonthLength > endMonthLength) { + leastMaxMonthLength = endMonthLength; + } + ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH] = leastMaxMonthLength; + + int maxMonthDay = ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR]; + int leastMaxMonthDay = ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR]; + + if (maxMonthDay < startMonthDay) { + maxMonthDay = startMonthDay; + } + if (maxMonthDay < endMonthDay) { + maxMonthDay = endMonthDay; + } + + ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR] = maxMonthDay; + + if (leastMaxMonthDay > startMonthDay) { + leastMaxMonthDay = startMonthDay; + } + if (leastMaxMonthDay > endMonthDay) { + leastMaxMonthDay = endMonthDay; + } + ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR] = leastMaxMonthDay; + } + + /** + * Package private Entry for suppling deviations from the Reader. + * Each entry consists of a range using the Hijrah calendar, + * start year, month, end year, end month, and an offset. + * The offset is used to modify the length of the month +2, +1, -1, -2. + */ + static final class Deviation { + + Deviation(int startYear, int startMonth, int endYear, int endMonth, int offset) { + this.startYear = startYear; + this.startMonth = startMonth; + this.endYear = endYear; + this.endMonth = endMonth; + this.offset = offset; + } + + final int startYear; + final int startMonth; + final int endYear; + final int endMonth; + final int offset; + + int getStartYear() { + return startYear; + } + + int getStartMonth() { + return startMonth; + } + + int getEndYear() { + return endYear; + } + + int getEndMonth() { + return endMonth; + } + + int getOffset() { + return offset; + } + + @Override + public String toString() { + return String.format("[year: %4d, month: %2d, offset: %+d]", startYear, startMonth, offset); + } + } + +} diff --git a/src/share/classes/java/time/calendar/HijrahDate.java b/src/share/classes/java/time/calendar/HijrahDate.java new file mode 100644 index 0000000000000000000000000000000000000000..c32525530f6f20bb29cc3ff9666f00f352fb175b --- /dev/null +++ b/src/share/classes/java/time/calendar/HijrahDate.java @@ -0,0 +1,445 @@ +/* + * 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 java.time.calendar; + +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.Serializable; +import java.time.DateTimeException; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.TemporalField; +import java.time.temporal.ValueRange; +import java.util.Objects; + +/** + * A date in the Hijrah calendar system. + *

+ * This implements {@code ChronoLocalDate} for the {@link HijrahChrono Hijrah calendar}. + *

+ * The Hijrah calendar has a different total of days in a year than + * Gregorian calendar, and a month is based on the period of a complete + * revolution of the moon around the earth (as between successive new moons). + * The calendar cycles becomes longer and unstable, and sometimes a manual + * adjustment (for entering deviation) is necessary for correctness + * because of the complex algorithm. + *

+ * HijrahDate supports the manual adjustment feature by providing a configuration + * file. The configuration file contains the adjustment (deviation) data with following format. + *

+ *   StartYear/StartMonth(0-based)-EndYear/EndMonth(0-based):Deviation day (1, 2, -1, or -2)
+ *   Line separator or ";" is used for the separator of each deviation data.
+ * Here is the example. + *
+ *     1429/0-1429/1:1
+ *     1429/2-1429/7:1;1429/6-1429/11:1
+ *     1429/11-9999/11:1
+ * The default location of the configuration file is: + *
+ *   $CLASSPATH/java/time/i18n
+ * And the default file name is: + *
+ *   hijrah_deviation.cfg
+ * The default location and file name can be overriden by setting + * following two Java's system property. + *
+ *   Location: java.time.i18n.HijrahDate.deviationConfigDir
+ *   File name: java.time.i18n.HijrahDate.deviationConfigFile
+ * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +final class HijrahDate + extends ChronoDateImpl + implements ChronoLocalDate, Serializable { + // this class is package-scoped so that future conversion to public + // would not change serialization + + /** + * Serialization version. + */ + private static final long serialVersionUID = -5207853542612002020L; + + /** + * The Chronology of this HijrahDate. + */ + private final HijrahChrono chrono; + /** + * The era. + */ + private final transient HijrahEra era; + /** + * The year. + */ + private final transient int yearOfEra; + /** + * The month-of-year. + */ + private final transient int monthOfYear; + /** + * The day-of-month. + */ + private final transient int dayOfMonth; + /** + * The day-of-year. + */ + private final transient int dayOfYear; + /** + * The day-of-week. + */ + private final transient DayOfWeek dayOfWeek; + /** + * Gregorian days for this object. Holding number of days since 1970/01/01. + * The number of days are calculated with pure Gregorian calendar + * based. + */ + private final long gregorianEpochDay; + /** + * True if year is leap year. + */ + private final transient boolean isLeapYear; + + //------------------------------------------------------------------------- + /** + * Obtains an instance of {@code HijrahDate} from the Hijrah era year, + * month-of-year and day-of-month. This uses the Hijrah era. + * + * @param prolepticYear the proleptic year to represent in the Hijrah + * @param monthOfYear the month-of-year to represent, from 1 to 12 + * @param dayOfMonth the day-of-month to represent, from 1 to 30 + * @return the Hijrah date, never null + * @throws DateTimeException if the value of any field is out of range + */ + static HijrahDate of(HijrahChrono chrono, int prolepticYear, int monthOfYear, int dayOfMonth) { + return (prolepticYear >= 1) ? + HijrahDate.of(chrono, HijrahEra.AH, prolepticYear, monthOfYear, dayOfMonth) : + HijrahDate.of(chrono, HijrahEra.BEFORE_AH, 1 - prolepticYear, monthOfYear, dayOfMonth); + } + + /** + * Obtains an instance of {@code HijrahDate} from the era, year-of-era + * month-of-year and day-of-month. + * + * @param era the era to represent, not null + * @param yearOfEra the year-of-era to represent, from 1 to 9999 + * @param monthOfYear the month-of-year to represent, from 1 to 12 + * @param dayOfMonth the day-of-month to represent, from 1 to 31 + * @return the Hijrah date, never null + * @throws DateTimeException if the value of any field is out of range + */ + private static HijrahDate of(HijrahChrono chrono, HijrahEra era, int yearOfEra, int monthOfYear, int dayOfMonth) { + Objects.requireNonNull(era, "era"); + chrono.checkValidYearOfEra(yearOfEra); + chrono.checkValidMonth(monthOfYear); + chrono.checkValidDayOfMonth(dayOfMonth); + long gregorianDays = chrono.getGregorianEpochDay(era.prolepticYear(yearOfEra), monthOfYear, dayOfMonth); + return new HijrahDate(chrono, gregorianDays); + } + + /** + * Obtains an instance of {@code HijrahDate} from a date. + * + * @param date the date to use, not null + * @return the Hijrah date, never null + * @throws DateTimeException if the year is invalid + */ + private static HijrahDate of(HijrahChrono chrono, LocalDate date) { + long gregorianDays = date.toEpochDay(); + return new HijrahDate(chrono, gregorianDays); + } + + static HijrahDate ofEpochDay(HijrahChrono chrono, long epochDay) { + return new HijrahDate(chrono, epochDay); + } + + /** + * Constructs an instance with the specified date. + * + * @param gregorianDay the number of days from 0001/01/01 (Gregorian), caller calculated + */ + private HijrahDate(HijrahChrono chrono, long gregorianDay) { + this.chrono = chrono; + int[] dateInfo = chrono.getHijrahDateInfo(gregorianDay); + + chrono.checkValidYearOfEra(dateInfo[1]); + chrono.checkValidMonth(dateInfo[2]); + chrono.checkValidDayOfMonth(dateInfo[3]); + chrono.checkValidDayOfYear(dateInfo[4]); + + this.era = HijrahEra.of(dateInfo[0]); + this.yearOfEra = dateInfo[1]; + this.monthOfYear = dateInfo[2]; + this.dayOfMonth = dateInfo[3]; + this.dayOfYear = dateInfo[4]; + this.dayOfWeek = DayOfWeek.of(dateInfo[5]); + this.gregorianEpochDay = gregorianDay; + this.isLeapYear = chrono.isLeapYear(this.yearOfEra); + } + + //----------------------------------------------------------------------- + @Override + public HijrahChrono getChrono() { + return chrono; + } + + @Override + public ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + if (isSupported(field)) { + ChronoField f = (ChronoField) field; + switch (f) { + case DAY_OF_MONTH: return ValueRange.of(1, lengthOfMonth()); + case DAY_OF_YEAR: return ValueRange.of(1, lengthOfYear()); + case ALIGNED_WEEK_OF_MONTH: return ValueRange.of(1, 5); // TODO + case YEAR_OF_ERA: return ValueRange.of(1, 1000); // TODO + } + return getChrono().range(f); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doRange(this); + } + + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case DAY_OF_WEEK: return dayOfWeek.getValue(); + case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((dayOfWeek.getValue() - 1) % 7) + 1; + case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((dayOfYear - 1) % 7) + 1; + case DAY_OF_MONTH: return this.dayOfMonth; + case DAY_OF_YEAR: return this.dayOfYear; + case EPOCH_DAY: return toEpochDay(); + case ALIGNED_WEEK_OF_MONTH: return ((dayOfMonth - 1) / 7) + 1; + case ALIGNED_WEEK_OF_YEAR: return ((dayOfYear - 1) / 7) + 1; + case MONTH_OF_YEAR: return monthOfYear; + case YEAR_OF_ERA: return yearOfEra; + case YEAR: return yearOfEra; + case ERA: return era.getValue(); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doGet(this); + } + + @Override + public HijrahDate with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + f.checkValidValue(newValue); // TODO: validate value + int nvalue = (int) newValue; + switch (f) { + case DAY_OF_WEEK: return plusDays(newValue - dayOfWeek.getValue()); + case ALIGNED_DAY_OF_WEEK_IN_MONTH: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_MONTH)); + case ALIGNED_DAY_OF_WEEK_IN_YEAR: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_YEAR)); + case DAY_OF_MONTH: return resolvePreviousValid(yearOfEra, monthOfYear, nvalue); + case DAY_OF_YEAR: return resolvePreviousValid(yearOfEra, ((nvalue - 1) / 30) + 1, ((nvalue - 1) % 30) + 1); + case EPOCH_DAY: return new HijrahDate(chrono, nvalue); + case ALIGNED_WEEK_OF_MONTH: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_MONTH)) * 7); + case ALIGNED_WEEK_OF_YEAR: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_YEAR)) * 7); + case MONTH_OF_YEAR: return resolvePreviousValid(yearOfEra, nvalue, dayOfMonth); + case YEAR_OF_ERA: return resolvePreviousValid(yearOfEra >= 1 ? nvalue : 1 - nvalue, monthOfYear, dayOfMonth); + case YEAR: return resolvePreviousValid(nvalue, monthOfYear, dayOfMonth); + case ERA: return resolvePreviousValid(1 - yearOfEra, monthOfYear, dayOfMonth); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return (HijrahDate) ChronoLocalDate.super.with(field, newValue); + } + + private HijrahDate resolvePreviousValid(int yearOfEra, int month, int day) { + int monthDays = getMonthDays(month - 1, yearOfEra); + if (day > monthDays) { + day = monthDays; + } + return HijrahDate.of(chrono, yearOfEra, month, day); + } + + @Override + public long toEpochDay() { + return chrono.getGregorianEpochDay(yearOfEra, monthOfYear, dayOfMonth); + } + + //----------------------------------------------------------------------- + @Override + public HijrahEra getEra() { + return this.era; + } + + //----------------------------------------------------------------------- + /** + * Checks if the year is a leap year, according to the Hijrah calendar system rules. + * + * @return true if this date is in a leap year + */ + @Override + public boolean isLeapYear() { + return this.isLeapYear; + } + + //----------------------------------------------------------------------- + @Override + public HijrahDate plusYears(long years) { + if (years == 0) { + return this; + } + int newYear = Math.addExact(this.yearOfEra, (int)years); + return HijrahDate.of(chrono, this.era, newYear, this.monthOfYear, this.dayOfMonth); + } + + @Override + public HijrahDate plusMonths(long months) { + if (months == 0) { + return this; + } + int newMonth = this.monthOfYear - 1; + newMonth = newMonth + (int)months; + int years = newMonth / 12; + newMonth = newMonth % 12; + while (newMonth < 0) { + newMonth += 12; + years = Math.subtractExact(years, 1); + } + int newYear = Math.addExact(this.yearOfEra, years); + return HijrahDate.of(chrono, this.era, newYear, newMonth + 1, this.dayOfMonth); + } + + @Override + public HijrahDate plusDays(long days) { + return new HijrahDate(chrono, this.gregorianEpochDay + days); + } + + /** + * Returns month days from the beginning of year. + * + * @param month month (0-based) + * @parma year year + * @return month days from the beginning of year + */ + private int getMonthDays(int month, int year) { + int[] newMonths = chrono.getAdjustedMonthDays(year); + return newMonths[month]; + } + + /** + * Returns month length. + * + * @param month month (0-based) + * @param year year + * @return month length + */ + private int getMonthLength(int month, int year) { + int[] newMonths = chrono.getAdjustedMonthLength(year); + return newMonths[month]; + } + + @Override + public int lengthOfMonth() { + return getMonthLength(monthOfYear - 1, yearOfEra); + } + + @Override + public int lengthOfYear() { + return chrono.getYearLength(yearOfEra); // TODO: proleptic year + } + + //----------------------------------------------------------------------- + private Object writeReplace() { + return new Ser(Ser.HIJRAH_DATE_TYPE, this); + } + + void writeExternal(ObjectOutput out) throws IOException { + // HijrahChrono is implicit in the Hijrah_DATE_TYPE + out.writeObject(chrono); + out.writeInt(get(YEAR)); + out.writeByte(get(MONTH_OF_YEAR)); + out.writeByte(get(DAY_OF_MONTH)); + } + + /** + * Replaces the date instance from the stream with a valid one. + * ReadExternal has already read the fields and created a new instance + * from the data. + * + * @return the resolved date, never null + */ + private Object readResolve() { + return this; + } + + static ChronoLocalDate readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + HijrahChrono chrono = (HijrahChrono)in.readObject(); + int year = in.readInt(); + int month = in.readByte(); + int dayOfMonth = in.readByte(); + return chrono.date(year, month, dayOfMonth); + } + +} diff --git a/src/share/classes/java/time/calendar/HijrahDeviationReader.java b/src/share/classes/java/time/calendar/HijrahDeviationReader.java new file mode 100644 index 0000000000000000000000000000000000000000..470448394a165a27d5c018d564d6af2838799b54 --- /dev/null +++ b/src/share/classes/java/time/calendar/HijrahDeviationReader.java @@ -0,0 +1,246 @@ +/* + * 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 java.time.calendar; + + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.FileSystems; +import java.security.AccessController; +import java.text.ParseException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatters; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.util.Arrays; +import java.util.function.Block; + +/** + * A reader for Hijrah Deviation files. + *

+ * For each Hijrah calendar a deviation file is used + * to modify the initial Hijrah calendar by changing the length of + * individual months. + *

+ * The default location of the deviation files is + * {@code + FILE_SEP + "lib"}. + * The deviation files are named {@code "hijrah_" + ID}. + *

+ * The deviation file for a calendar can be overridden by defining the + * property {@code java.time.calendar.HijrahChrono.File.hijrah_} + * with the full pathname of the deviation file. + *

+ * The deviation file is read line by line: + *

    + *
  • The "#" character begins a comment - + * all characters including and after the "#" on the line are ignored
  • + *
  • Valid lines contain two fields, separated by whitespace, whitespace + * is otherwise ignored.
  • + *
  • The first field is a LocalDate using the format "MMM-dd-yyyy"; + * The LocalDate must be converted to a HijrahDate using + * the {@code islamic} calendar.
  • + *
  • The second field is the offset, +2, +1, -1, -2 as parsed + * by Integer.valueOf to modify the length of the Hijrah month.
  • + *
  • Empty lines are ignore.
  • + *
  • Exceptions are throw for invalid formatted dates and offset, + * and other I/O errors on the file. + *
+ *

Example:

+ *
# Deviation data for islamicc calendar
+ * Mar-23-2012 -1
+ * Apr-22-2012 +1
+ * May-21-2012 -1
+ * Dec-14-2012 +1
+ * 
+ * + * @since 1.8 + */ +final class HijrahDeviationReader { + + /** + * Default prefix for name of deviation file; suffix is typeId. + */ + private static final String DEFAULT_CONFIG_FILE_PREFIX = "hijrah_"; + + /** + * Read Hijrah_deviation.cfg file. The config file contains the deviation + * data with format defined in the class javadoc. + * + * @param typeId the name of the calendar + * @param calendarType the calendar type + * @return {@code true} if the file was read and each entry accepted by the + * Block; else {@code false} no configuration was done + * + * @throws IOException for zip/jar file handling exception. + * @throws ParseException if the format of the configuration file is wrong. + */ + static boolean readDeviation(String typeId, String calendarType, + Block block) throws IOException, ParseException { + InputStream is = getConfigFileInputStream(typeId); + if (is != null) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) { + String line = ""; + int num = 0; + while ((line = br.readLine()) != null) { + num++; + HijrahChrono.Deviation entry = parseLine(line, num); + if (entry != null) { + block.accept(entry); + } + } + } + return true; + } + return false; + } + + /** + * Parse each deviation element. + * + * @param line a line to parse + * @param num line number + * @return an Entry or null if the line is empty. + * @throws ParseException if line has incorrect format. + */ + private static HijrahChrono.Deviation parseLine(final String line, final int num) throws ParseException { + int hash = line.indexOf("#"); + String nocomment = (hash < 0) ? line : line.substring(0, hash); + String[] split = nocomment.split("\\s"); + if (split.length == 0 || split[0].isEmpty()) { + return null; // Nothing to parse + } + if (split.length != 2) { + throw new ParseException("Less than 2 tokens on line : " + line + Arrays.toString(split) + ", split.length: " + split.length, num); + } + + //element [0] is a date + //element [1] is the offset + + LocalDate isoDate = DateTimeFormatters.pattern("MMM-dd-yyyy").parse(split[0], LocalDate::from); + int offset = Integer.valueOf(split[1]); + + // Convert date to HijrahDate using the default Islamic Calendar + + ChronoLocalDate hijrahDate = HijrahChrono.INSTANCE.date(isoDate); + + int year = hijrahDate.get(ChronoField.YEAR); + int month = hijrahDate.get(ChronoField.MONTH_OF_YEAR); + return new HijrahChrono.Deviation(year, month, year, month, offset); + } + + + /** + * Return InputStream for deviation configuration file. The default location + * of the deviation file is: + *
+     *   $CLASSPATH/java/time/calendar
+     * 
And the default file name is: + *
+     *   hijrah_ + typeId + .cfg
+     * 
The default location and file name can be overridden by setting + * following two Java system properties. + *
+     *   Location: java.time.calendar.HijrahDate.deviationConfigDir
+     *   File name: java.time.calendar.HijrahDate.File. + typeid
+     * 
Regarding the file format, see readDeviationConfig() method for + * details. + * + * @param typeId the name of the calendar deviation data + * @return InputStream for file reading. + * @throws IOException for zip/jar file handling exception. + */ + private static InputStream getConfigFileInputStream(final String typeId) throws IOException { + try { + InputStream stream = + (InputStream)AccessController + .doPrivileged((java.security.PrivilegedExceptionAction) () -> { + String propFilename = "java.time.calendar.HijrahChrono.File." + typeId; + String filename = System.getProperty(propFilename); + File file = null; + if (filename != null) { + file = new File(filename); + } else { + String libDir = System.getProperty("java.home") + File.separator + "lib"; + try { + libDir = FileSystems.getDefault().getPath(libDir).toRealPath().toString(); + } catch(Exception e) {} + filename = DEFAULT_CONFIG_FILE_PREFIX + typeId + ".cfg"; + file = new File(libDir, filename); + } + + if (file.exists()) { + try { + return new FileInputStream(file); + } catch (IOException ioe) { + throw ioe; + } + } else { + return null; + } + }); + return stream; + } catch (Exception ex) { + ex.printStackTrace(); + // Not working + return null; + } + } +} diff --git a/src/share/classes/java/time/calendar/HijrahEra.java b/src/share/classes/java/time/calendar/HijrahEra.java new file mode 100644 index 0000000000000000000000000000000000000000..5c622f78e8e1a7f7f06ed037e75371b5dffe12d5 --- /dev/null +++ b/src/share/classes/java/time/calendar/HijrahEra.java @@ -0,0 +1,227 @@ +/* + * 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 java.time.calendar; + +import static java.time.temporal.ChronoField.ERA; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.time.DateTimeException; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.TextStyle; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.Era; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalField; +import java.time.temporal.ValueRange; +import java.util.Locale; + +/** + * An era in the Hijrah calendar system. + *

+ * The Hijrah calendar system has two eras. + * The date {@code 0001-01-01 (Hijrah)} is {@code 622-06-19 (ISO)}. + *

+ * Do not use {@code ordinal()} to obtain the numeric representation of {@code HijrahEra}. + * Use {@code getValue()} instead. + * + *

Specification for implementors

+ * This is an immutable and thread-safe enum. + * + * @since 1.8 + */ +enum HijrahEra implements Era { + + /** + * The singleton instance for the era before the current one, 'Before Anno Hegirae', + * which has the value 0. + */ + BEFORE_AH, + /** + * The singleton instance for the current era, 'Anno Hegirae', which has the value 1. + */ + AH; + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code HijrahEra} from a value. + *

+ * The current era (from ISO date 622-06-19 onwards) has the value 1 + * The previous era has the value 0. + * + * @param hijrahEra the era to represent, from 0 to 1 + * @return the HijrahEra singleton, never null + * @throws DateTimeException if the era is invalid + */ + public static HijrahEra of(int hijrahEra) { + switch (hijrahEra) { + case 0: + return BEFORE_AH; + case 1: + return AH; + default: + throw new DateTimeException("HijrahEra not valid"); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the era numeric value. + *

+ * The current era (from ISO date 622-06-19 onwards) has the value 1. + * The previous era has the value 0. + * + * @return the era value, from 0 (BEFORE_AH) to 1 (AH) + */ + @Override + public int getValue() { + return ordinal(); + } + + @Override + public HijrahChrono getChrono() { + return HijrahChrono.INSTANCE; + } + + // JDK8 default methods: + //----------------------------------------------------------------------- + @Override + public ChronoLocalDate date(int year, int month, int day) { + return getChrono().date(this, year, month, day); + } + + @Override + public ChronoLocalDate dateYearDay(int year, int dayOfYear) { + return getChrono().dateYearDay(this, year, dayOfYear); + } + + //----------------------------------------------------------------------- + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == ERA; + } + return field != null && field.doIsSupported(this); + } + + @Override + public ValueRange range(TemporalField field) { + if (field == ERA) { + return field.range(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doRange(this); + } + + @Override + public int get(TemporalField field) { + if (field == ERA) { + return getValue(); + } + return range(field).checkValidIntValue(getLong(field), field); + } + + @Override + public long getLong(TemporalField field) { + if (field == ERA) { + return getValue(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doGet(this); + } + + //------------------------------------------------------------------------- + @Override + public Temporal adjustInto(Temporal temporal) { + return temporal.with(ERA, getValue()); + } + + //----------------------------------------------------------------------- + @Override + public String getText(TextStyle style, Locale locale) { + return new DateTimeFormatterBuilder().appendText(ERA, style).toFormatter(locale).print(this); + } + + /** + * Returns the proleptic year from this era and year of era. + * + * @param yearOfEra the year of Era + * @return the computed prolepticYear + */ + int prolepticYear(int yearOfEra) { + return (this == HijrahEra.AH ? yearOfEra : 1 - yearOfEra); + } + + //----------------------------------------------------------------------- + private Object writeReplace() { + return new Ser(Ser.HIJRAH_ERA_TYPE, this); + } + + void writeExternal(DataOutput out) throws IOException { + out.writeByte(this.getValue()); + } + + static HijrahEra readExternal(DataInput in) throws IOException { + byte eraValue = in.readByte(); + return HijrahEra.of(eraValue); + } + +} diff --git a/src/share/classes/java/time/calendar/JapaneseChrono.java b/src/share/classes/java/time/calendar/JapaneseChrono.java new file mode 100644 index 0000000000000000000000000000000000000000..12073c66b79a211d1f1dc2b1238df2a9ba087898 --- /dev/null +++ b/src/share/classes/java/time/calendar/JapaneseChrono.java @@ -0,0 +1,321 @@ +/* + * 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 java.time.calendar; + +import java.io.Serializable; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.Era; +import java.time.temporal.ISOChrono; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.ValueRange; +import java.time.temporal.Year; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import sun.util.calendar.CalendarSystem; +import sun.util.calendar.LocalGregorianCalendar; + +/** + * The Japanese Imperial calendar system. + *

+ * This chronology defines the rules of the Japanese Imperial calendar system. + * This calendar system is primarily used in Japan. + * The Japanese Imperial calendar system is the same as the ISO calendar system + * apart from the era-based year numbering. + *

+ * Only Meiji (1865-04-07 - 1868-09-07) and later eras are supported. + * Older eras are handled as an unknown era where the year-of-era is the ISO year. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class JapaneseChrono extends Chrono implements Serializable { + // TODO: definition for unknown era may break requirement that year-of-era >= 1 + + static final LocalGregorianCalendar JCAL = + (LocalGregorianCalendar) CalendarSystem.forName("japanese"); + + // Locale for creating a JapaneseImpericalCalendar. + static final Locale LOCALE = Locale.forLanguageTag("ja-JP-u-ca-japanese"); + + /** + * Singleton instance for Japanese chronology. + */ + public static final JapaneseChrono INSTANCE = new JapaneseChrono(); + + /** + * The singleton instance for the before Meiji era ( - 1868-09-07) + * which has the value -999. + */ + public static final Era ERA_SEIREKI = JapaneseEra.SEIREKI; + /** + * The singleton instance for the Meiji era (1868-09-08 - 1912-07-29) + * which has the value -1. + */ + public static final Era ERA_MEIJI = JapaneseEra.MEIJI; + /** + * The singleton instance for the Taisho era (1912-07-30 - 1926-12-24) + * which has the value 0. + */ + public static final Era ERA_TAISHO = JapaneseEra.TAISHO; + /** + * The singleton instance for the Showa era (1926-12-25 - 1989-01-07) + * which has the value 1. + */ + public static final Era ERA_SHOWA = JapaneseEra.SHOWA; + /** + * The singleton instance for the Heisei era (1989-01-08 - current) + * which has the value 2. + */ + public static final Era ERA_HEISEI = JapaneseEra.HEISEI; + /** + * Serialization version. + */ + private static final long serialVersionUID = 459996390165777884L; + + //----------------------------------------------------------------------- + /** + * Restricted constructor. + */ + private JapaneseChrono() { + } + + /** + * Resolve singleton. + * + * @return the singleton instance, not null + */ + private Object readResolve() { + return INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the ID of the chronology - 'Japanese'. + *

+ * The ID uniquely identifies the {@code Chrono}. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * + * @return the chronology ID - 'Japanese' + * @see #getCalendarType() + */ + @Override + public String getId() { + return "Japanese"; + } + + /** + * Gets the calendar type of the underlying calendar system - 'japanese'. + *

+ * The calendar type is an identifier defined by the + * Unicode Locale Data Markup Language (LDML) specification. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * It can also be used as part of a locale, accessible via + * {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'. + * + * @return the calendar system type - 'japanese' + * @see #getId() + */ + @Override + public String getCalendarType() { + return "japanese"; + } + + //----------------------------------------------------------------------- + @Override + public ChronoLocalDate date(Era era, int yearOfEra, int month, int dayOfMonth) { + if (era instanceof JapaneseEra == false) { + throw new DateTimeException("Era must be JapaneseEra"); + } + return JapaneseDate.of((JapaneseEra) era, yearOfEra, month, dayOfMonth); + } + + @Override + public ChronoLocalDate date(int prolepticYear, int month, int dayOfMonth) { + return new JapaneseDate(LocalDate.of(prolepticYear, month, dayOfMonth)); + } + + @Override + public ChronoLocalDate dateYearDay(int prolepticYear, int dayOfYear) { + LocalDate date = LocalDate.ofYearDay(prolepticYear, dayOfYear); + return date(prolepticYear, date.getMonthValue(), date.getDayOfMonth()); + } + + @Override + public ChronoLocalDate date(TemporalAccessor temporal) { + if (temporal instanceof JapaneseDate) { + return (JapaneseDate) temporal; + } + return new JapaneseDate(LocalDate.from(temporal)); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified year is a leap year. + *

+ * Japanese calendar leap years occur exactly in line with ISO leap years. + * This method does not validate the year passed in, and only has a + * well-defined result for years in the supported range. + * + * @param prolepticYear the proleptic-year to check, not validated for range + * @return true if the year is a leap year + */ + @Override + public boolean isLeapYear(long prolepticYear) { + return ISOChrono.INSTANCE.isLeapYear(prolepticYear); + } + + @Override + public int prolepticYear(Era era, int yearOfEra) { + if (era instanceof JapaneseEra == false) { + throw new DateTimeException("Era must be JapaneseEra"); + } + JapaneseEra jera = (JapaneseEra) era; + int gregorianYear = jera.getPrivateEra().getSinceDate().getYear() + yearOfEra - 1; + if (yearOfEra == 1) { + return gregorianYear; + } + LocalGregorianCalendar.Date jdate = JCAL.newCalendarDate(null); + jdate.setEra(jera.getPrivateEra()).setDate(yearOfEra, 1, 1); + JCAL.normalize(jdate); + if (jdate.getNormalizedYear() == gregorianYear) { + return gregorianYear; + } + throw new DateTimeException("invalid yearOfEra value"); + } + + /** + * Returns the calendar system era object from the given numeric value. + * + * See the description of each Era for the numeric values of: + * {@link #ERA_HEISEI}, {@link #ERA_SHOWA},{@link #ERA_TAISHO}, + * {@link #ERA_MEIJI}), only Meiji and later eras are supported. + * Prior to Meiji {@link #ERA_SEIREKI} is used. + * + * @param eraValue the era value + * @return the Japanese {@code Era} for the given numeric era value + * @throws DateTimeException if {@code eraValue} is invalid + */ + @Override + public Era eraOf(int eraValue) { + return JapaneseEra.of(eraValue); + } + + @Override + public List> eras() { + return Arrays.>asList(JapaneseEra.values()); + } + + //----------------------------------------------------------------------- + @Override + public ValueRange range(ChronoField field) { + switch (field) { + case DAY_OF_MONTH: + case DAY_OF_WEEK: + case MICRO_OF_DAY: + case MICRO_OF_SECOND: + case HOUR_OF_DAY: + case HOUR_OF_AMPM: + case MINUTE_OF_DAY: + case MINUTE_OF_HOUR: + case SECOND_OF_DAY: + case SECOND_OF_MINUTE: + case MILLI_OF_DAY: + case MILLI_OF_SECOND: + case NANO_OF_DAY: + case NANO_OF_SECOND: + case CLOCK_HOUR_OF_DAY: + case CLOCK_HOUR_OF_AMPM: + case EPOCH_DAY: + case EPOCH_MONTH: + return field.range(); + } + Calendar jcal = Calendar.getInstance(LOCALE); + int fieldIndex; + switch (field) { + case ERA: + return ValueRange.of(jcal.getMinimum(Calendar.ERA) - JapaneseEra.ERA_OFFSET, + jcal.getMaximum(Calendar.ERA) - JapaneseEra.ERA_OFFSET); + case YEAR: + case YEAR_OF_ERA: + return ValueRange.of(Year.MIN_VALUE, jcal.getGreatestMinimum(Calendar.YEAR), + jcal.getLeastMaximum(Calendar.YEAR), Year.MAX_VALUE); + case MONTH_OF_YEAR: + return ValueRange.of(jcal.getMinimum(Calendar.MONTH) + 1, jcal.getGreatestMinimum(Calendar.MONTH) + 1, + jcal.getLeastMaximum(Calendar.MONTH) + 1, jcal.getMaximum(Calendar.MONTH) + 1); + case DAY_OF_YEAR: + fieldIndex = Calendar.DAY_OF_YEAR; + break; + default: + // TODO: review the remaining fields + throw new UnsupportedOperationException("Unimplementable field: " + field); + } + return ValueRange.of(jcal.getMinimum(fieldIndex), jcal.getGreatestMinimum(fieldIndex), + jcal.getLeastMaximum(fieldIndex), jcal.getMaximum(fieldIndex)); + } + +} diff --git a/src/share/classes/java/time/calendar/JapaneseDate.java b/src/share/classes/java/time/calendar/JapaneseDate.java new file mode 100644 index 0000000000000000000000000000000000000000..d923c88f1c66c679e4989fd0aebfa7a5c95c88f7 --- /dev/null +++ b/src/share/classes/java/time/calendar/JapaneseDate.java @@ -0,0 +1,377 @@ +/* + * 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 java.time.calendar; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.TemporalField; +import java.time.temporal.ValueRange; +import java.util.Calendar; +import java.util.Objects; + +import sun.util.calendar.LocalGregorianCalendar; + +/** + * A date in the Japanese Imperial calendar system. + *

+ * This implements {@code ChronoLocalDate} for the + * {@linkplain JapaneseChrono Japanese Imperial calendar}. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +final class JapaneseDate + extends ChronoDateImpl + implements ChronoLocalDate, Serializable { + // this class is package-scoped so that future conversion to public + // would not change serialization + + /** + * Serialization version. + */ + private static final long serialVersionUID = -305327627230580483L; + + /** + * The underlying ISO local date. + */ + private transient final LocalDate isoDate; + /** + * The JapaneseEra of this date. + */ + private transient JapaneseEra era; + /** + * The Japanese imperial calendar year of this date. + */ + private transient int yearOfEra; + + /** + * Obtains an instance of {@code JapaneseDate} from the era, year-of-era, + * month-of-year and day-of-month. + * + * @param era the era to represent, not null + * @param yearOfEra the year-of-era to represent + * @param month the month-of-year to represent + * @param dayOfMonth the day-of-month to represent, from 1 to 31 + * @return the Japanese date, never null + * @throws DateTimeException if the value of any field is out of range, or + * if the day-of-month is invalid for the month-year + */ + static JapaneseDate of(JapaneseEra era, int yearOfEra, int month, int dayOfMonth) { + Objects.requireNonNull(era, "era"); + LocalGregorianCalendar.Date jdate = JapaneseChrono.JCAL.newCalendarDate(null); + jdate.setEra(era.getPrivateEra()).setDate(yearOfEra, month, dayOfMonth); + if (!JapaneseChrono.JCAL.validate(jdate)) { + throw new IllegalArgumentException(); + } + LocalDate date = LocalDate.of(jdate.getNormalizedYear(), month, dayOfMonth); + return new JapaneseDate(era, yearOfEra, date); + } + + //----------------------------------------------------------------------- + /** + * Creates an instance from an ISO date. + * + * @param isoDate the standard local date, validated not null + */ + JapaneseDate(LocalDate isoDate) { + LocalGregorianCalendar.Date jdate = toPrivateJapaneseDate(isoDate); + this.era = JapaneseEra.toJapaneseEra(jdate.getEra()); + this.yearOfEra = jdate.getYear(); + this.isoDate = isoDate; + } + + /** + * Constructs a {@code JapaneseDate}. This constructor does NOT validate the given parameters, + * and {@code era} and {@code year} must agree with {@code isoDate}. + * + * @param era the era, validated not null + * @param year the year-of-era, validated + * @param isoDate the standard local date, validated not null + */ + JapaneseDate(JapaneseEra era, int year, LocalDate isoDate) { + this.era = era; + this.yearOfEra = year; + this.isoDate = isoDate; + } + + //----------------------------------------------------------------------- + @Override + public JapaneseChrono getChrono() { + return JapaneseChrono.INSTANCE; + } + + @Override + public int lengthOfMonth() { + return isoDate.lengthOfMonth(); + } + + @Override + public ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + if (isSupported(field)) { + ChronoField f = (ChronoField) field; + switch (f) { + case DAY_OF_YEAR: + return actualRange(Calendar.DAY_OF_YEAR); + case YEAR_OF_ERA: + return actualRange(Calendar.YEAR); + } + return getChrono().range(f); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doRange(this); + } + + private ValueRange actualRange(int calendarField) { + Calendar jcal = Calendar.getInstance(JapaneseChrono.LOCALE); + jcal.set(Calendar.ERA, era.getValue() + JapaneseEra.ERA_OFFSET); + jcal.set(yearOfEra, isoDate.getMonthValue() - 1, isoDate.getDayOfMonth()); + return ValueRange.of(jcal.getActualMinimum(calendarField), + jcal.getActualMaximum(calendarField)); + } + + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case YEAR_OF_ERA: + return yearOfEra; + case ERA: + return era.getValue(); + case DAY_OF_YEAR: { + LocalGregorianCalendar.Date jdate = toPrivateJapaneseDate(isoDate); + return JapaneseChrono.JCAL.getDayOfYear(jdate); + } + } + // TODO: review other fields + return isoDate.getLong(field); + } + return field.doGet(this); + } + + /** + * Returns a {@code LocalGregorianCalendar.Date} converted from the given {@code isoDate}. + * + * @param isoDate the local date, not null + * @return a {@code LocalGregorianCalendar.Date}, not null + */ + private static LocalGregorianCalendar.Date toPrivateJapaneseDate(LocalDate isoDate) { + LocalGregorianCalendar.Date jdate = JapaneseChrono.JCAL.newCalendarDate(null); + sun.util.calendar.Era sunEra = JapaneseEra.privateEraFrom(isoDate); + int year = isoDate.getYear(); + if (sunEra != null) { + year -= sunEra.getSinceDate().getYear() - 1; + } + jdate.setEra(sunEra).setYear(year).setMonth(isoDate.getMonthValue()).setDayOfMonth(isoDate.getDayOfMonth()); + JapaneseChrono.JCAL.normalize(jdate); + return jdate; + } + + //----------------------------------------------------------------------- + @Override + public JapaneseDate with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + if (getLong(f) == newValue) { + return this; + } + switch (f) { + case YEAR_OF_ERA: + case YEAR: + case ERA: { + f.checkValidValue(newValue); + int nvalue = (int) newValue; + switch (f) { + case YEAR_OF_ERA: + return this.withYear(nvalue); + case YEAR: + return with(isoDate.withYear(nvalue)); + case ERA: { + return this.withYear(JapaneseEra.of(nvalue), yearOfEra); + } + } + } + } + // TODO: review other fields, such as WEEK_OF_YEAR + return with(isoDate.with(field, newValue)); + } + return (JapaneseDate) ChronoLocalDate.super.with(field, newValue); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date with the year altered. + *

+ * This method changes the year of the date. + * If the month-day is invalid for the year, then the previous valid day + * will be selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param era the era to set in the result, not null + * @param yearOfEra the year-of-era to set in the returned date + * @return a {@code JapaneseDate} based on this date with the requested year, never null + * @throws DateTimeException if {@code year} is invalid + */ + private JapaneseDate withYear(JapaneseEra era, int yearOfEra) { + int year = JapaneseChrono.INSTANCE.prolepticYear(era, yearOfEra); + return with(isoDate.withYear(year)); + } + + /** + * Returns a copy of this date with the year-of-era altered. + *

+ * This method changes the year-of-era of the date. + * If the month-day is invalid for the year, then the previous valid day + * will be selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param year the year to set in the returned date + * @return a {@code JapaneseDate} based on this date with the requested year-of-era, never null + * @throws DateTimeException if {@code year} is invalid + */ + private JapaneseDate withYear(int year) { + return withYear((JapaneseEra) getEra(), year); + } + + //----------------------------------------------------------------------- + @Override + JapaneseDate plusYears(long years) { + return with(isoDate.plusYears(years)); + } + + @Override + JapaneseDate plusMonths(long months) { + return with(isoDate.plusMonths(months)); + } + + @Override + JapaneseDate plusDays(long days) { + return with(isoDate.plusDays(days)); + } + + private JapaneseDate with(LocalDate newDate) { + return (newDate.equals(isoDate) ? this : new JapaneseDate(newDate)); + } + + @Override // override for performance + public long toEpochDay() { + return isoDate.toEpochDay(); + } + + //------------------------------------------------------------------------- + @Override // override for performance + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof JapaneseDate) { + JapaneseDate otherDate = (JapaneseDate) obj; + return this.isoDate.equals(otherDate.isoDate); + } + return false; + } + + @Override // override for performance + public int hashCode() { + return getChrono().getId().hashCode() ^ isoDate.hashCode(); + } + + @Override + public String toString() { + if (era == JapaneseEra.SEIREKI) { + return getChrono().getId() + " " + isoDate.toString(); + } + return super.toString(); + } + + //----------------------------------------------------------------------- + private Object writeReplace() { + return new Ser(Ser.JAPANESE_DATE_TYPE, this); + } + + void writeExternal(DataOutput out) throws IOException { + // JapaneseChrono is implicit in the JAPANESE_DATE_TYPE + out.writeInt(get(YEAR)); + out.writeByte(get(MONTH_OF_YEAR)); + out.writeByte(get(DAY_OF_MONTH)); + } + + static ChronoLocalDate readExternal(DataInput in) throws IOException { + int year = in.readInt(); + int month = in.readByte(); + int dayOfMonth = in.readByte(); + return JapaneseChrono.INSTANCE.date(year, month, dayOfMonth); + } + + +} diff --git a/src/share/classes/java/time/calendar/JapaneseEra.java b/src/share/classes/java/time/calendar/JapaneseEra.java new file mode 100644 index 0000000000000000000000000000000000000000..3bc4fd720f7ecfb4a612eb62170d6ad59e844461 --- /dev/null +++ b/src/share/classes/java/time/calendar/JapaneseEra.java @@ -0,0 +1,335 @@ +/* + * 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 java.time.calendar; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.temporal.Era; +import java.util.Arrays; + +import sun.util.calendar.CalendarDate; + +/** + * An era in the Japanese Imperial calendar system. + *

+ * This class defines the valid eras for the Japanese chronology. + * Only Meiji (1868-09-08 - 1912-07-29) and later eras are supported. + * Japan introduced the Gregorian calendar since Meiji 6. The dates + * between Meiji 1 - 5 are not historically correct. + * The older eras are recognized as Seireki (Western calendar) era, + * and the year of era of Seireki is proleptic Gregorian year. + * (The Julian to Gregorian transition is not supported.) + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +final class JapaneseEra + implements Era, Serializable { + + // The offset value to 0-based index from the era value. + // i.e., getValue() + ERA_OFFSET == 0-based index; except that -999 is mapped to zero + static final int ERA_OFFSET = 2; + + static final sun.util.calendar.Era[] ERA_CONFIG; + + /** + * The singleton instance for the before Meiji era ( - 1868-09-07) + * which has the value -999. + */ + public static final JapaneseEra SEIREKI = new JapaneseEra(-999, LocalDate.MIN); + /** + * The singleton instance for the 'Meiji' era (1868-09-08 - 1912-07-29) + * which has the value -1. + */ + public static final JapaneseEra MEIJI = new JapaneseEra(-1, LocalDate.of(1868, 9, 8)); + /** + * The singleton instance for the 'Taisho' era (1912-07-30 - 1926-12-24) + * which has the value 0. + */ + public static final JapaneseEra TAISHO = new JapaneseEra(0, LocalDate.of(1912, 7, 30)); + /** + * The singleton instance for the 'Showa' era (1926-12-25 - 1989-01-07) + * which has the value 1. + */ + public static final JapaneseEra SHOWA = new JapaneseEra(1, LocalDate.of(1926, 12, 25)); + /** + * The singleton instance for the 'Heisei' era (1989-01-08 - current) + * which has the value 2. + */ + public static final JapaneseEra HEISEI = new JapaneseEra(2, LocalDate.of(1989, 1, 8)); + + // the number of defined JapaneseEra constants. + // There could be an extra era defined in its configuration. + private static final int N_ERA_CONSTANTS = HEISEI.getValue() + ERA_OFFSET + 1; + + /** + * Serialization version. + */ + private static final long serialVersionUID = 1466499369062886794L; + + // array for the singleton JapaneseEra instances + private static final JapaneseEra[] KNOWN_ERAS; + + static { + sun.util.calendar.Era[] sunEras = JapaneseChrono.JCAL.getEras(); + ERA_CONFIG = new sun.util.calendar.Era[sunEras.length + 1]; + for (int i = 1; i < ERA_CONFIG.length; i++) { + ERA_CONFIG[i] = sunEras[i - 1]; + } + KNOWN_ERAS = new JapaneseEra[ERA_CONFIG.length]; + KNOWN_ERAS[0] = SEIREKI; + KNOWN_ERAS[1] = MEIJI; + KNOWN_ERAS[2] = TAISHO; + KNOWN_ERAS[3] = SHOWA; + KNOWN_ERAS[4] = HEISEI; + for (int i = N_ERA_CONSTANTS; i < ERA_CONFIG.length; i++) { + CalendarDate date = ERA_CONFIG[i].getSinceDate(); + LocalDate isoDate = LocalDate.of(date.getYear(), date.getMonth(), date.getDayOfMonth()); + KNOWN_ERAS[i] = new JapaneseEra(i - ERA_OFFSET, isoDate); + } + }; + + /** + * The era value. + * @serial + */ + private final int eraValue; + + // the first day of the era + private final transient LocalDate since; + + /** + * Creates an instance. + * + * @param eraValue the era value, validated + * @param since the date representing the first date of the era, validated not null + */ + private JapaneseEra(int eraValue, LocalDate since) { + this.eraValue = eraValue; + this.since = since; + } + + /** + * Returns the singleton {@code JapaneseEra} corresponding to this object. + * It's possible that this version of {@code JapaneseEra} doesn't support the latest era value. + * In that case, this method throws an {@code ObjectStreamException}. + * + * @return the singleton {@code JapaneseEra} for this object + * @throws ObjectStreamException if the deserialized object has any unknown numeric era value. + */ + private Object readResolve() throws ObjectStreamException { + try { + return of(eraValue); + } catch (DateTimeException e) { + InvalidObjectException ex = new InvalidObjectException("Invalid era"); + ex.initCause(e); + throw ex; + } + } + + //----------------------------------------------------------------------- + /** + * Returns the Sun private Era instance corresponding to this {@code JapaneseEra}. + * SEIREKI doesn't have its corresponding one. + * + * @return the Sun private Era instance for this {@code JapaneseEra}, + * or null for SEIREKI. + */ + sun.util.calendar.Era getPrivateEra() { + return ERA_CONFIG[ordinal(eraValue)]; + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code JapaneseEra} from a value. + *

+ * The {@link #SHOWA} era that contains 1970-01-01 (ISO calendar system) has the value 1 + * Later era is numbered 2 ({@link #HEISEI}). Earlier eras are numbered 0 ({@link #TAISHO}), + * -1 ({@link #MEIJI}), only Meiji and later eras are supported. The prior to Meiji, + * {@link #SEIREKI} is used. + * + * @param japaneseEra the era to represent + * @return the {@code JapaneseEra} singleton, never null + * @throws DateTimeException if {@code japaneseEra} is invalid + */ + public static JapaneseEra of(int japaneseEra) { + if (japaneseEra != SEIREKI.eraValue && + (japaneseEra < MEIJI.eraValue || japaneseEra > HEISEI.eraValue)) { + throw new DateTimeException("japaneseEra is invalid"); + } + return KNOWN_ERAS[ordinal(japaneseEra)]; + } + + /** + * Returns an array of JapaneseEras. + * @return an array of JapaneseEras + */ + static JapaneseEra[] values() { + return Arrays.copyOf(KNOWN_ERAS, KNOWN_ERAS.length); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code JapaneseEra} from a date. + * + * @param date the date, not null + * @return the Era singleton, never null + */ + static JapaneseEra from(LocalDate date) { + for (int i = KNOWN_ERAS.length - 1; i > 0; i--) { + JapaneseEra era = KNOWN_ERAS[i]; + if (date.compareTo(era.since) >= 0) { + return era; + } + } + return SEIREKI; + } + + static JapaneseEra toJapaneseEra(sun.util.calendar.Era privateEra) { + for (int i = ERA_CONFIG.length - 1; i > 0; i--) { + if (ERA_CONFIG[i].equals(privateEra)) { + return KNOWN_ERAS[i]; + } + } + return SEIREKI; + } + + static sun.util.calendar.Era privateEraFrom(LocalDate isoDate) { + for (int i = KNOWN_ERAS.length - 1; i > 0; i--) { + JapaneseEra era = KNOWN_ERAS[i]; + if (isoDate.compareTo(era.since) >= 0) { + return ERA_CONFIG[i]; + } + } + return null; + } + + /** + * Returns the index into the arrays from the Era value. + * the eraValue is a valid Era number, -999, -1..2. + * @param eravalue the era value to convert to the index + * @return the index of the current Era + */ + private static int ordinal(int eravalue) { + return (eravalue == SEIREKI.eraValue) ? 0 : eravalue + ERA_OFFSET; + } + + //----------------------------------------------------------------------- + /** + * Returns the numeric value of this {@code JapaneseEra}. + *

+ * The {@link #SHOWA} era that contains 1970-01-01 (ISO calendar system) has the value 1. + * Later eras are numbered from 2 ({@link #HEISEI}). + * Earlier eras are numbered 0 ({@link #TAISHO}), -1 ({@link #MEIJI}), and -999 ({@link #SEIREKI}). + * + * @return the era value + */ + @Override + public int getValue() { + return eraValue; + } + + @Override + public JapaneseChrono getChrono() { + return JapaneseChrono.INSTANCE; + } + + //----------------------------------------------------------------------- + String getAbbreviation() { + int index = ordinal(getValue()); + if (index == 0) { + return ""; + } + return ERA_CONFIG[index].getAbbreviation(); + } + + String getName() { + int index = ordinal(getValue()); + if (index == 0) { + return "Seireki"; + } + return ERA_CONFIG[index].getName(); + } + + @Override + public String toString() { + return getName(); + } + + //----------------------------------------------------------------------- + private Object writeReplace() { + return new Ser(Ser.JAPANESE_ERA_TYPE, this); + } + + void writeExternal(DataOutput out) throws IOException { + out.writeByte(this.getValue()); + } + + static JapaneseEra readExternal(DataInput in) throws IOException { + byte eraValue = in.readByte(); + return JapaneseEra.of(eraValue); + } + +} diff --git a/src/share/classes/java/time/calendar/MinguoChrono.java b/src/share/classes/java/time/calendar/MinguoChrono.java new file mode 100644 index 0000000000000000000000000000000000000000..70fa85a6d43c2b110c994fd623e8cadd459ddc33 --- /dev/null +++ b/src/share/classes/java/time/calendar/MinguoChrono.java @@ -0,0 +1,245 @@ +/* + * 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 java.time.calendar; + +import static java.time.temporal.ChronoField.YEAR; + +import java.io.Serializable; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.Era; +import java.time.temporal.ISOChrono; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.ValueRange; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** + * The Minguo calendar system. + *

+ * This chronology defines the rules of the Minguo calendar system. + * This calendar system is primarily used in the Republic of China, often known as Taiwan. + * Dates are aligned such that {@code 0001-01-01 (Minguo)} is {@code 1911-01-01 (ISO)}. + *

+ * The fields are defined as follows: + *

    + *
  • era - There are two eras, the current 'Republic' (ERA_ROC) and the previous era (ERA_BEFORE_ROC). + *
  • year-of-era - The year-of-era for the current era increases uniformly from the epoch at year one. + * For the previous era the year increases from one as time goes backwards. + * The value for the current era is equal to the ISO proleptic-year minus 1911. + *
  • proleptic-year - The proleptic year is the same as the year-of-era for the + * current era. For the previous era, years have zero, then negative values. + * The value is equal to the ISO proleptic-year minus 1911. + *
  • month-of-year - The Minguo month-of-year exactly matches ISO. + *
  • day-of-month - The Minguo day-of-month exactly matches ISO. + *
  • day-of-year - The Minguo day-of-year exactly matches ISO. + *
  • leap-year - The Minguo leap-year pattern exactly matches ISO, such that the two calendars + * are never out of step. + *

+ * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class MinguoChrono extends Chrono implements Serializable { + + /** + * Singleton instance for the Minguo chronology. + */ + public static final MinguoChrono INSTANCE = new MinguoChrono(); + + /** + * The singleton instance for the era ROC. + */ + public static final Era ERA_ROC = MinguoEra.ROC; + + /** + * The singleton instance for the era BEFORE_ROC. + */ + public static final Era ERA_BEFORE_ROC = MinguoEra.BEFORE_ROC; + + /** + * Serialization version. + */ + private static final long serialVersionUID = 1039765215346859963L; + /** + * The difference in years between ISO and Minguo. + */ + static final int YEARS_DIFFERENCE = 1911; + + /** + * Restricted constructor. + */ + private MinguoChrono() { + } + + /** + * Resolve singleton. + * + * @return the singleton instance, not null + */ + private Object readResolve() { + return INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the ID of the chronology - 'Minguo'. + *

+ * The ID uniquely identifies the {@code Chrono}. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * + * @return the chronology ID - 'Minguo' + * @see #getCalendarType() + */ + @Override + public String getId() { + return "Minguo"; + } + + /** + * Gets the calendar type of the underlying calendar system - 'roc'. + *

+ * The calendar type is an identifier defined by the + * Unicode Locale Data Markup Language (LDML) specification. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * It can also be used as part of a locale, accessible via + * {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'. + * + * @return the calendar system type - 'roc' + * @see #getId() + */ + @Override + public String getCalendarType() { + return "roc"; + } + + //----------------------------------------------------------------------- + @Override + public ChronoLocalDate date(int prolepticYear, int month, int dayOfMonth) { + return new MinguoDate(LocalDate.of(prolepticYear + YEARS_DIFFERENCE, month, dayOfMonth)); + } + + @Override + public ChronoLocalDate dateYearDay(int prolepticYear, int dayOfYear) { + return new MinguoDate(LocalDate.ofYearDay(prolepticYear + YEARS_DIFFERENCE, dayOfYear)); + } + + @Override + public ChronoLocalDate date(TemporalAccessor temporal) { + if (temporal instanceof MinguoDate) { + return (MinguoDate) temporal; + } + return new MinguoDate(LocalDate.from(temporal)); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified year is a leap year. + *

+ * Minguo leap years occur exactly in line with ISO leap years. + * This method does not validate the year passed in, and only has a + * well-defined result for years in the supported range. + * + * @param prolepticYear the proleptic-year to check, not validated for range + * @return true if the year is a leap year + */ + @Override + public boolean isLeapYear(long prolepticYear) { + return ISOChrono.INSTANCE.isLeapYear(prolepticYear + YEARS_DIFFERENCE); + } + + @Override + public int prolepticYear(Era era, int yearOfEra) { + if (era instanceof MinguoEra == false) { + throw new DateTimeException("Era must be MinguoEra"); + } + return (era == MinguoEra.ROC ? yearOfEra : 1 - yearOfEra); + } + + @Override + public Era eraOf(int eraValue) { + return MinguoEra.of(eraValue); + } + + @Override + public List> eras() { + return Arrays.>asList(MinguoEra.values()); + } + + //----------------------------------------------------------------------- + @Override + public ValueRange range(ChronoField field) { + switch (field) { + case YEAR_OF_ERA: { + ValueRange range = YEAR.range(); + return ValueRange.of(1, range.getMaximum() - YEARS_DIFFERENCE, -range.getMinimum() + 1 + YEARS_DIFFERENCE); + } + case YEAR: { + ValueRange range = YEAR.range(); + return ValueRange.of(range.getMinimum() - YEARS_DIFFERENCE, range.getMaximum() - YEARS_DIFFERENCE); + } + } + return field.range(); + } + +} diff --git a/src/share/classes/java/time/calendar/MinguoDate.java b/src/share/classes/java/time/calendar/MinguoDate.java new file mode 100644 index 0000000000000000000000000000000000000000..39981d6335335ff74843744e40988b3685171136 --- /dev/null +++ b/src/share/classes/java/time/calendar/MinguoDate.java @@ -0,0 +1,261 @@ +/* + * 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 java.time.calendar; + +import static java.time.calendar.MinguoChrono.YEARS_DIFFERENCE; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.Serializable; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.TemporalField; +import java.time.temporal.ValueRange; +import java.util.Objects; + +/** + * A date in the Minguo calendar system. + *

+ * This implements {@code ChronoLocalDate} for the {@link MinguoChrono Minguo calendar}. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +final class MinguoDate + extends ChronoDateImpl + implements ChronoLocalDate, Serializable { + // this class is package-scoped so that future conversion to public + // would not change serialization + + /** + * Serialization version. + */ + private static final long serialVersionUID = 1300372329181994526L; + + /** + * The underlying date. + */ + private final LocalDate isoDate; + + /** + * Creates an instance from an ISO date. + * + * @param isoDate the standard local date, validated not null + */ + MinguoDate(LocalDate isoDate) { + Objects.requireNonNull(isoDate, "isoDate"); + this.isoDate = isoDate; + } + + //----------------------------------------------------------------------- + @Override + public MinguoChrono getChrono() { + return MinguoChrono.INSTANCE; + } + + @Override + public int lengthOfMonth() { + return isoDate.lengthOfMonth(); + } + + @Override + public ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + if (isSupported(field)) { + ChronoField f = (ChronoField) field; + switch (f) { + case DAY_OF_MONTH: + case DAY_OF_YEAR: + case ALIGNED_WEEK_OF_MONTH: + return isoDate.range(field); + case YEAR_OF_ERA: { + ValueRange range = YEAR.range(); + long max = (getProlepticYear() <= 0 ? -range.getMinimum() + 1 + YEARS_DIFFERENCE : range.getMaximum() - YEARS_DIFFERENCE); + return ValueRange.of(1, max); + } + } + return getChrono().range(f); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doRange(this); + } + + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case YEAR_OF_ERA: { + int prolepticYear = getProlepticYear(); + return (prolepticYear >= 1 ? prolepticYear : 1 - prolepticYear); + } + case YEAR: + return getProlepticYear(); + case ERA: + return (getProlepticYear() >= 1 ? 1 : 0); + } + return isoDate.getLong(field); + } + return field.doGet(this); + } + + private int getProlepticYear() { + return isoDate.getYear() - YEARS_DIFFERENCE; + } + + //----------------------------------------------------------------------- + @Override + public MinguoDate with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + if (getLong(f) == newValue) { + return this; + } + switch (f) { + case YEAR_OF_ERA: + case YEAR: + case ERA: { + f.checkValidValue(newValue); + int nvalue = (int) newValue; + switch (f) { + case YEAR_OF_ERA: + return with(isoDate.withYear(getProlepticYear() >= 1 ? nvalue + YEARS_DIFFERENCE : (1 - nvalue) + YEARS_DIFFERENCE)); + case YEAR: + return with(isoDate.withYear(nvalue + YEARS_DIFFERENCE)); + case ERA: + return with(isoDate.withYear((1 - getProlepticYear()) + YEARS_DIFFERENCE)); + } + } + } + return with(isoDate.with(field, newValue)); + } + return (MinguoDate) ChronoLocalDate.super.with(field, newValue); + } + + //----------------------------------------------------------------------- + @Override + MinguoDate plusYears(long years) { + return with(isoDate.plusYears(years)); + } + + @Override + MinguoDate plusMonths(long months) { + return with(isoDate.plusMonths(months)); + } + + @Override + MinguoDate plusDays(long days) { + return with(isoDate.plusDays(days)); + } + + private MinguoDate with(LocalDate newDate) { + return (newDate.equals(isoDate) ? this : new MinguoDate(newDate)); + } + + @Override // override for performance + public long toEpochDay() { + return isoDate.toEpochDay(); + } + + //------------------------------------------------------------------------- + @Override // override for performance + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof MinguoDate) { + MinguoDate otherDate = (MinguoDate) obj; + return this.isoDate.equals(otherDate.isoDate); + } + return false; + } + + @Override // override for performance + public int hashCode() { + return getChrono().getId().hashCode() ^ isoDate.hashCode(); + } + + //----------------------------------------------------------------------- + private Object writeReplace() { + return new Ser(Ser.MINGUO_DATE_TYPE, this); + } + + void writeExternal(DataOutput out) throws IOException { + // MinguoChrono is implicit in the MINGUO_DATE_TYPE + out.writeInt(get(YEAR)); + out.writeByte(get(MONTH_OF_YEAR)); + out.writeByte(get(DAY_OF_MONTH)); + + } + + static ChronoLocalDate readExternal(DataInput in) throws IOException { + int year = in.readInt(); + int month = in.readByte(); + int dayOfMonth = in.readByte(); + return MinguoChrono.INSTANCE.date(year, month, dayOfMonth); + } + + +} diff --git a/src/share/classes/java/time/calendar/MinguoEra.java b/src/share/classes/java/time/calendar/MinguoEra.java new file mode 100644 index 0000000000000000000000000000000000000000..7cbf430eeea176b50988ab835a2d3b7fe9dfff44 --- /dev/null +++ b/src/share/classes/java/time/calendar/MinguoEra.java @@ -0,0 +1,217 @@ +/* + * 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 java.time.calendar; + +import static java.time.temporal.ChronoField.ERA; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.time.DateTimeException; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.TextStyle; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.Era; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalField; +import java.time.temporal.ValueRange; +import java.util.Locale; + +/** + * An era in the Minguo calendar system. + *

+ * The Minguo calendar system has two eras. + * The date {@code 0001-01-01 (Minguo)} is equal to {@code 1912-01-01 (ISO)}. + *

+ * Do not use {@code ordinal()} to obtain the numeric representation of {@code MinguoEra}. + * Use {@code getValue()} instead. + * + *

Specification for implementors

+ * This is an immutable and thread-safe enum. + * + * @since 1.8 + */ +enum MinguoEra implements Era { + + /** + * The singleton instance for the era BEFORE_ROC, 'Before Republic of China'. + * This has the numeric value of {@code 0}. + */ + BEFORE_ROC, + /** + * The singleton instance for the era ROC, 'Republic of China'. + * This has the numeric value of {@code 1}. + */ + ROC; + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code MinguoEra} from an {@code int} value. + *

+ * {@code MinguoEra} is an enum representing the Minguo eras of BEFORE_ROC/ROC. + * This factory allows the enum to be obtained from the {@code int} value. + * + * @param era the BEFORE_ROC/ROC value to represent, from 0 (BEFORE_ROC) to 1 (ROC) + * @return the era singleton, not null + * @throws DateTimeException if the value is invalid + */ + public static MinguoEra of(int era) { + switch (era) { + case 0: + return BEFORE_ROC; + case 1: + return ROC; + default: + throw new DateTimeException("Invalid era: " + era); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the numeric era {@code int} value. + *

+ * The era BEFORE_ROC has the value 0, while the era ROC has the value 1. + * + * @return the era value, from 0 (BEFORE_ROC) to 1 (ROC) + */ + @Override + public int getValue() { + return ordinal(); + } + + @Override + public MinguoChrono getChrono() { + return MinguoChrono.INSTANCE; + } + + // JDK8 default methods: + //----------------------------------------------------------------------- + @Override + public ChronoLocalDate date(int year, int month, int day) { + return getChrono().date(this, year, month, day); + } + + @Override + public ChronoLocalDate dateYearDay(int year, int dayOfYear) { + return getChrono().dateYearDay(this, year, dayOfYear); + } + + //----------------------------------------------------------------------- + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == ERA; + } + return field != null && field.doIsSupported(this); + } + + @Override + public ValueRange range(TemporalField field) { + if (field == ERA) { + return field.range(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doRange(this); + } + + @Override + public int get(TemporalField field) { + if (field == ERA) { + return getValue(); + } + return range(field).checkValidIntValue(getLong(field), field); + } + + @Override + public long getLong(TemporalField field) { + if (field == ERA) { + return getValue(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doGet(this); + } + + //------------------------------------------------------------------------- + @Override + public Temporal adjustInto(Temporal temporal) { + return temporal.with(ERA, getValue()); + } + + //----------------------------------------------------------------------- + @Override + public String getText(TextStyle style, Locale locale) { + return new DateTimeFormatterBuilder().appendText(ERA, style).toFormatter(locale).print(this); + } + + //----------------------------------------------------------------------- + private Object writeReplace() { + return new Ser(Ser.MINGUO_ERA_TYPE, this); + } + + void writeExternal(DataOutput out) throws IOException { + out.writeByte(this.getValue()); + } + + static MinguoEra readExternal(DataInput in) throws IOException { + byte eraValue = in.readByte(); + return MinguoEra.of(eraValue); + } + +} diff --git a/src/share/classes/java/time/calendar/Ser.java b/src/share/classes/java/time/calendar/Ser.java new file mode 100644 index 0000000000000000000000000000000000000000..48f5c81fe05da0de7eff18ca11330b16d568fe87 --- /dev/null +++ b/src/share/classes/java/time/calendar/Ser.java @@ -0,0 +1,214 @@ +/* + * 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) 2011-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 java.time.calendar; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.StreamCorruptedException; +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * The shared serialization delegate for this package. + * + *

Implementation notes

+ * This class wraps the object being serialized, and takes a byte representing the type of the class to + * be serialized. This byte can also be used for versioning the serialization format. In this case another + * byte flag would be used in order to specify an alternative version of the type format. + * For example {@code JAPANESE_DATE_TYPE_VERSION_2 = 21}. + *

+ * In order to serialise the object it writes its byte and then calls back to the appropriate class where + * the serialisation is performed. In order to deserialise the object it read in the type byte, switching + * in order to select which class to call back into. + *

+ * The serialisation format is determined on a per class basis. In the case of field based classes each + * of the fields is written out with an appropriate size format in descending order of the field's size. For + * example in the case of {@link LocalDate} year is written before month. Composite classes, such as + * {@link LocalDateTime} are serialised as one object. Enum classes are serialised using the index of their + * element. + *

+ * This class is mutable and should be created once per serialization. + * + * @serial include + * @since 1.8 + */ +final class Ser implements Externalizable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = 7857518227608961174L; + + static final byte JAPANESE_DATE_TYPE = 1; + static final byte JAPANESE_ERA_TYPE = 2; + static final byte HIJRAH_DATE_TYPE = 3; + static final byte HIJRAH_ERA_TYPE = 4; + static final byte MINGUO_DATE_TYPE = 5; + static final byte MINGUO_ERA_TYPE = 6; + static final byte THAIBUDDHIST_DATE_TYPE = 7; + static final byte THAIBUDDHIST_ERA_TYPE = 8; + + /** The type being serialized. */ + private byte type; + /** The object being serialized. */ + private Object object; + + /** + * Constructor for deserialization. + */ + public Ser() { + } + + /** + * Creates an instance for serialization. + * + * @param type the type + * @param object the object + */ + Ser(byte type, Object object) { + this.type = type; + this.object = object; + } + + //----------------------------------------------------------------------- + /** + * Implements the {@code Externalizable} interface to write the object. + * + * @param out the data stream to write to, not null + */ + @Override + public void writeExternal(ObjectOutput out) throws IOException { + writeInternal(type, object, out); + } + + private static void writeInternal(byte type, Object object, ObjectOutput out) throws IOException { + out.writeByte(type); + switch (type) { + case JAPANESE_DATE_TYPE: + ((JapaneseDate) object).writeExternal(out); + break; + case JAPANESE_ERA_TYPE: + ((JapaneseEra) object).writeExternal(out); + break; + case HIJRAH_DATE_TYPE: + ((HijrahDate) object).writeExternal(out); + break; + case HIJRAH_ERA_TYPE: + ((HijrahEra) object).writeExternal(out); + break; + case MINGUO_DATE_TYPE: + ((MinguoDate) object).writeExternal(out); + break; + case MINGUO_ERA_TYPE: + ((MinguoEra) object).writeExternal(out); + break; + case THAIBUDDHIST_DATE_TYPE: + ((ThaiBuddhistDate) object).writeExternal(out); + break; + case THAIBUDDHIST_ERA_TYPE: + ((ThaiBuddhistEra) object).writeExternal(out); + break; + default: + throw new InvalidClassException("Unknown serialized type"); + } + } + + //----------------------------------------------------------------------- + /** + * Implements the {@code Externalizable} interface to read the object. + * + * @param in the data to read, not null + */ + @Override + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + type = in.readByte(); + object = readInternal(type, in); + } + + static Object read(ObjectInput in) throws IOException, ClassNotFoundException { + byte type = in.readByte(); + return readInternal(type, in); + } + + private static Object readInternal(byte type, ObjectInput in) throws IOException, ClassNotFoundException { + switch (type) { + case JAPANESE_DATE_TYPE: return JapaneseDate.readExternal(in); + case JAPANESE_ERA_TYPE: return JapaneseEra.readExternal(in); + case HIJRAH_DATE_TYPE: return HijrahDate.readExternal(in); + case HIJRAH_ERA_TYPE: return HijrahEra.readExternal(in); + case MINGUO_DATE_TYPE: return MinguoDate.readExternal(in); + case MINGUO_ERA_TYPE: return MinguoEra.readExternal(in); + case THAIBUDDHIST_DATE_TYPE: return ThaiBuddhistDate.readExternal(in); + case THAIBUDDHIST_ERA_TYPE: return ThaiBuddhistEra.readExternal(in); + default: + throw new StreamCorruptedException("Unknown serialized type"); + } + } + + /** + * Returns the object that will replace this one. + * + * @return the read object, should never be null + */ + private Object readResolve() { + return object; + } + +} diff --git a/src/share/classes/java/time/calendar/ThaiBuddhistChrono.java b/src/share/classes/java/time/calendar/ThaiBuddhistChrono.java new file mode 100644 index 0000000000000000000000000000000000000000..1fadb5e57c49618c3e307590efe1e3e06a48be94 --- /dev/null +++ b/src/share/classes/java/time/calendar/ThaiBuddhistChrono.java @@ -0,0 +1,280 @@ +/* + * 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 java.time.calendar; + +import static java.time.temporal.ChronoField.YEAR; + +import java.io.Serializable; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.Era; +import java.time.temporal.ISOChrono; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.ValueRange; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +/** + * The Thai Buddhist calendar system. + *

+ * This chronology defines the rules of the Thai Buddhist calendar system. + * This calendar system is primarily used in Thailand. + * Dates are aligned such that {@code 2484-01-01 (Buddhist)} is {@code 1941-01-01 (ISO)}. + *

+ * The fields are defined as follows: + *

    + *
  • era - There are two eras, the current 'Buddhist' (ERA_BE) and the previous era (ERA_BEFORE_BE). + *
  • year-of-era - The year-of-era for the current era increases uniformly from the epoch at year one. + * For the previous era the year increases from one as time goes backwards. + * The value for the current era is equal to the ISO proleptic-year plus 543. + *
  • proleptic-year - The proleptic year is the same as the year-of-era for the + * current era. For the previous era, years have zero, then negative values. + * The value is equal to the ISO proleptic-year plus 543. + *
  • month-of-year - The ThaiBuddhist month-of-year exactly matches ISO. + *
  • day-of-month - The ThaiBuddhist day-of-month exactly matches ISO. + *
  • day-of-year - The ThaiBuddhist day-of-year exactly matches ISO. + *
  • leap-year - The ThaiBuddhist leap-year pattern exactly matches ISO, such that the two calendars + * are never out of step. + *

+ * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class ThaiBuddhistChrono extends Chrono implements Serializable { + + /** + * Singleton instance of the Buddhist chronology. + */ + public static final ThaiBuddhistChrono INSTANCE = new ThaiBuddhistChrono(); + /** + * The singleton instance for the era before the current one - Before Buddhist - + * which has the value 0. + */ + public static final Era ERA_BEFORE_BE = ThaiBuddhistEra.BEFORE_BE; + /** + * The singleton instance for the current era - Buddhist - which has the value 1. + */ + public static final Era ERA_BE = ThaiBuddhistEra.BE; + + /** + * Serialization version. + */ + private static final long serialVersionUID = 2775954514031616474L; + /** + * Containing the offset to add to the ISO year. + */ + static final int YEARS_DIFFERENCE = 543; + /** + * Narrow names for eras. + */ + private static final HashMap ERA_NARROW_NAMES = new HashMap<>(); + /** + * Short names for eras. + */ + private static final HashMap ERA_SHORT_NAMES = new HashMap<>(); + /** + * Full names for eras. + */ + private static final HashMap ERA_FULL_NAMES = new HashMap<>(); + /** + * Fallback language for the era names. + */ + private static final String FALLBACK_LANGUAGE = "en"; + /** + * Language that has the era names. + */ + private static final String TARGET_LANGUAGE = "th"; + /** + * Name data. + */ + static { + ERA_NARROW_NAMES.put(FALLBACK_LANGUAGE, new String[]{"BB", "BE"}); + ERA_NARROW_NAMES.put(TARGET_LANGUAGE, new String[]{"BB", "BE"}); + ERA_SHORT_NAMES.put(FALLBACK_LANGUAGE, new String[]{"B.B.", "B.E."}); + ERA_SHORT_NAMES.put(TARGET_LANGUAGE, + new String[]{"\u0e1e.\u0e28.", + "\u0e1b\u0e35\u0e01\u0e48\u0e2d\u0e19\u0e04\u0e23\u0e34\u0e2a\u0e15\u0e4c\u0e01\u0e32\u0e25\u0e17\u0e35\u0e48"}); + ERA_FULL_NAMES.put(FALLBACK_LANGUAGE, new String[]{"Before Buddhist", "Budhhist Era"}); + ERA_FULL_NAMES.put(TARGET_LANGUAGE, + new String[]{"\u0e1e\u0e38\u0e17\u0e18\u0e28\u0e31\u0e01\u0e23\u0e32\u0e0a", + "\u0e1b\u0e35\u0e01\u0e48\u0e2d\u0e19\u0e04\u0e23\u0e34\u0e2a\u0e15\u0e4c\u0e01\u0e32\u0e25\u0e17\u0e35\u0e48"}); + } + + /** + * Restricted constructor. + */ + private ThaiBuddhistChrono() { + } + + /** + * Resolve singleton. + * + * @return the singleton instance, not null + */ + private Object readResolve() { + return INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the ID of the chronology - 'ThaiBuddhist'. + *

+ * The ID uniquely identifies the {@code Chrono}. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * + * @return the chronology ID - 'ThaiBuddhist' + * @see #getCalendarType() + */ + @Override + public String getId() { + return "ThaiBuddhist"; + } + + /** + * Gets the calendar type of the underlying calendar system - 'buddhist'. + *

+ * The calendar type is an identifier defined by the + * Unicode Locale Data Markup Language (LDML) specification. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * It can also be used as part of a locale, accessible via + * {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'. + * + * @return the calendar system type - 'buddhist' + * @see #getId() + */ + @Override + public String getCalendarType() { + return "buddhist"; + } + + //----------------------------------------------------------------------- + @Override + public ChronoLocalDate date(int prolepticYear, int month, int dayOfMonth) { + return new ThaiBuddhistDate(LocalDate.of(prolepticYear - YEARS_DIFFERENCE, month, dayOfMonth)); + } + + @Override + public ChronoLocalDate dateYearDay(int prolepticYear, int dayOfYear) { + return new ThaiBuddhistDate(LocalDate.ofYearDay(prolepticYear - YEARS_DIFFERENCE, dayOfYear)); + } + + @Override + public ChronoLocalDate date(TemporalAccessor temporal) { + if (temporal instanceof ThaiBuddhistDate) { + return (ThaiBuddhistDate) temporal; + } + return new ThaiBuddhistDate(LocalDate.from(temporal)); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified year is a leap year. + *

+ * Thai Buddhist leap years occur exactly in line with ISO leap years. + * This method does not validate the year passed in, and only has a + * well-defined result for years in the supported range. + * + * @param prolepticYear the proleptic-year to check, not validated for range + * @return true if the year is a leap year + */ + @Override + public boolean isLeapYear(long prolepticYear) { + return ISOChrono.INSTANCE.isLeapYear(prolepticYear - YEARS_DIFFERENCE); + } + + @Override + public int prolepticYear(Era era, int yearOfEra) { + if (era instanceof ThaiBuddhistEra == false) { + throw new DateTimeException("Era must be BuddhistEra"); + } + return (era == ThaiBuddhistEra.BE ? yearOfEra : 1 - yearOfEra); + } + + @Override + public Era eraOf(int eraValue) { + return ThaiBuddhistEra.of(eraValue); + } + + @Override + public List> eras() { + return Arrays.>asList(ThaiBuddhistEra.values()); + } + + //----------------------------------------------------------------------- + @Override + public ValueRange range(ChronoField field) { + switch (field) { + case YEAR_OF_ERA: { + ValueRange range = YEAR.range(); + return ValueRange.of(1, -(range.getMinimum() + YEARS_DIFFERENCE) + 1, range.getMaximum() + YEARS_DIFFERENCE); + } + case YEAR: { + ValueRange range = YEAR.range(); + return ValueRange.of(range.getMinimum() + YEARS_DIFFERENCE, range.getMaximum() + YEARS_DIFFERENCE); + } + } + return field.range(); + } + +} diff --git a/src/share/classes/java/time/calendar/ThaiBuddhistDate.java b/src/share/classes/java/time/calendar/ThaiBuddhistDate.java new file mode 100644 index 0000000000000000000000000000000000000000..14723f66a6009339697ec1a044430889992c693f --- /dev/null +++ b/src/share/classes/java/time/calendar/ThaiBuddhistDate.java @@ -0,0 +1,259 @@ +/* + * 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 java.time.calendar; + +import static java.time.calendar.ThaiBuddhistChrono.YEARS_DIFFERENCE; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.Serializable; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.TemporalField; +import java.time.temporal.ValueRange; +import java.util.Objects; + +/** + * A date in the Thai Buddhist calendar system. + *

+ * This implements {@code ChronoLocalDate} for the {@link ThaiBuddhistChrono Thai Buddhist calendar}. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +final class ThaiBuddhistDate + extends ChronoDateImpl + implements ChronoLocalDate, Serializable { + // this class is package-scoped so that future conversion to public + // would not change serialization + + /** + * Serialization version. + */ + private static final long serialVersionUID = -8722293800195731463L; + + /** + * The underlying date. + */ + private final LocalDate isoDate; + + /** + * Creates an instance from an ISO date. + * + * @param isoDate the standard local date, validated not null + */ + ThaiBuddhistDate(LocalDate isoDate) { + Objects.requireNonNull(isoDate, "isoDate"); + this.isoDate = isoDate; + } + + //----------------------------------------------------------------------- + @Override + public ThaiBuddhistChrono getChrono() { + return ThaiBuddhistChrono.INSTANCE; + } + + @Override + public int lengthOfMonth() { + return isoDate.lengthOfMonth(); + } + + @Override + public ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + if (isSupported(field)) { + ChronoField f = (ChronoField) field; + switch (f) { + case DAY_OF_MONTH: + case DAY_OF_YEAR: + case ALIGNED_WEEK_OF_MONTH: + return isoDate.range(field); + case YEAR_OF_ERA: { + ValueRange range = YEAR.range(); + long max = (getProlepticYear() <= 0 ? -(range.getMinimum() + YEARS_DIFFERENCE) + 1 : range.getMaximum() + YEARS_DIFFERENCE); + return ValueRange.of(1, max); + } + } + return getChrono().range(f); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doRange(this); + } + + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case YEAR_OF_ERA: { + int prolepticYear = getProlepticYear(); + return (prolepticYear >= 1 ? prolepticYear : 1 - prolepticYear); + } + case YEAR: + return getProlepticYear(); + case ERA: + return (getProlepticYear() >= 1 ? 1 : 0); + } + return isoDate.getLong(field); + } + return field.doGet(this); + } + + private int getProlepticYear() { + return isoDate.getYear() + YEARS_DIFFERENCE; + } + + //----------------------------------------------------------------------- + @Override + public ThaiBuddhistDate with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + if (getLong(f) == newValue) { + return this; + } + switch (f) { + case YEAR_OF_ERA: + case YEAR: + case ERA: { + f.checkValidValue(newValue); + int nvalue = (int) newValue; + switch (f) { + case YEAR_OF_ERA: + return with(isoDate.withYear((getProlepticYear() >= 1 ? nvalue : 1 - nvalue) - YEARS_DIFFERENCE)); + case YEAR: + return with(isoDate.withYear(nvalue - YEARS_DIFFERENCE)); + case ERA: + return with(isoDate.withYear((1 - getProlepticYear()) - YEARS_DIFFERENCE)); + } + } + } + return with(isoDate.with(field, newValue)); + } + return (ThaiBuddhistDate) ChronoLocalDate.super.with(field, newValue); + } + + //----------------------------------------------------------------------- + @Override + ThaiBuddhistDate plusYears(long years) { + return with(isoDate.plusYears(years)); + } + + @Override + ThaiBuddhistDate plusMonths(long months) { + return with(isoDate.plusMonths(months)); + } + + @Override + ThaiBuddhistDate plusDays(long days) { + return with(isoDate.plusDays(days)); + } + + private ThaiBuddhistDate with(LocalDate newDate) { + return (newDate.equals(isoDate) ? this : new ThaiBuddhistDate(newDate)); + } + + @Override // override for performance + public long toEpochDay() { + return isoDate.toEpochDay(); + } + + //------------------------------------------------------------------------- + @Override // override for performance + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof ThaiBuddhistDate) { + ThaiBuddhistDate otherDate = (ThaiBuddhistDate) obj; + return this.isoDate.equals(otherDate.isoDate); + } + return false; + } + + @Override // override for performance + public int hashCode() { + return getChrono().getId().hashCode() ^ isoDate.hashCode(); + } + + //----------------------------------------------------------------------- + private Object writeReplace() { + return new Ser(Ser.THAIBUDDHIST_DATE_TYPE, this); + } + + void writeExternal(DataOutput out) throws IOException { + // MinguoChrono is implicit in the THAIBUDDHIST_DATE_TYPE + out.writeInt(this.get(YEAR)); + out.writeByte(this.get(MONTH_OF_YEAR)); + out.writeByte(this.get(DAY_OF_MONTH)); + } + + static ChronoLocalDate readExternal(DataInput in) throws IOException { + int year = in.readInt(); + int month = in.readByte(); + int dayOfMonth = in.readByte(); + return ThaiBuddhistChrono.INSTANCE.date(year, month, dayOfMonth); + } + +} diff --git a/src/share/classes/java/time/calendar/ThaiBuddhistEra.java b/src/share/classes/java/time/calendar/ThaiBuddhistEra.java new file mode 100644 index 0000000000000000000000000000000000000000..713d3e681c5a5b59b34d162b562f1b6e0dc86750 --- /dev/null +++ b/src/share/classes/java/time/calendar/ThaiBuddhistEra.java @@ -0,0 +1,216 @@ +/* + * 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 java.time.calendar; + +import static java.time.temporal.ChronoField.ERA; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.time.DateTimeException; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.TextStyle; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.Era; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalField; +import java.time.temporal.ValueRange; +import java.util.Locale; + +/** + * An era in the Thai Buddhist calendar system. + *

+ * The Thai Buddhist calendar system has two eras. + *

+ * Do not use ordinal() to obtain the numeric representation of a ThaiBuddhistEra + * instance. Use getValue() instead. + * + *

Specification for implementors

+ * This is an immutable and thread-safe enum. + * + * @since 1.8 + */ +enum ThaiBuddhistEra implements Era { + + /** + * The singleton instance for the era before the current one, 'Before Buddhist Era', + * which has the value 0. + */ + BEFORE_BE, + /** + * The singleton instance for the current era, 'Buddhist Era', which has the value 1. + */ + BE; + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code ThaiBuddhistEra} from a value. + *

+ * The current era (from ISO year -543 onwards) has the value 1 + * The previous era has the value 0. + * + * @param thaiBuddhistEra the era to represent, from 0 to 1 + * @return the BuddhistEra singleton, never null + * @throws IllegalCalendarFieldValueException if the era is invalid + */ + public static ThaiBuddhistEra of(int thaiBuddhistEra) { + switch (thaiBuddhistEra) { + case 0: + return BEFORE_BE; + case 1: + return BE; + default: + throw new DateTimeException("Era is not valid for ThaiBuddhistEra"); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the era numeric value. + *

+ * The current era (from ISO year -543 onwards) has the value 1 + * The previous era has the value 0. + * + * @return the era value, from 0 (BEFORE_BE) to 1 (BE) + */ + @Override + public int getValue() { + return ordinal(); + } + + @Override + public ThaiBuddhistChrono getChrono() { + return ThaiBuddhistChrono.INSTANCE; + } + + // JDK8 default methods: + //----------------------------------------------------------------------- + @Override + public ChronoLocalDate date(int year, int month, int day) { + return getChrono().date(this, year, month, day); + } + + @Override + public ChronoLocalDate dateYearDay(int year, int dayOfYear) { + return getChrono().dateYearDay(this, year, dayOfYear); + } + + //----------------------------------------------------------------------- + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == ERA; + } + return field != null && field.doIsSupported(this); + } + + @Override + public ValueRange range(TemporalField field) { + if (field == ERA) { + return field.range(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doRange(this); + } + + @Override + public int get(TemporalField field) { + if (field == ERA) { + return getValue(); + } + return range(field).checkValidIntValue(getLong(field), field); + } + + @Override + public long getLong(TemporalField field) { + if (field == ERA) { + return getValue(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doGet(this); + } + + //------------------------------------------------------------------------- + @Override + public Temporal adjustInto(Temporal temporal) { + return temporal.with(ERA, getValue()); + } + + //----------------------------------------------------------------------- + @Override + public String getText(TextStyle style, Locale locale) { + return new DateTimeFormatterBuilder().appendText(ERA, style).toFormatter(locale).print(this); + } + + //----------------------------------------------------------------------- + private Object writeReplace() { + return new Ser(Ser.THAIBUDDHIST_ERA_TYPE, this); + } + + void writeExternal(DataOutput out) throws IOException { + out.writeByte(this.getValue()); + } + + static ThaiBuddhistEra readExternal(DataInput in) throws IOException { + byte eraValue = in.readByte(); + return ThaiBuddhistEra.of(eraValue); + } + +} diff --git a/src/share/classes/java/time/calendar/package-info.java b/src/share/classes/java/time/calendar/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..49c984ed5dc6a28dad529ba7600839150f2067fc --- /dev/null +++ b/src/share/classes/java/time/calendar/package-info.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +/** + *

+ * Support for calendar systems other than the default ISO. + *

+ *

+ * The main API is based around the calendar system defined in ISO-8601. + * This package provides support for alternate systems. + *

+ *

+ * The supported calendar systems includes: + *

+ *
    + *
  • {@link java.time.calendar.HijrahChrono Hijrah calendar}
  • + *
  • {@link java.time.calendar.JapaneseChrono Japanese calendar}
  • + *
  • {@link java.time.calendar.MinguoChrono Minguo calendar}
  • + *
  • {@link java.time.calendar.ThaiBuddhistChrono Thai Buddhist calendar}
  • + *
+ *

+ * It is intended that applications use the main API whenever possible, + * including code to read and write from a persistent data store, + * such as a database, and to send dates and times across a network. + * This package is then used at the user interface level to deal with + * localized input/output. + * See {@link java.time.temporal.ChronoLocalDate ChronoLocalDate} + * for a full discussion of the issues. + *

+ * + *

Example

+ *

+ * This example creates and uses a date in a non-ISO calendar system. + *

+ *
+ *   // Print the Thai Buddhist date
+ *       ChronoLocalDate<ThaiBuddhistChrono> now1 = ThaiBuddhistChrono.INSTANCE.dateNow();
+ *       int day = now1.get(ChronoField.DAY_OF_MONTH);
+ *       int dow = now1.get(ChronoField.DAY_OF_WEEK);
+ *       int month = now1.get(ChronoField.MONTH_OF_YEAR);
+ *       int year = now1.get(ChronoField.YEAR);
+ *       System.out.printf("  Today is %s %s %d-%s-%d%n", now1.getChrono().getId(),
+ *                 dow, day, month, year);
+ *
+ *   // Enumerate the list of available calendars and print today for each
+ *       Set<Chrono<?>> chronos = Chrono.getAvailableChronologies();
+ *       for (Chrono<?> chrono : chronos) {
+ *         ChronoLocalDate<?> date = chrono.dateNow();
+ *         System.out.printf("   %20s: %s%n", chrono.getId(), date.toString());
+ *       }
+ *
+ *   // Print today's date and the last day of the year for the Thai Buddhist Calendar.
+ *       ChronoLocalDate<ThaiBuddhistChrono> first = now1
+ *                 .with(ChronoField.DAY_OF_MONTH, 1)
+ *                 .with(ChronoField.MONTH_OF_YEAR, 1);
+ *       ChronoLocalDate<ThaiBuddhistChrono> last = first
+ *                 .plus(1, ChronoUnit.YEARS)
+ *                 .minus(1, ChronoUnit.DAYS);
+ *       System.out.printf("  %s: 1st of year: %s; end of year: %s%n", last.getChrono().getId(),
+ *                 first, last);
+ *  
+ * + *

Package specification

+ *

+ * Unless otherwise noted, passing a null argument to a constructor or method in any class or interface + * in this package will cause a {@link java.lang.NullPointerException NullPointerException} to be thrown. + * The Javadoc "@param" definition is used to summarise the null-behavior. + * The "@throws {@link java.lang.NullPointerException}" is not explicitly documented in each method. + *

+ *

+ * All calculations should check for numeric overflow and throw either an {@link java.lang.ArithmeticException} + * or a {@link java.time.DateTimeException}. + *

+ * @since JDK1.8 + */ +package java.time.calendar; diff --git a/src/share/classes/java/time/format/DateTimeBuilder.java b/src/share/classes/java/time/format/DateTimeBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..32c7084593a66b2a480f9a911670f11baa429836 --- /dev/null +++ b/src/share/classes/java/time/format/DateTimeBuilder.java @@ -0,0 +1,719 @@ +/* + * 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) 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 java.time.format; + +import static java.time.temporal.Adjusters.nextOrSame; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; +import static java.time.temporal.ChronoField.AMPM_OF_DAY; +import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM; +import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.EPOCH_MONTH; +import static java.time.temporal.ChronoField.HOUR_OF_AMPM; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.INSTANT_SECONDS; +import static java.time.temporal.ChronoField.MICRO_OF_DAY; +import static java.time.temporal.ChronoField.MICRO_OF_SECOND; +import static java.time.temporal.ChronoField.MILLI_OF_DAY; +import static java.time.temporal.ChronoField.MILLI_OF_SECOND; +import static java.time.temporal.ChronoField.MINUTE_OF_DAY; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.NANO_OF_DAY; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.OFFSET_SECONDS; +import static java.time.temporal.ChronoField.SECOND_OF_DAY; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoField.YEAR; + +import java.time.DateTimeException; +import java.time.DayOfWeek; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoField; +import java.time.temporal.Queries; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; + +/** + * Builder that can holds date and time fields and related date and time objects. + *

+ * This class still needs major revision before JDK1.8 ships. + *

+ * The builder is used to hold onto different elements of date and time. + * It is designed as two separate maps: + *

    + *
  • from {@link java.time.temporal.TemporalField} to {@code long} value, where the value may be + * outside the valid range for the field + *
  • from {@code Class} to {@link java.time.temporal.TemporalAccessor}, holding larger scale objects + * like {@code LocalDateTime}. + *

+ * + *

Specification for implementors

+ * This class is mutable and not thread-safe. + * It should only be used from a single thread. + * + * @since 1.8 + */ +public final class DateTimeBuilder + implements TemporalAccessor, Cloneable { + + /** + * The map of other fields. + */ + private Map otherFields; + /** + * The map of date-time fields. + */ + private final EnumMap standardFields = new EnumMap(ChronoField.class); + /** + * The list of complete date-time objects. + */ + private final List objects = new ArrayList<>(2); + + //----------------------------------------------------------------------- + /** + * Creates an empty instance of the builder. + */ + public DateTimeBuilder() { + } + + /** + * Creates a new instance of the builder with a single field-value. + *

+ * This is equivalent to using {@link #addFieldValue(TemporalField, long)} on an empty builder. + * + * @param field the field to add, not null + * @param value the value to add, not null + */ + public DateTimeBuilder(TemporalField field, long value) { + addFieldValue(field, value); + } + + /** + * Creates a new instance of the builder. + * + * @param zone the zone, may be null + * @param chrono the chronology, may be null + */ + public DateTimeBuilder(ZoneId zone, Chrono chrono) { + if (zone != null) { + objects.add(zone); + } + if (chrono != null) { + objects.add(chrono); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the map of field-value pairs in the builder. + * + * @return a modifiable copy of the field-value map, not null + */ + public Map getFieldValueMap() { + Map map = new HashMap(standardFields); + if (otherFields != null) { + map.putAll(otherFields); + } + return map; + } + + /** + * Checks whether the specified field is present in the builder. + * + * @param field the field to find in the field-value map, not null + * @return true if the field is present + */ + public boolean containsFieldValue(TemporalField field) { + Objects.requireNonNull(field, "field"); + return standardFields.containsKey(field) || (otherFields != null && otherFields.containsKey(field)); + } + + /** + * Gets the value of the specified field from the builder. + * + * @param field the field to query in the field-value map, not null + * @return the value of the field, may be out of range + * @throws DateTimeException if the field is not present + */ + public long getFieldValue(TemporalField field) { + Objects.requireNonNull(field, "field"); + Long value = getFieldValue0(field); + if (value == null) { + throw new DateTimeException("Field not found: " + field); + } + return value; + } + + private Long getFieldValue0(TemporalField field) { + if (field instanceof ChronoField) { + return standardFields.get(field); + } else if (otherFields != null) { + return otherFields.get(field); + } + return null; + } + + /** + * Gets the value of the specified field from the builder ensuring it is valid. + * + * @param field the field to query in the field-value map, not null + * @return the value of the field, may be out of range + * @throws DateTimeException if the field is not present + */ + public long getValidFieldValue(TemporalField field) { + long value = getFieldValue(field); + return field.range().checkValidValue(value, field); + } + + /** + * Adds a field-value pair to the builder. + *

+ * This adds a field to the builder. + * If the field is not already present, then the field-value pair is added to the map. + * If the field is already present and it has the same value as that specified, no action occurs. + * If the field is already present and it has a different value to that specified, then + * an exception is thrown. + * + * @param field the field to add, not null + * @param value the value to add, not null + * @return {@code this}, for method chaining + * @throws DateTimeException if the field is already present with a different value + */ + public DateTimeBuilder addFieldValue(TemporalField field, long value) { + Objects.requireNonNull(field, "field"); + Long old = getFieldValue0(field); // check first for better error message + if (old != null && old.longValue() != value) { + throw new DateTimeException("Conflict found: " + field + " " + old + " differs from " + field + " " + value + ": " + this); + } + return putFieldValue0(field, value); + } + + private DateTimeBuilder putFieldValue0(TemporalField field, long value) { + if (field instanceof ChronoField) { + standardFields.put((ChronoField) field, value); + } else { + if (otherFields == null) { + otherFields = new LinkedHashMap(); + } + otherFields.put(field, value); + } + return this; + } + + /** + * Removes a field-value pair from the builder. + *

+ * This removes a field, which must exist, from the builder. + * See {@link #removeFieldValues(TemporalField...)} for a version which does not throw an exception + * + * @param field the field to remove, not null + * @return the previous value of the field + * @throws DateTimeException if the field is not found + */ + public long removeFieldValue(TemporalField field) { + Objects.requireNonNull(field, "field"); + Long value = null; + if (field instanceof ChronoField) { + value = standardFields.remove(field); + } else if (otherFields != null) { + value = otherFields.remove(field); + } + if (value == null) { + throw new DateTimeException("Field not found: " + field); + } + return value; + } + + //----------------------------------------------------------------------- + /** + * Removes a list of fields from the builder. + *

+ * This removes the specified fields from the builder. + * No exception is thrown if the fields are not present. + * + * @param fields the fields to remove, not null + */ + public void removeFieldValues(TemporalField... fields) { + for (TemporalField field : fields) { + if (field instanceof ChronoField) { + standardFields.remove(field); + } else if (otherFields != null) { + otherFields.remove(field); + } + } + } + + /** + * Queries a list of fields from the builder. + *

+ * This gets the value of the specified fields from the builder into + * an array where the positions match the order of the fields. + * If a field is not present, the array will contain null in that position. + * + * @param fields the fields to query, not null + * @return the array of field values, not null + */ + public Long[] queryFieldValues(TemporalField... fields) { + Long[] values = new Long[fields.length]; + int i = 0; + for (TemporalField field : fields) { + values[i++] = getFieldValue0(field); + } + return values; + } + + //----------------------------------------------------------------------- + /** + * Gets the list of date-time objects in the builder. + *

+ * This map is intended for use with {@link ZoneOffset} and {@link ZoneId}. + * The returned map is live and may be edited. + * + * @return the editable list of date-time objects, not null + */ + public List getCalendricalList() { + return objects; + } + + /** + * Adds a date-time object to the builder. + *

+ * This adds a date-time object to the builder. + * If the object is a {@code DateTimeBuilder}, each field is added using {@link #addFieldValue}. + * If the object is not already present, then the object is added. + * If the object is already present and it is equal to that specified, no action occurs. + * If the object is already present and it is not equal to that specified, then an exception is thrown. + * + * @param object the object to add, not null + * @return {@code this}, for method chaining + * @throws DateTimeException if the field is already present with a different value + */ + public DateTimeBuilder addCalendrical(Object object) { + Objects.requireNonNull(object, "object"); + // special case + if (object instanceof DateTimeBuilder) { + DateTimeBuilder dtb = (DateTimeBuilder) object; + for (TemporalField field : dtb.getFieldValueMap().keySet()) { + addFieldValue(field, dtb.getFieldValue(field)); + } + return this; + } + if (object instanceof Instant) { + addFieldValue(INSTANT_SECONDS, ((Instant) object).getEpochSecond()); + addFieldValue(NANO_OF_SECOND, ((Instant) object).getNano()); + } else { + objects.add(object); + } +// TODO +// // preserve state of builder until validated +// Class cls = dateTime.extract(Class.class); +// if (cls == null) { +// throw new DateTimeException("Invalid dateTime, unable to extract Class"); +// } +// Object obj = objects.get(cls); +// if (obj != null) { +// if (obj.equals(dateTime) == false) { +// throw new DateTimeException("Conflict found: " + dateTime.getClass().getSimpleName() + " " + obj + " differs from " + dateTime + ": " + this); +// } +// } else { +// objects.put(cls, dateTime); +// } + return this; + } + + //----------------------------------------------------------------------- + /** + * Resolves the builder, evaluating the date and time. + *

+ * This examines the contents of the builder and resolves it to produce the best + * available date and time, throwing an exception if a problem occurs. + * Calling this method changes the state of the builder. + * + * @return {@code this}, for method chaining + */ + public DateTimeBuilder resolve() { + splitObjects(); + // handle unusual fields + if (otherFields != null) { + outer: + while (true) { + Set> entrySet = new HashSet<>(otherFields.entrySet()); + for (Entry entry : entrySet) { + if (entry.getKey().resolve(this, entry.getValue())) { + continue outer; + } + } + break; + } + } + // handle standard fields + mergeDate(); + mergeTime(); + // TODO: cross validate remaining fields? + return this; + } + + private void mergeDate() { + if (standardFields.containsKey(EPOCH_DAY)) { + checkDate(LocalDate.ofEpochDay(standardFields.remove(EPOCH_DAY))); + return; + } + + // normalize fields + if (standardFields.containsKey(EPOCH_MONTH)) { + long em = standardFields.remove(EPOCH_MONTH); + addFieldValue(MONTH_OF_YEAR, (em % 12) + 1); + addFieldValue(YEAR, (em / 12) + 1970); + } + + // build date + if (standardFields.containsKey(YEAR)) { + if (standardFields.containsKey(MONTH_OF_YEAR)) { + if (standardFields.containsKey(DAY_OF_MONTH)) { + int y = Math.toIntExact(standardFields.remove(YEAR)); + int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR)); + int dom = Math.toIntExact(standardFields.remove(DAY_OF_MONTH)); + checkDate(LocalDate.of(y, moy, dom)); + return; + } + if (standardFields.containsKey(ALIGNED_WEEK_OF_MONTH)) { + if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) { + int y = Math.toIntExact(standardFields.remove(YEAR)); + int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR)); + int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_MONTH)); + int ad = Math.toIntExact(standardFields.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH)); + checkDate(LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7 + (ad - 1))); + return; + } + if (standardFields.containsKey(DAY_OF_WEEK)) { + int y = Math.toIntExact(standardFields.remove(YEAR)); + int moy = Math.toIntExact(standardFields.remove(MONTH_OF_YEAR)); + int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_MONTH)); + int dow = Math.toIntExact(standardFields.remove(DAY_OF_WEEK)); + checkDate(LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow)))); + return; + } + } + } + if (standardFields.containsKey(DAY_OF_YEAR)) { + int y = Math.toIntExact(standardFields.remove(YEAR)); + int doy = Math.toIntExact(standardFields.remove(DAY_OF_YEAR)); + checkDate(LocalDate.ofYearDay(y, doy)); + return; + } + if (standardFields.containsKey(ALIGNED_WEEK_OF_YEAR)) { + if (standardFields.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) { + int y = Math.toIntExact(standardFields.remove(YEAR)); + int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_YEAR)); + int ad = Math.toIntExact(standardFields.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR)); + checkDate(LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7 + (ad - 1))); + return; + } + if (standardFields.containsKey(DAY_OF_WEEK)) { + int y = Math.toIntExact(standardFields.remove(YEAR)); + int aw = Math.toIntExact(standardFields.remove(ALIGNED_WEEK_OF_YEAR)); + int dow = Math.toIntExact(standardFields.remove(DAY_OF_WEEK)); + checkDate(LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow)))); + return; + } + } + } + } + + private void checkDate(LocalDate date) { + // TODO: this doesn't handle aligned weeks over into next month which would otherwise be valid + + addCalendrical(date); + for (ChronoField field : standardFields.keySet()) { + long val1; + try { + val1 = date.getLong(field); + } catch (DateTimeException ex) { + continue; + } + Long val2 = standardFields.get(field); + if (val1 != val2) { + throw new DateTimeException("Conflict found: Field " + field + " " + val1 + " differs from " + field + " " + val2 + " derived from " + date); + } + } + } + + private void mergeTime() { + if (standardFields.containsKey(CLOCK_HOUR_OF_DAY)) { + long ch = standardFields.remove(CLOCK_HOUR_OF_DAY); + addFieldValue(HOUR_OF_DAY, ch == 24 ? 0 : ch); + } + if (standardFields.containsKey(CLOCK_HOUR_OF_AMPM)) { + long ch = standardFields.remove(CLOCK_HOUR_OF_AMPM); + addFieldValue(HOUR_OF_AMPM, ch == 12 ? 0 : ch); + } + if (standardFields.containsKey(AMPM_OF_DAY) && standardFields.containsKey(HOUR_OF_AMPM)) { + long ap = standardFields.remove(AMPM_OF_DAY); + long hap = standardFields.remove(HOUR_OF_AMPM); + addFieldValue(HOUR_OF_DAY, ap * 12 + hap); + } +// if (timeFields.containsKey(HOUR_OF_DAY) && timeFields.containsKey(MINUTE_OF_HOUR)) { +// long hod = timeFields.remove(HOUR_OF_DAY); +// long moh = timeFields.remove(MINUTE_OF_HOUR); +// addFieldValue(MINUTE_OF_DAY, hod * 60 + moh); +// } +// if (timeFields.containsKey(MINUTE_OF_DAY) && timeFields.containsKey(SECOND_OF_MINUTE)) { +// long mod = timeFields.remove(MINUTE_OF_DAY); +// long som = timeFields.remove(SECOND_OF_MINUTE); +// addFieldValue(SECOND_OF_DAY, mod * 60 + som); +// } + if (standardFields.containsKey(NANO_OF_DAY)) { + long nod = standardFields.remove(NANO_OF_DAY); + addFieldValue(SECOND_OF_DAY, nod / 1000_000_000L); + addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L); + } + if (standardFields.containsKey(MICRO_OF_DAY)) { + long cod = standardFields.remove(MICRO_OF_DAY); + addFieldValue(SECOND_OF_DAY, cod / 1000_000L); + addFieldValue(MICRO_OF_SECOND, cod % 1000_000L); + } + if (standardFields.containsKey(MILLI_OF_DAY)) { + long lod = standardFields.remove(MILLI_OF_DAY); + addFieldValue(SECOND_OF_DAY, lod / 1000); + addFieldValue(MILLI_OF_SECOND, lod % 1000); + } + if (standardFields.containsKey(SECOND_OF_DAY)) { + long sod = standardFields.remove(SECOND_OF_DAY); + addFieldValue(HOUR_OF_DAY, sod / 3600); + addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60); + addFieldValue(SECOND_OF_MINUTE, sod % 60); + } + if (standardFields.containsKey(MINUTE_OF_DAY)) { + long mod = standardFields.remove(MINUTE_OF_DAY); + addFieldValue(HOUR_OF_DAY, mod / 60); + addFieldValue(MINUTE_OF_HOUR, mod % 60); + } + +// long sod = nod / 1000_000_000L; +// addFieldValue(HOUR_OF_DAY, sod / 3600); +// addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60); +// addFieldValue(SECOND_OF_MINUTE, sod % 60); +// addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L); + if (standardFields.containsKey(MILLI_OF_SECOND) && standardFields.containsKey(MICRO_OF_SECOND)) { + long los = standardFields.remove(MILLI_OF_SECOND); + long cos = standardFields.get(MICRO_OF_SECOND); + addFieldValue(MICRO_OF_SECOND, los * 1000 + (cos % 1000)); + } + + Long hod = standardFields.get(HOUR_OF_DAY); + Long moh = standardFields.get(MINUTE_OF_HOUR); + Long som = standardFields.get(SECOND_OF_MINUTE); + Long nos = standardFields.get(NANO_OF_SECOND); + if (hod != null) { + int hodVal = Math.toIntExact(hod); + if (moh != null) { + int mohVal = Math.toIntExact(moh); + if (som != null) { + int somVal = Math.toIntExact(som); + if (nos != null) { + int nosVal = Math.toIntExact(nos); + addCalendrical(LocalTime.of(hodVal, mohVal, somVal, nosVal)); + } else { + addCalendrical(LocalTime.of(hodVal, mohVal, somVal)); + } + } else { + addCalendrical(LocalTime.of(hodVal, mohVal)); + } + } else { + addCalendrical(LocalTime.of(hodVal, 0)); + } + } + } + + private void splitObjects() { + List objectsToAdd = new ArrayList<>(); + for (Object object : objects) { + if (object instanceof LocalDate || object instanceof LocalTime || + object instanceof ZoneId || object instanceof Chrono) { + continue; + } + if (object instanceof ZoneOffset || object instanceof Instant) { + objectsToAdd.add(object); + + } else if (object instanceof TemporalAccessor) { + // TODO +// TemporalAccessor dt = (TemporalAccessor) object; +// objectsToAdd.add(dt.extract(LocalDate.class)); +// objectsToAdd.add(dt.extract(LocalTime.class)); +// objectsToAdd.add(dt.extract(ZoneId.class)); +// objectsToAdd.add(dt.extract(Chrono.class)); + } + } + for (Object object : objectsToAdd) { + if (object != null) { + addCalendrical(object); + } + } + } + + //----------------------------------------------------------------------- + @Override + public R query(TemporalQuery query) { + if (query == Queries.zoneId()) { + return (R) extract(ZoneId.class); + } + if (query == Queries.offset()) { + ZoneOffset offset = extract(ZoneOffset.class); + if (offset == null && standardFields.containsKey(OFFSET_SECONDS)) { + offset = ZoneOffset.ofTotalSeconds(Math.toIntExact(standardFields.get(OFFSET_SECONDS))); + } + return (R) offset; + } + if (query == Queries.chrono()) { + return extract(Chrono.class); + } + // incomplete, so no need to handle PRECISION + return TemporalAccessor.super.query(query); + } + + @SuppressWarnings("unchecked") + public R extract(Class type) { + R result = null; + for (Object obj : objects) { + if (type.isInstance(obj)) { + if (result != null && result.equals(obj) == false) { + throw new DateTimeException("Conflict found: " + type.getSimpleName() + " differs " + result + " vs " + obj + ": " + this); + } + result = (R) obj; + } + } + return result; + } + + //----------------------------------------------------------------------- + /** + * Clones this builder, creating a new independent copy referring to the + * same map of fields and objects. + * + * @return the cloned builder, not null + */ + @Override + public DateTimeBuilder clone() { + DateTimeBuilder dtb = new DateTimeBuilder(); + dtb.objects.addAll(this.objects); + dtb.standardFields.putAll(this.standardFields); + dtb.standardFields.putAll(this.standardFields); + if (this.otherFields != null) { + dtb.otherFields.putAll(this.otherFields); + } + return dtb; + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + StringBuilder buf = new StringBuilder(128); + buf.append("DateTimeBuilder["); + Map fields = getFieldValueMap(); + if (fields.size() > 0) { + buf.append("fields=").append(fields); + } + if (objects.size() > 0) { + if (fields.size() > 0) { + buf.append(", "); + } + buf.append("objects=").append(objects); + } + buf.append(']'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + @Override + public boolean isSupported(TemporalField field) { + return field != null && containsFieldValue(field); + } + + @Override + public long getLong(TemporalField field) { + return getFieldValue(field); + } + +} diff --git a/src/share/classes/java/time/format/DateTimeFormatStyleProvider.java b/src/share/classes/java/time/format/DateTimeFormatStyleProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..e50ce265629f6d7800173cabef756aa24892218e --- /dev/null +++ b/src/share/classes/java/time/format/DateTimeFormatStyleProvider.java @@ -0,0 +1,154 @@ +/* + * 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 java.time.format; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.temporal.Chrono; +import java.util.Locale; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A provider to obtain date-time formatters for a style. + *

+ * + *

Specification for implementors

+ * This implementation is based on extraction of data from a {@link SimpleDateFormat}. + * This class is immutable and thread-safe. + * This Implementations caches the returned formatters. + * + * @since 1.8 + */ +final class DateTimeFormatStyleProvider { + // TODO: Better implementation based on CLDR + + /** Cache of formatters. */ + private static final ConcurrentMap FORMATTER_CACHE = new ConcurrentHashMap<>(16, 0.75f, 2); + + private DateTimeFormatStyleProvider() {} + + /** + * Gets an Instance of the provider of format styles. + * + * @return the provider, not null + */ + static DateTimeFormatStyleProvider getInstance() { + return new DateTimeFormatStyleProvider(); + } + + /** + * Gets a localized date, time or date-time formatter. + *

+ * The formatter will be the most appropriate to use for the date and time style in the locale. + * For example, some locales will use the month name while others will use the number. + * + * @param dateStyle the date formatter style to obtain, null to obtain a time formatter + * @param timeStyle the time formatter style to obtain, null to obtain a date formatter + * @param chrono the chronology to use, not null + * @param locale the locale to use, not null + * @return the date-time formatter, not null + * @throws IllegalArgumentException if both format styles are null or if the locale is not recognized + */ + public DateTimeFormatter getFormatter( + FormatStyle dateStyle, FormatStyle timeStyle, Chrono chrono, Locale locale) { + if (dateStyle == null && timeStyle == null) { + throw new IllegalArgumentException("Date and Time style must not both be null"); + } + String key = chrono.getId() + '|' + locale.toString() + '|' + dateStyle + timeStyle; + Object cached = FORMATTER_CACHE.get(key); + if (cached != null) { + if (cached.equals("")) { + throw new IllegalArgumentException("Unable to convert DateFormat to DateTimeFormatter"); + } + return (DateTimeFormatter) cached; + } + DateFormat dateFormat; + if (dateStyle != null) { + if (timeStyle != null) { + dateFormat = DateFormat.getDateTimeInstance(convertStyle(dateStyle), convertStyle(timeStyle), locale); + } else { + dateFormat = DateFormat.getDateInstance(convertStyle(dateStyle), locale); + } + } else { + dateFormat = DateFormat.getTimeInstance(convertStyle(timeStyle), locale); + } + if (dateFormat instanceof SimpleDateFormat) { + String pattern = ((SimpleDateFormat) dateFormat).toPattern(); + DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale); + FORMATTER_CACHE.putIfAbsent(key, formatter); + return formatter; + } + FORMATTER_CACHE.putIfAbsent(key, ""); + throw new IllegalArgumentException("Unable to convert DateFormat to DateTimeFormatter"); + } + + /** + * Converts the enum style to the old format style. + * @param style the enum style, not null + * @return the int style + */ + private int convertStyle(FormatStyle style) { + return style.ordinal(); // indices happen to align + } + +} diff --git a/src/share/classes/java/time/format/DateTimeFormatSymbols.java b/src/share/classes/java/time/format/DateTimeFormatSymbols.java new file mode 100644 index 0000000000000000000000000000000000000000..14a116760f1ac008eeee19057d74c52fafe9c2c6 --- /dev/null +++ b/src/share/classes/java/time/format/DateTimeFormatSymbols.java @@ -0,0 +1,369 @@ +/* + * 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 java.time.format; + +import java.text.DecimalFormatSymbols; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Localized symbols used in date and time formatting. + *

+ * A significant part of dealing with dates and times is the localization. + * This class acts as a central point for accessing the information. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class DateTimeFormatSymbols { + + /** + * The standard set of non-localized symbols. + *

+ * This uses standard ASCII characters for zero, positive, negative and a dot for the decimal point. + */ + public static final DateTimeFormatSymbols STANDARD = new DateTimeFormatSymbols('0', '+', '-', '.'); + /** + * The cache of symbols instances. + */ + private static final ConcurrentMap CACHE = new ConcurrentHashMap<>(16, 0.75f, 2); + + /** + * The zero digit. + */ + private final char zeroDigit; + /** + * The positive sign. + */ + private final char positiveSign; + /** + * The negative sign. + */ + private final char negativeSign; + /** + * The decimal separator. + */ + private final char decimalSeparator; + + //----------------------------------------------------------------------- + /** + * Lists all the locales that are supported. + *

+ * The locale 'en_US' will always be present. + * + * @return an array of locales for which localization is supported + */ + public static Locale[] getAvailableLocales() { + return DecimalFormatSymbols.getAvailableLocales(); + } + + /** + * Obtains symbols for the default locale. + *

+ * This method provides access to locale sensitive symbols. + * + * @return the info, not null + */ + public static DateTimeFormatSymbols ofDefaultLocale() { + return of(Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Obtains symbols for the specified locale. + *

+ * This method provides access to locale sensitive symbols. + * + * @param locale the locale, not null + * @return the info, not null + */ + public static DateTimeFormatSymbols of(Locale locale) { + Objects.requireNonNull(locale, "locale"); + DateTimeFormatSymbols info = CACHE.get(locale); + if (info == null) { + info = create(locale); + CACHE.putIfAbsent(locale, info); + info = CACHE.get(locale); + } + return info; + } + + private static DateTimeFormatSymbols create(Locale locale) { + DecimalFormatSymbols oldSymbols = DecimalFormatSymbols.getInstance(locale); + char zeroDigit = oldSymbols.getZeroDigit(); + char positiveSign = '+'; + char negativeSign = oldSymbols.getMinusSign(); + char decimalSeparator = oldSymbols.getDecimalSeparator(); + if (zeroDigit == '0' && negativeSign == '-' && decimalSeparator == '.') { + return STANDARD; + } + return new DateTimeFormatSymbols(zeroDigit, positiveSign, negativeSign, decimalSeparator); + } + + //----------------------------------------------------------------------- + /** + * Restricted constructor. + * + * @param zeroChar the character to use for the digit of zero + * @param positiveSignChar the character to use for the positive sign + * @param negativeSignChar the character to use for the negative sign + * @param decimalPointChar the character to use for the decimal point + */ + private DateTimeFormatSymbols(char zeroChar, char positiveSignChar, char negativeSignChar, char decimalPointChar) { + this.zeroDigit = zeroChar; + this.positiveSign = positiveSignChar; + this.negativeSign = negativeSignChar; + this.decimalSeparator = decimalPointChar; + } + + //----------------------------------------------------------------------- + /** + * Gets the character that represents zero. + *

+ * The character used to represent digits may vary by culture. + * This method specifies the zero character to use, which implies the characters for one to nine. + * + * @return the character for zero + */ + public char getZeroDigit() { + return zeroDigit; + } + + /** + * Returns a copy of the info with a new character that represents zero. + *

+ * The character used to represent digits may vary by culture. + * This method specifies the zero character to use, which implies the characters for one to nine. + * + * @param zeroDigit the character for zero + * @return a copy with a new character that represents zero, not null + + */ + public DateTimeFormatSymbols withZeroDigit(char zeroDigit) { + if (zeroDigit == this.zeroDigit) { + return this; + } + return new DateTimeFormatSymbols(zeroDigit, positiveSign, negativeSign, decimalSeparator); + } + + //----------------------------------------------------------------------- + /** + * Gets the character that represents the positive sign. + *

+ * The character used to represent a positive number may vary by culture. + * This method specifies the character to use. + * + * @return the character for the positive sign + */ + public char getPositiveSign() { + return positiveSign; + } + + /** + * Returns a copy of the info with a new character that represents the positive sign. + *

+ * The character used to represent a positive number may vary by culture. + * This method specifies the character to use. + * + * @param positiveSign the character for the positive sign + * @return a copy with a new character that represents the positive sign, not null + */ + public DateTimeFormatSymbols withPositiveSign(char positiveSign) { + if (positiveSign == this.positiveSign) { + return this; + } + return new DateTimeFormatSymbols(zeroDigit, positiveSign, negativeSign, decimalSeparator); + } + + //----------------------------------------------------------------------- + /** + * Gets the character that represents the negative sign. + *

+ * The character used to represent a negative number may vary by culture. + * This method specifies the character to use. + * + * @return the character for the negative sign + */ + public char getNegativeSign() { + return negativeSign; + } + + /** + * Returns a copy of the info with a new character that represents the negative sign. + *

+ * The character used to represent a negative number may vary by culture. + * This method specifies the character to use. + * + * @param negativeSign the character for the negative sign + * @return a copy with a new character that represents the negative sign, not null + */ + public DateTimeFormatSymbols withNegativeSign(char negativeSign) { + if (negativeSign == this.negativeSign) { + return this; + } + return new DateTimeFormatSymbols(zeroDigit, positiveSign, negativeSign, decimalSeparator); + } + + //----------------------------------------------------------------------- + /** + * Gets the character that represents the decimal point. + *

+ * The character used to represent a decimal point may vary by culture. + * This method specifies the character to use. + * + * @return the character for the decimal point + */ + public char getDecimalSeparator() { + return decimalSeparator; + } + + /** + * Returns a copy of the info with a new character that represents the decimal point. + *

+ * The character used to represent a decimal point may vary by culture. + * This method specifies the character to use. + * + * @param decimalSeparator the character for the decimal point + * @return a copy with a new character that represents the decimal point, not null + */ + public DateTimeFormatSymbols withDecimalSeparator(char decimalSeparator) { + if (decimalSeparator == this.decimalSeparator) { + return this; + } + return new DateTimeFormatSymbols(zeroDigit, positiveSign, negativeSign, decimalSeparator); + } + + //----------------------------------------------------------------------- + /** + * Checks whether the character is a digit, based on the currently set zero character. + * + * @param ch the character to check + * @return the value, 0 to 9, of the character, or -1 if not a digit + */ + int convertToDigit(char ch) { + int val = ch - zeroDigit; + return (val >= 0 && val <= 9) ? val : -1; + } + + /** + * Converts the input numeric text to the internationalized form using the zero character. + * + * @param numericText the text, consisting of digits 0 to 9, to convert, not null + * @return the internationalized text, not null + */ + String convertNumberToI18N(String numericText) { + if (zeroDigit == '0') { + return numericText; + } + int diff = zeroDigit - '0'; + char[] array = numericText.toCharArray(); + for (int i = 0; i < array.length; i++) { + array[i] = (char) (array[i] + diff); + } + return new String(array); + } + + //----------------------------------------------------------------------- + /** + * Checks if these symbols equal another set of symbols. + * + * @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 DateTimeFormatSymbols) { + DateTimeFormatSymbols other = (DateTimeFormatSymbols) obj; + return (zeroDigit == other.zeroDigit && positiveSign == other.positiveSign && + negativeSign == other.negativeSign && decimalSeparator == other.decimalSeparator); + } + return false; + } + + /** + * A hash code for these symbols. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return zeroDigit + positiveSign + negativeSign + decimalSeparator; + } + + //----------------------------------------------------------------------- + /** + * Returns a string describing these symbols. + * + * @return a string description, not null + */ + @Override + public String toString() { + return "Symbols[" + zeroDigit + positiveSign + negativeSign + decimalSeparator + "]"; + } + +} diff --git a/src/share/classes/java/time/format/DateTimeFormatter.java b/src/share/classes/java/time/format/DateTimeFormatter.java new file mode 100644 index 0000000000000000000000000000000000000000..1b9aa2c8e4d1af8d7f41cc6f31492b52032358ad --- /dev/null +++ b/src/share/classes/java/time/format/DateTimeFormatter.java @@ -0,0 +1,655 @@ +/* + * 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 java.time.format; + +import java.io.IOException; +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParseException; +import java.text.ParsePosition; +import java.time.DateTimeException; +import java.time.ZoneId; +import java.time.format.DateTimeFormatterBuilder.CompositePrinterParser; +import java.time.temporal.Chrono; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQuery; +import java.util.Locale; +import java.util.Objects; + +/** + * Formatter for printing and parsing date-time objects. + *

+ * This class provides the main application entry point for printing and parsing. + * Common instances of {@code DateTimeFormatter} are provided by {@link DateTimeFormatters}. + * For more complex formatters, a {@link DateTimeFormatterBuilder builder} is provided. + *

+ * In most cases, it is not necessary to use this class directly when formatting. + * The main date-time classes provide two methods - one for printing, + * {@code toString(DateTimeFormatter formatter)}, and one for parsing, + * {@code parse(CharSequence text, DateTimeFormatter formatter)}. + * For example: + *

+ *  String text = date.toString(formatter);
+ *  LocalDate date = LocalDate.parse(text, formatter);
+ * 
+ * Some aspects of printing and parsing are dependent on the locale. + * The locale can be changed using the {@link #withLocale(Locale)} method + * which returns a new formatter in the requested locale. + *

+ * Some applications may need to use the older {@link Format} class for formatting. + * The {@link #toFormat()} method returns an implementation of the old API. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class DateTimeFormatter { + + /** + * The printer and/or parser to use, not null. + */ + private final CompositePrinterParser printerParser; + /** + * The locale to use for formatting, not null. + */ + private final Locale locale; + /** + * The symbols to use for formatting, not null. + */ + private final DateTimeFormatSymbols symbols; + /** + * The chronology to use for formatting, null for no override. + */ + private final Chrono chrono; + /** + * The zone to use for formatting, null for no override. + */ + private final ZoneId zone; + + /** + * Constructor. + * + * @param printerParser the printer/parser to use, not null + * @param locale the locale to use, not null + * @param symbols the symbols to use, not null + * @param chrono the chronology to use, null for no override + * @param zone the zone to use, null for no override + */ + DateTimeFormatter(CompositePrinterParser printerParser, Locale locale, + DateTimeFormatSymbols symbols, Chrono chrono, ZoneId zone) { + this.printerParser = Objects.requireNonNull(printerParser, "printerParser"); + this.locale = Objects.requireNonNull(locale, "locale"); + this.symbols = Objects.requireNonNull(symbols, "symbols"); + this.chrono = chrono; + this.zone = zone; + } + + //----------------------------------------------------------------------- + /** + * Gets the locale to be used during formatting. + *

+ * This is used to lookup any part of the formatter needing specific + * localization, such as the text or localized pattern. + * + * @return the locale of this formatter, not null + */ + public Locale getLocale() { + return locale; + } + + /** + * Returns a copy of this formatter with a new locale. + *

+ * This is used to lookup any part of the formatter needing specific + * localization, such as the text or localized pattern. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param locale the new locale, not null + * @return a formatter based on this formatter with the requested locale, not null + */ + public DateTimeFormatter withLocale(Locale locale) { + if (this.locale.equals(locale)) { + return this; + } + return new DateTimeFormatter(printerParser, locale, symbols, chrono, zone); + } + + //----------------------------------------------------------------------- + /** + * Gets the set of symbols to be used during formatting. + * + * @return the locale of this formatter, not null + */ + public DateTimeFormatSymbols getSymbols() { + return symbols; + } + + /** + * Returns a copy of this formatter with a new set of symbols. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param symbols the new symbols, not null + * @return a formatter based on this formatter with the requested symbols, not null + */ + public DateTimeFormatter withSymbols(DateTimeFormatSymbols symbols) { + if (this.symbols.equals(symbols)) { + return this; + } + return new DateTimeFormatter(printerParser, locale, symbols, chrono, zone); + } + + //----------------------------------------------------------------------- + /** + * Gets the overriding chronology to be used during formatting. + *

+ * This returns the override chronology, used to convert dates. + * By default, a formatter has no override chronology, returning null. + * See {@link #withChrono(Chrono)} for more details on overriding. + * + * @return the chronology of this formatter, null if no override + */ + public Chrono getChrono() { + return chrono; + } + + /** + * Returns a copy of this formatter with a new override chronology. + *

+ * This returns a formatter with similar state to this formatter but + * with the override chronology set. + * By default, a formatter has no override chronology, returning null. + *

+ * If an override is added, then any date that is printed or parsed will be affected. + *

+ * When printing, if the {@code Temporal} object contains a date then it will + * be converted to a date in the override chronology. + * Any time or zone will be retained unless overridden. + * The converted result will behave in a manner equivalent to an implementation + * of {@code ChronoLocalDate},{@code ChronoLocalDateTime} or {@code ChronoZonedDateTime}. + *

+ * When parsing, the override chronology will be used to interpret the + * {@link java.time.temporal.ChronoField fields} into a date unless the + * formatter directly parses a valid chronology. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param chrono the new chronology, not null + * @return a formatter based on this formatter with the requested override chronology, not null + */ + public DateTimeFormatter withChrono(Chrono chrono) { + if (Objects.equals(this.chrono, chrono)) { + return this; + } + return new DateTimeFormatter(printerParser, locale, symbols, chrono, zone); + } + + //----------------------------------------------------------------------- + /** + * Gets the overriding zone to be used during formatting. + *

+ * This returns the override zone, used to convert instants. + * By default, a formatter has no override zone, returning null. + * See {@link #withZone(ZoneId)} for more details on overriding. + * + * @return the chronology of this formatter, null if no override + */ + public ZoneId getZone() { + return zone; + } + + /** + * Returns a copy of this formatter with a new override zone. + *

+ * This returns a formatter with similar state to this formatter but + * with the override zone set. + * By default, a formatter has no override zone, returning null. + *

+ * If an override is added, then any instant that is printed or parsed will be affected. + *

+ * When printing, if the {@code Temporal} object contains an instant then it will + * be converted to a zoned date-time using the override zone. + * If the input has a chronology then it will be retained unless overridden. + * If the input does not have a chronology, such as {@code Instant}, then + * the ISO chronology will be used. + * The converted result will behave in a manner equivalent to an implementation + * of {@code ChronoZonedDateTime}. + *

+ * When parsing, the override zone will be used to interpret the + * {@link java.time.temporal.ChronoField fields} into an instant unless the + * formatter directly parses a valid zone. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param zone the new override zone, not null + * @return a formatter based on this formatter with the requested override zone, not null + */ + public DateTimeFormatter withZone(ZoneId zone) { + if (Objects.equals(this.zone, zone)) { + return this; + } + return new DateTimeFormatter(printerParser, locale, symbols, chrono, zone); + } + + //----------------------------------------------------------------------- + /** + * Prints a date-time object using this formatter. + *

+ * This prints the date-time to a String using the rules of the formatter. + * + * @param temporal the temporal object to print, not null + * @return the printed string, not null + * @throws DateTimeException if an error occurs during printing + */ + public String print(TemporalAccessor temporal) { + StringBuilder buf = new StringBuilder(32); + printTo(temporal, buf); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * Prints a date-time object to an {@code Appendable} using this formatter. + *

+ * This prints the date-time to the specified destination. + * {@link Appendable} is a general purpose interface that is implemented by all + * key character output classes including {@code StringBuffer}, {@code StringBuilder}, + * {@code PrintStream} and {@code Writer}. + *

+ * Although {@code Appendable} methods throw an {@code IOException}, this method does not. + * Instead, any {@code IOException} is wrapped in a runtime exception. + * See {@link DateTimePrintException#rethrowIOException()} for a means + * to extract the {@code IOException}. + * + * @param temporal the temporal object to print, not null + * @param appendable the appendable to print to, not null + * @throws DateTimeException if an error occurs during printing + */ + public void printTo(TemporalAccessor temporal, Appendable appendable) { + Objects.requireNonNull(temporal, "temporal"); + Objects.requireNonNull(appendable, "appendable"); + try { + DateTimePrintContext context = new DateTimePrintContext(temporal, this); + if (appendable instanceof StringBuilder) { + printerParser.print(context, (StringBuilder) appendable); + } else { + // buffer output to avoid writing to appendable in case of error + StringBuilder buf = new StringBuilder(32); + printerParser.print(context, buf); + appendable.append(buf); + } + } catch (IOException ex) { + throw new DateTimePrintException(ex.getMessage(), ex); + } + } + + //----------------------------------------------------------------------- + /** + * Fully parses the text producing an object of the specified type. + *

+ * Most applications should use this method for parsing. + * It parses the entire text to produce the required date-time. + * The query is typically a method reference to a {@code from(TemporalAccessor)} method. + * For example: + *

+     *  LocalDateTime dt = parser.parse(str, LocalDateTime::from);
+     * 
+ * If the parse completes without reading the entire length of the text, + * or a problem occurs during parsing or merging, then an exception is thrown. + * + * @param the type of the parsed date-time + * @param text the text to parse, not null + * @param query the query defining the type to parse to, not null + * @return the parsed date-time, not null + * @throws DateTimeParseException if the parse fails + */ + public T parse(CharSequence text, TemporalQuery query) { + Objects.requireNonNull(text, "text"); + Objects.requireNonNull(query, "query"); + String str = text.toString(); // parsing whole String, so this makes sense + try { + DateTimeBuilder builder = parseToBuilder(str).resolve(); + return builder.query(query); + } catch (DateTimeParseException ex) { + throw ex; + } catch (RuntimeException ex) { + throw createError(str, ex); + } + } + + /** + * Fully parses the text producing an object of one of the specified types. + *

+ * This parse method is convenient for use when the parser can handle optional elements. + * For example, a pattern of 'yyyy-MM[-dd[Z]]' can be fully parsed to an {@code OffsetDate}, + * or partially parsed to a {@code LocalDate} or a {@code YearMonth}. + * The queries must be specified in order, starting from the best matching full-parse option + * and ending with the worst matching minimal parse option. + * The query is typically a method reference to a {@code from(TemporalAccessor)} method. + *

+ * The result is associated with the first type that successfully parses. + * Normally, applications will use {@code instanceof} to check the result. + * For example: + *

+     *  TemporalAccessor dt = parser.parseBest(str, OffsetDate::from, LocalDate::from);
+     *  if (dt instanceof OffsetDate) {
+     *   ...
+     *  } else {
+     *   ...
+     *  }
+     * 
+ * If the parse completes without reading the entire length of the text, + * or a problem occurs during parsing or merging, then an exception is thrown. + * + * @param text the text to parse, not null + * @param queries the queries defining the types to attempt to parse to, + * must implement {@code TemporalAccessor}, not null + * @return the parsed date-time, not null + * @throws IllegalArgumentException if less than 2 types are specified + * @throws DateTimeException if none of the queries can be parsed from the input + * @throws DateTimeParseException if the parse fails + */ + public TemporalAccessor parseBest(CharSequence text, TemporalQuery... queries) { + Objects.requireNonNull(text, "text"); + Objects.requireNonNull(queries, "queries"); + if (queries.length < 2) { + throw new IllegalArgumentException("At least two queries must be specified"); + } + String str = text.toString(); // parsing whole String, so this makes sense + try { + DateTimeBuilder builder = parseToBuilder(str).resolve(); + for (TemporalQuery query : queries) { + try { + return (TemporalAccessor) builder.query(query); + } catch (RuntimeException ex) { + // continue + } + } + throw new DateTimeException("Unable to convert parsed text using any of the specified queries"); + } catch (DateTimeParseException ex) { + throw ex; + } catch (RuntimeException ex) { + throw createError(str, ex); + } + } + + private DateTimeParseException createError(String str, RuntimeException ex) { + String abbr = str; + if (abbr.length() > 64) { + abbr = abbr.substring(0, 64) + "..."; + } + return new DateTimeParseException("Text '" + abbr + "' could not be parsed: " + ex.getMessage(), str, 0, ex); + } + + //----------------------------------------------------------------------- + /** + * Parses the text to a builder. + *

+ * This parses to a {@code DateTimeBuilder} ensuring that the text is fully parsed. + * This method throws {@link DateTimeParseException} if unable to parse, or + * some other {@code DateTimeException} if another date/time problem occurs. + * + * @param text the text to parse, not null + * @return the engine representing the result of the parse, not null + * @throws DateTimeParseException if the parse fails + */ + public DateTimeBuilder parseToBuilder(CharSequence text) { + Objects.requireNonNull(text, "text"); + String str = text.toString(); // parsing whole String, so this makes sense + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder result = parseToBuilder(str, pos); + if (result == null || pos.getErrorIndex() >= 0 || pos.getIndex() < str.length()) { + String abbr = str; + if (abbr.length() > 64) { + abbr = abbr.substring(0, 64) + "..."; + } + if (pos.getErrorIndex() >= 0) { + throw new DateTimeParseException("Text '" + abbr + "' could not be parsed at index " + + pos.getErrorIndex(), str, pos.getErrorIndex()); + } else { + throw new DateTimeParseException("Text '" + abbr + "' could not be parsed, unparsed text found at index " + + pos.getIndex(), str, pos.getIndex()); + } + } + return result; + } + + /** + * Parses the text to a builder. + *

+ * This parses to a {@code DateTimeBuilder} but does not require the input to be fully parsed. + *

+ * This method does not throw {@link DateTimeParseException}. + * Instead, errors are returned within the state of the specified parse position. + * Callers must check for errors before using the context. + *

+ * This method may throw some other {@code DateTimeException} if a date/time problem occurs. + * + * @param text the text to parse, not null + * @param position the position to parse from, updated with length parsed + * and the index of any error, not null + * @return the parsed text, null only if the parse results in an error + * @throws DateTimeException if some problem occurs during parsing + * @throws IndexOutOfBoundsException if the position is invalid + */ + public DateTimeBuilder parseToBuilder(CharSequence text, ParsePosition position) { + Objects.requireNonNull(text, "text"); + Objects.requireNonNull(position, "position"); + DateTimeParseContext context = new DateTimeParseContext(this); + int pos = position.getIndex(); + pos = printerParser.parse(context, text, pos); + if (pos < 0) { + position.setErrorIndex(~pos); + return null; + } + position.setIndex(pos); + return context.toBuilder(); + } + + //----------------------------------------------------------------------- + /** + * Returns the formatter as a composite printer parser. + * + * @param optional whether the printer/parser should be optional + * @return the printer/parser, not null + */ + CompositePrinterParser toPrinterParser(boolean optional) { + return printerParser.withOptional(optional); + } + + /** + * Returns this formatter as a {@code java.text.Format} instance. + *

+ * The returned {@link Format} instance will print any {@link java.time.temporal.TemporalAccessor} + * and parses to a resolved {@link DateTimeBuilder}. + *

+ * Exceptions will follow the definitions of {@code Format}, see those methods + * for details about {@code IllegalArgumentException} during formatting and + * {@code ParseException} or null during parsing. + * The format does not support attributing of the returned format string. + * + * @return this formatter as a classic format instance, not null + */ + public Format toFormat() { + return new ClassicFormat(this, null); + } + + /** + * Returns this formatter as a {@code java.text.Format} instance that will + * parse using the specified query. + *

+ * The returned {@link Format} instance will print any {@link java.time.temporal.TemporalAccessor} + * and parses to the type specified. + * The type must be one that is supported by {@link #parse}. + *

+ * Exceptions will follow the definitions of {@code Format}, see those methods + * for details about {@code IllegalArgumentException} during formatting and + * {@code ParseException} or null during parsing. + * The format does not support attributing of the returned format string. + * + * @param parseQuery the query defining the type to parse to, not null + * @return this formatter as a classic format instance, not null + */ + public Format toFormat(TemporalQuery parseQuery) { + Objects.requireNonNull(parseQuery, "parseQuery"); + return new ClassicFormat(this, parseQuery); + } + + //----------------------------------------------------------------------- + /** + * Returns a description of the underlying formatters. + * + * @return a description of this formatter, not null + */ + @Override + public String toString() { + String pattern = printerParser.toString(); + pattern = pattern.startsWith("[") ? pattern : pattern.substring(1, pattern.length() - 1); + return pattern; + // TODO: Fix tests to not depend on toString() +// return "DateTimeFormatter[" + locale + +// (chrono != null ? "," + chrono : "") + +// (zone != null ? "," + zone : "") + +// pattern + "]"; + } + + //----------------------------------------------------------------------- + /** + * Implements the classic Java Format API. + * @serial exclude + */ + @SuppressWarnings("serial") // not actually serializable + static class ClassicFormat extends Format { + /** The formatter. */ + private final DateTimeFormatter formatter; + /** The type to be parsed. */ + private final TemporalQuery parseType; + /** Constructor. */ + public ClassicFormat(DateTimeFormatter formatter, TemporalQuery parseType) { + this.formatter = formatter; + this.parseType = parseType; + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + Objects.requireNonNull(obj, "obj"); + Objects.requireNonNull(toAppendTo, "toAppendTo"); + Objects.requireNonNull(pos, "pos"); + if (obj instanceof TemporalAccessor == false) { + throw new IllegalArgumentException("Format target must implement TemporalAccessor"); + } + pos.setBeginIndex(0); + pos.setEndIndex(0); + try { + formatter.printTo((TemporalAccessor) obj, toAppendTo); + } catch (RuntimeException ex) { + throw new IllegalArgumentException(ex.getMessage(), ex); + } + return toAppendTo; + } + @Override + public Object parseObject(String text) throws ParseException { + Objects.requireNonNull(text, "text"); + try { + if (parseType != null) { + return formatter.parse(text, parseType); + } + return formatter.parseToBuilder(text); + } catch (DateTimeParseException ex) { + throw new ParseException(ex.getMessage(), ex.getErrorIndex()); + } catch (RuntimeException ex) { + throw (ParseException) new ParseException(ex.getMessage(), 0).initCause(ex); + } + } + @Override + public Object parseObject(String text, ParsePosition pos) { + Objects.requireNonNull(text, "text"); + DateTimeBuilder builder; + try { + builder = formatter.parseToBuilder(text, pos); + } catch (IndexOutOfBoundsException ex) { + if (pos.getErrorIndex() < 0) { + pos.setErrorIndex(0); + } + return null; + } + if (builder == null) { + if (pos.getErrorIndex() < 0) { + pos.setErrorIndex(0); + } + return null; + } + if (parseType == null) { + return builder; + } + try { + return builder.resolve().query(parseType); + } catch (RuntimeException ex) { + pos.setErrorIndex(0); + return null; + } + } + } + +} diff --git a/src/share/classes/java/time/format/DateTimeFormatterBuilder.java b/src/share/classes/java/time/format/DateTimeFormatterBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..9ad63a6e6fc8582af1775cd58a617402a8fcea39 --- /dev/null +++ b/src/share/classes/java/time/format/DateTimeFormatterBuilder.java @@ -0,0 +1,3365 @@ +/* + * 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 java.time.format; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.INSTANT_SECONDS; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.OFFSET_SECONDS; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoField.YEAR; + +import java.lang.ref.SoftReference; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.text.ParsePosition; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeTextProvider.LocaleStore; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoField; +import java.time.temporal.ISOChrono; +import java.time.temporal.ISOFields; +import java.time.temporal.Queries; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.ValueRange; +import java.time.temporal.WeekFields; +import java.time.zone.ZoneRulesProvider; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; + +import sun.util.locale.provider.TimeZoneNameUtility; + +/** + * Builder to create date-time formatters. + *

+ * This allows a {@code DateTimeFormatter} to be created. + * All date-time formatters are created ultimately using this builder. + *

+ * The basic elements of date-time can all be added: + *

    + *
  • Value - a numeric value
  • + *
  • Fraction - a fractional value including the decimal place. Always use this when + * outputting fractions to ensure that the fraction is parsed correctly
  • + *
  • Text - the textual equivalent for the value
  • + *
  • OffsetId/Offset - the {@linkplain ZoneOffset zone offset}
  • + *
  • ZoneId - the {@linkplain ZoneId time-zone} id
  • + *
  • ZoneText - the name of the time-zone
  • + *
  • Literal - a text literal
  • + *
  • Nested and Optional - formats can be nested or made optional
  • + *
  • Other - the printer and parser interfaces can be used to add user supplied formatting
  • + *

+ * In addition, any of the elements may be decorated by padding, either with spaces or any other character. + *

+ * Finally, a shorthand pattern, mostly compatible with {@code java.text.SimpleDateFormat SimpleDateFormat} + * can be used, see {@link #appendPattern(String)}. + * In practice, this simply parses the pattern and calls other methods on the builder. + * + *

Specification for implementors

+ * This class is a mutable builder intended for use from a single thread. + * + * @since 1.8 + */ +public final class DateTimeFormatterBuilder { + + /** + * Query for a time-zone that is region-only. + */ + private static final TemporalQuery QUERY_REGION_ONLY = (temporal) -> { + ZoneId zone = temporal.query(Queries.zoneId()); + return (zone != null && zone instanceof ZoneOffset == false ? zone : null); + }; + + /** + * The currently active builder, used by the outermost builder. + */ + private DateTimeFormatterBuilder active = this; + /** + * The parent builder, null for the outermost builder. + */ + private final DateTimeFormatterBuilder parent; + /** + * The list of printers that will be used. + */ + private final List printerParsers = new ArrayList<>(); + /** + * Whether this builder produces an optional formatter. + */ + private final boolean optional; + /** + * The width to pad the next field to. + */ + private int padNextWidth; + /** + * The character to pad the next field with. + */ + private char padNextChar; + /** + * The index of the last variable width value parser. + */ + private int valueParserIndex = -1; + + /** + * Constructs a new instance of the builder. + */ + public DateTimeFormatterBuilder() { + super(); + parent = null; + optional = false; + } + + /** + * Constructs a new instance of the builder. + * + * @param parent the parent builder, not null + * @param optional whether the formatter is optional, not null + */ + private DateTimeFormatterBuilder(DateTimeFormatterBuilder parent, boolean optional) { + super(); + this.parent = parent; + this.optional = optional; + } + + //----------------------------------------------------------------------- + /** + * Changes the parse style to be case sensitive for the remainder of the formatter. + *

+ * Parsing can be case sensitive or insensitive - by default it is case sensitive. + * This method allows the case sensitivity setting of parsing to be changed. + *

+ * Calling this method changes the state of the builder such that all + * subsequent builder method calls will parse text in case sensitive mode. + * See {@link #parseCaseInsensitive} for the opposite setting. + * The parse case sensitive/insensitive methods may be called at any point + * in the builder, thus the parser can swap between case parsing modes + * multiple times during the parse. + *

+ * Since the default is case sensitive, this method should only be used after + * a previous call to {@code #parseCaseInsensitive}. + * + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder parseCaseSensitive() { + appendInternal(SettingsParser.SENSITIVE); + return this; + } + + /** + * Changes the parse style to be case insensitive for the remainder of the formatter. + *

+ * Parsing can be case sensitive or insensitive - by default it is case sensitive. + * This method allows the case sensitivity setting of parsing to be changed. + *

+ * Calling this method changes the state of the builder such that all + * subsequent builder method calls will parse text in case insensitive mode. + * See {@link #parseCaseSensitive()} for the opposite setting. + * The parse case sensitive/insensitive methods may be called at any point + * in the builder, thus the parser can swap between case parsing modes + * multiple times during the parse. + * + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder parseCaseInsensitive() { + appendInternal(SettingsParser.INSENSITIVE); + return this; + } + + //----------------------------------------------------------------------- + /** + * Changes the parse style to be strict for the remainder of the formatter. + *

+ * Parsing can be strict or lenient - by default its strict. + * This controls the degree of flexibility in matching the text and sign styles. + *

+ * When used, this method changes the parsing to be strict from this point onwards. + * As strict is the default, this is normally only needed after calling {@link #parseLenient()}. + * The change will remain in force until the end of the formatter that is eventually + * constructed or until {@code parseLenient} is called. + * + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder parseStrict() { + appendInternal(SettingsParser.STRICT); + return this; + } + + /** + * Changes the parse style to be lenient for the remainder of the formatter. + * Note that case sensitivity is set separately to this method. + *

+ * Parsing can be strict or lenient - by default its strict. + * This controls the degree of flexibility in matching the text and sign styles. + * Applications calling this method should typically also call {@link #parseCaseInsensitive()}. + *

+ * When used, this method changes the parsing to be lenient from this point onwards. + * The change will remain in force until the end of the formatter that is eventually + * constructed or until {@code parseStrict} is called. + * + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder parseLenient() { + appendInternal(SettingsParser.LENIENT); + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends the value of a date-time field to the formatter using a normal + * output style. + *

+ * The value of the field will be output during a print. + * If the value cannot be obtained then an exception will be thrown. + *

+ * The value will be printed as per the normal print of an integer value. + * Only negative numbers will be signed. No padding will be added. + *

+ * The parser for a variable width value such as this normally behaves greedily, + * requiring one digit, but accepting as many digits as possible. + * This behavior can be affected by 'adjacent value parsing'. + * See {@link #appendValue(java.time.temporal.TemporalField, int)} for full details. + * + * @param field the field to append, not null + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendValue(TemporalField field) { + Objects.requireNonNull(field, "field"); + active.valueParserIndex = appendInternal(new NumberPrinterParser(field, 1, 19, SignStyle.NORMAL)); + return this; + } + + /** + * Appends the value of a date-time field to the formatter using a fixed + * width, zero-padded approach. + *

+ * The value of the field will be output during a print. + * If the value cannot be obtained then an exception will be thrown. + *

+ * The value will be zero-padded on the left. If the size of the value + * means that it cannot be printed within the width then an exception is thrown. + * If the value of the field is negative then an exception is thrown during printing. + *

+ * This method supports a special technique of parsing known as 'adjacent value parsing'. + * This technique solves the problem where a variable length value is followed by one or more + * fixed length values. The standard parser is greedy, and thus it would normally + * steal the digits that are needed by the fixed width value parsers that follow the + * variable width one. + *

+ * No action is required to initiate 'adjacent value parsing'. + * When a call to {@code appendValue} with a variable width is made, the builder + * enters adjacent value parsing setup mode. If the immediately subsequent method + * call or calls on the same builder are to this method, then the parser will reserve + * space so that the fixed width values can be parsed. + *

+ * For example, consider {@code builder.appendValue(YEAR).appendValue(MONTH_OF_YEAR, 2);} + * The year is a variable width parse of between 1 and 19 digits. + * The month is a fixed width parse of 2 digits. + * Because these were appended to the same builder immediately after one another, + * the year parser will reserve two digits for the month to parse. + * Thus, the text '201106' will correctly parse to a year of 2011 and a month of 6. + * Without adjacent value parsing, the year would greedily parse all six digits and leave + * nothing for the month. + *

+ * Adjacent value parsing applies to each set of fixed width not-negative values in the parser + * that immediately follow any kind of variable width value. + * Calling any other append method will end the setup of adjacent value parsing. + * Thus, in the unlikely event that you need to avoid adjacent value parsing behavior, + * simply add the {@code appendValue} to another {@code DateTimeFormatterBuilder} + * and add that to this builder. + *

+ * If adjacent parsing is active, then parsing must match exactly the specified + * number of digits in both strict and lenient modes. + * In addition, no positive or negative sign is permitted. + * + * @param field the field to append, not null + * @param width the width of the printed field, from 1 to 19 + * @return this, for chaining, not null + * @throws IllegalArgumentException if the width is invalid + */ + public DateTimeFormatterBuilder appendValue(TemporalField field, int width) { + Objects.requireNonNull(field, "field"); + if (width < 1 || width > 19) { + throw new IllegalArgumentException("The width must be from 1 to 19 inclusive but was " + width); + } + NumberPrinterParser pp = new NumberPrinterParser(field, width, width, SignStyle.NOT_NEGATIVE); + return appendFixedWidth(width, pp); + } + + /** + * Appends the value of a date-time field to the formatter providing full + * control over printing. + *

+ * The value of the field will be output during a print. + * If the value cannot be obtained then an exception will be thrown. + *

+ * This method provides full control of the numeric formatting, including + * zero-padding and the positive/negative sign. + *

+ * The parser for a variable width value such as this normally behaves greedily, + * accepting as many digits as possible. + * This behavior can be affected by 'adjacent value parsing'. + * See {@link #appendValue(java.time.temporal.TemporalField, int)} for full details. + *

+ * In strict parsing mode, the minimum number of parsed digits is {@code minWidth}. + * In lenient parsing mode, the minimum number of parsed digits is one. + *

+ * If this method is invoked with equal minimum and maximum widths and a sign style of + * {@code NOT_NEGATIVE} then it delegates to {@code appendValue(TemporalField,int)}. + * In this scenario, the printing and parsing behavior described there occur. + * + * @param field the field to append, not null + * @param minWidth the minimum field width of the printed field, from 1 to 19 + * @param maxWidth the maximum field width of the printed field, from 1 to 19 + * @param signStyle the positive/negative output style, not null + * @return this, for chaining, not null + * @throws IllegalArgumentException if the widths are invalid + */ + public DateTimeFormatterBuilder appendValue( + TemporalField field, int minWidth, int maxWidth, SignStyle signStyle) { + if (minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE) { + return appendValue(field, maxWidth); + } + Objects.requireNonNull(field, "field"); + Objects.requireNonNull(signStyle, "signStyle"); + if (minWidth < 1 || minWidth > 19) { + throw new IllegalArgumentException("The minimum width must be from 1 to 19 inclusive but was " + minWidth); + } + if (maxWidth < 1 || maxWidth > 19) { + throw new IllegalArgumentException("The maximum width must be from 1 to 19 inclusive but was " + maxWidth); + } + if (maxWidth < minWidth) { + throw new IllegalArgumentException("The maximum width must exceed or equal the minimum width but " + + maxWidth + " < " + minWidth); + } + NumberPrinterParser pp = new NumberPrinterParser(field, minWidth, maxWidth, signStyle); + if (minWidth == maxWidth) { + appendInternal(pp); + } else { + active.valueParserIndex = appendInternal(pp); + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends the reduced value of a date-time field to the formatter. + *

+ * This is typically used for printing and parsing a two digit year. + * The {@code width} is the printed and parsed width. + * The {@code baseValue} is used during parsing to determine the valid range. + *

+ * For printing, the width is used to determine the number of characters to print. + * The rightmost characters are output to match the width, left padding with zero. + *

+ * For parsing, exactly the number of characters specified by the width are parsed. + * This is incomplete information however, so the base value is used to complete the parse. + * The base value is the first valid value in a range of ten to the power of width. + *

+ * For example, a base value of {@code 1980} and a width of {@code 2} will have + * valid values from {@code 1980} to {@code 2079}. + * During parsing, the text {@code "12"} will result in the value {@code 2012} as that + * is the value within the range where the last two digits are "12". + *

+ * This is a fixed width parser operating using 'adjacent value parsing'. + * See {@link #appendValue(java.time.temporal.TemporalField, int)} for full details. + * + * @param field the field to append, not null + * @param width the width of the printed and parsed field, from 1 to 18 + * @param baseValue the base value of the range of valid values + * @return this, for chaining, not null + * @throws IllegalArgumentException if the width or base value is invalid + */ + public DateTimeFormatterBuilder appendValueReduced( + TemporalField field, int width, int baseValue) { + Objects.requireNonNull(field, "field"); + ReducedPrinterParser pp = new ReducedPrinterParser(field, width, baseValue); + appendFixedWidth(width, pp); + return this; + } + + /** + * Appends a fixed width printer-parser. + * + * @param width the width + * @param pp the printer-parser, not null + * @return this, for chaining, not null + */ + private DateTimeFormatterBuilder appendFixedWidth(int width, NumberPrinterParser pp) { + if (active.valueParserIndex >= 0) { + // adjacent parsing mode, update setting in previous parsers + NumberPrinterParser basePP = (NumberPrinterParser) active.printerParsers.get(active.valueParserIndex); + basePP = basePP.withSubsequentWidth(width); + int activeValueParser = active.valueParserIndex; + active.printerParsers.set(active.valueParserIndex, basePP); + appendInternal(pp.withFixedWidth()); + active.valueParserIndex = activeValueParser; + } else { + // not adjacent parsing + appendInternal(pp); + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends the fractional value of a date-time field to the formatter. + *

+ * The fractional value of the field will be output including the + * preceding decimal point. The preceding value is not output. + * For example, the second-of-minute value of 15 would be output as {@code .25}. + *

+ * The width of the printed fraction can be controlled. Setting the + * minimum width to zero will cause no output to be generated. + * The printed fraction will have the minimum width necessary between + * the minimum and maximum widths - trailing zeroes are omitted. + * No rounding occurs due to the maximum width - digits are simply dropped. + *

+ * When parsing in strict mode, the number of parsed digits must be between + * the minimum and maximum width. When parsing in lenient mode, the minimum + * width is considered to be zero and the maximum is nine. + *

+ * If the value cannot be obtained then an exception will be thrown. + * If the value is negative an exception will be thrown. + * If the field does not have a fixed set of valid values then an + * exception will be thrown. + * If the field value in the date-time to be printed is invalid it + * cannot be printed and an exception will be thrown. + * + * @param field the field to append, not null + * @param minWidth the minimum width of the field excluding the decimal point, from 0 to 9 + * @param maxWidth the maximum width of the field excluding the decimal point, from 1 to 9 + * @param decimalPoint whether to output the localized decimal point symbol + * @return this, for chaining, not null + * @throws IllegalArgumentException if the field has a variable set of valid values or + * either width is invalid + */ + public DateTimeFormatterBuilder appendFraction( + TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) { + appendInternal(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint)); + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends the text of a date-time field to the formatter using the full + * text style. + *

+ * The text of the field will be output during a print. + * The value must be within the valid range of the field. + * If the value cannot be obtained then an exception will be thrown. + * If the field has no textual representation, then the numeric value will be used. + *

+ * The value will be printed as per the normal print of an integer value. + * Only negative numbers will be signed. No padding will be added. + * + * @param field the field to append, not null + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendText(TemporalField field) { + return appendText(field, TextStyle.FULL); + } + + /** + * Appends the text of a date-time field to the formatter. + *

+ * The text of the field will be output during a print. + * The value must be within the valid range of the field. + * If the value cannot be obtained then an exception will be thrown. + * If the field has no textual representation, then the numeric value will be used. + *

+ * The value will be printed as per the normal print of an integer value. + * Only negative numbers will be signed. No padding will be added. + * + * @param field the field to append, not null + * @param textStyle the text style to use, not null + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendText(TemporalField field, TextStyle textStyle) { + Objects.requireNonNull(field, "field"); + Objects.requireNonNull(textStyle, "textStyle"); + appendInternal(new TextPrinterParser(field, textStyle, DateTimeTextProvider.getInstance())); + return this; + } + + /** + * Appends the text of a date-time field to the formatter using the specified + * map to supply the text. + *

+ * The standard text outputting methods use the localized text in the JDK. + * This method allows that text to be specified directly. + * The supplied map is not validated by the builder to ensure that printing or + * parsing is possible, thus an invalid map may throw an error during later use. + *

+ * Supplying the map of text provides considerable flexibility in printing and parsing. + * For example, a legacy application might require or supply the months of the + * year as "JNY", "FBY", "MCH" etc. These do not match the standard set of text + * for localized month names. Using this method, a map can be created which + * defines the connection between each value and the text: + *

+     * Map<Long, String> map = new HashMap<>();
+     * map.put(1, "JNY");
+     * map.put(2, "FBY");
+     * map.put(3, "MCH");
+     * ...
+     * builder.appendText(MONTH_OF_YEAR, map);
+     * 
+ *

+ * Other uses might be to output the value with a suffix, such as "1st", "2nd", "3rd", + * or as Roman numerals "I", "II", "III", "IV". + *

+ * During printing, the value is obtained and checked that it is in the valid range. + * If text is not available for the value then it is output as a number. + * During parsing, the parser will match against the map of text and numeric values. + * + * @param field the field to append, not null + * @param textLookup the map from the value to the text + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendText(TemporalField field, Map textLookup) { + Objects.requireNonNull(field, "field"); + Objects.requireNonNull(textLookup, "textLookup"); + Map copy = new LinkedHashMap<>(textLookup); + Map> map = Collections.singletonMap(TextStyle.FULL, copy); + final LocaleStore store = new LocaleStore(map); + DateTimeTextProvider provider = new DateTimeTextProvider() { + @Override + public String getText(TemporalField field, long value, TextStyle style, Locale locale) { + return store.getText(value, style); + } + @Override + public Iterator> getTextIterator(TemporalField field, TextStyle style, Locale locale) { + return store.getTextIterator(style); + } + }; + appendInternal(new TextPrinterParser(field, TextStyle.FULL, provider)); + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends an instant using ISO-8601 to the formatter. + *

+ * Instants have a fixed output format. + * They are converted to a date-time with a zone-offset of UTC and printed + * using the standard ISO-8601 format. + *

+ * An alternative to this method is to print/parse the instant as a single + * epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}. + * + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendInstant() { + appendInternal(new InstantPrinterParser()); + return this; + } + + /** + * Appends the zone offset, such as '+01:00', to the formatter. + *

+ * This appends an instruction to print/parse the offset ID to the builder. + * This is equivalent to calling {@code appendOffset("HH:MM:ss", "Z")}. + * + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendOffsetId() { + appendInternal(OffsetIdPrinterParser.INSTANCE_ID); + return this; + } + + /** + * Appends the zone offset, such as '+01:00', to the formatter. + *

+ * This appends an instruction to print/parse the offset ID to the builder. + *

+ * During printing, the offset is obtained using a mechanism equivalent + * to querying the temporal with {@link Queries#offset()}. + * It will be printed using the format defined below. + * If the offset cannot be obtained then an exception is thrown unless the + * section of the formatter is optional. + *

+ * During parsing, the offset is parsed using the format defined below. + * If the offset cannot be parsed then an exception is thrown unless the + * section of the formatter is optional. + *

+ * The format of the offset is controlled by a pattern which must be one + * of the following: + *

    + *
  • {@code +HH} - hour only, ignoring any minute + *
  • {@code +HHMM} - hour and minute, no colon + *
  • {@code +HH:MM} - hour and minute, with colon + *
  • {@code +HHMMss} - hour and minute, with second if non-zero and no colon + *
  • {@code +HH:MM:ss} - hour and minute, with second if non-zero and colon + *
  • {@code +HHMMSS} - hour, minute and second, no colon + *
  • {@code +HH:MM:SS} - hour, minute and second, with colon + *

+ * The "no offset" text controls what text is printed when the offset is zero. + * Example values would be 'Z', '+00:00', 'UTC' or 'GMT'. + * Three formats are accepted for parsing UTC - the "no offset" text, and the + * plus and minus versions of zero defined by the pattern. + * + * @param pattern the pattern to use, not null + * @param noOffsetText the text to use when the offset is zero, not null + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendOffset(String pattern, String noOffsetText) { + appendInternal(new OffsetIdPrinterParser(noOffsetText, pattern)); + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter. + *

+ * This appends an instruction to print/parse the zone ID to the builder. + * The zone ID is obtained in a strict manner suitable for {@code ZonedDateTime}. + * By contrast, {@code OffsetDateTime} does not have a zone ID suitable + * for use with this method, see {@link #appendZoneOrOffsetId()}. + *

+ * During printing, the zone is obtained using a mechanism equivalent + * to querying the temporal with {@link Queries#zoneId()}. + * It will be printed using the result of {@link ZoneId#getId()}. + * If the zone cannot be obtained then an exception is thrown unless the + * section of the formatter is optional. + *

+ * During parsing, the zone is parsed and must match a known zone or offset. + * If the zone cannot be parsed then an exception is thrown unless the + * section of the formatter is optional. + * + * @return this, for chaining, not null + * @see #appendZoneRegionId() + */ + public DateTimeFormatterBuilder appendZoneId() { + appendInternal(new ZoneIdPrinterParser(Queries.zoneId(), "ZoneId()")); + return this; + } + + /** + * Appends the time-zone region ID, such as 'Europe/Paris', to the formatter, + * rejecting the zone ID if it is a {@code ZoneOffset}. + *

+ * This appends an instruction to print/parse the zone ID to the builder + * only if it is a region-based ID. + *

+ * During printing, the zone is obtained using a mechanism equivalent + * to querying the temporal with {@link Queries#zoneId()}. + * If the zone is a {@code ZoneOffset} or it cannot be obtained then + * an exception is thrown unless the section of the formatter is optional. + * If the zone is not an offset, then the zone will be printed using + * the zone ID from {@link ZoneId#getId()}. + *

+ * During parsing, the zone is parsed and must match a known zone or offset. + * If the zone cannot be parsed then an exception is thrown unless the + * section of the formatter is optional. + * Note that parsing accepts offsets, whereas printing will never produce + * one, thus parsing is equivalent to {@code appendZoneId}. + * + * @return this, for chaining, not null + * @see #appendZoneId() + */ + public DateTimeFormatterBuilder appendZoneRegionId() { + appendInternal(new ZoneIdPrinterParser(QUERY_REGION_ONLY, "ZoneRegionId()")); + return this; + } + + /** + * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to + * the formatter, using the best available zone ID. + *

+ * This appends an instruction to print/parse the best available + * zone or offset ID to the builder. + * The zone ID is obtained in a lenient manner that first attempts to + * find a true zone ID, such as that on {@code ZonedDateTime}, and + * then attempts to find an offset, such as that on {@code OffsetDateTime}. + *

+ * During printing, the zone is obtained using a mechanism equivalent + * to querying the temporal with {@link Queries#zone()}. + * It will be printed using the result of {@link ZoneId#getId()}. + * If the zone cannot be obtained then an exception is thrown unless the + * section of the formatter is optional. + *

+ * During parsing, the zone is parsed and must match a known zone or offset. + * If the zone cannot be parsed then an exception is thrown unless the + * section of the formatter is optional. + *

+ * This method is is identical to {@code appendZoneId()} except in the + * mechanism used to obtain the zone. + * + * @return this, for chaining, not null + * @see #appendZoneId() + */ + public DateTimeFormatterBuilder appendZoneOrOffsetId() { + appendInternal(new ZoneIdPrinterParser(Queries.zone(), "ZoneOrOffsetId()")); + return this; + } + + /** + * Appends the time-zone name, such as 'British Summer Time', to the formatter. + *

+ * This appends an instruction to print the textual name of the zone to the builder. + *

+ * During printing, the zone is obtained using a mechanism equivalent + * to querying the temporal with {@link Queries#zoneId()}. + * If the zone is a {@code ZoneOffset} it will be printed using the + * result of {@link ZoneOffset#getId()}. + * If the zone is not an offset, the textual name will be looked up + * for the locale set in the {@link DateTimeFormatter}. + * If the temporal object being printed represents an instant, then the text + * will be the summer or winter time text as appropriate. + * If the lookup for text does not find any suitable reuslt, then the + * {@link ZoneId#getId() ID} will be printed instead. + * If the zone cannot be obtained then an exception is thrown unless the + * section of the formatter is optional. + *

+ * Parsing is not currently supported. + * + * @param textStyle the text style to use, not null + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle) { + // TODO: parsing of zone text? +// * During parsing, either the textual zone name, the zone ID or the offset +// * is accepted. +// * If the zone cannot be parsed then an exception is thrown unless the +// * section of the formatter is optional. + appendInternal(new ZoneTextPrinterParser(textStyle)); + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends the chronology ID to the formatter. + *

+ * The chronology ID will be output during a print. + * If the chronology cannot be obtained then an exception will be thrown. + * + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendChronoId() { + appendInternal(new ChronoPrinterParser(null)); + return this; + } + + /** + * Appends the chronology name to the formatter. + *

+ * The calendar system name will be output during a print. + * If the chronology cannot be obtained then an exception will be thrown. + * The calendar system name is obtained from the formatting symbols. + * + * @param textStyle the text style to use, not null + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendChronoText(TextStyle textStyle) { + Objects.requireNonNull(textStyle, "textStyle"); + appendInternal(new ChronoPrinterParser(textStyle)); + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends a localized date-time pattern to the formatter. + *

+ * The pattern is resolved lazily using the locale being used during the print/parse + * (stored in {@link DateTimeFormatter}. + *

+ * The pattern can vary by chronology, although typically it doesn't. + * This method uses the standard ISO chronology patterns. + * + * @param dateStyle the date style to use, null means no date required + * @param timeStyle the time style to use, null means no time required + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendLocalized(FormatStyle dateStyle, FormatStyle timeStyle) { + return appendLocalized(dateStyle, timeStyle, ISOChrono.INSTANCE); + } + + /** + * Appends a localized date-time pattern to the formatter. + *

+ * The pattern is resolved lazily using the locale being used during the print/parse, + * stored in {@link DateTimeFormatter}. + *

+ * The pattern can vary by chronology, although typically it doesn't. + * This method allows the chronology to be specified. + * + * @param dateStyle the date style to use, null means no date required + * @param timeStyle the time style to use, null means no time required + * @param chrono the chronology to use, not null + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendLocalized(FormatStyle dateStyle, FormatStyle timeStyle, Chrono chrono) { + Objects.requireNonNull(chrono, "chrono"); + if (dateStyle != null || timeStyle != null) { + appendInternal(new LocalizedPrinterParser(dateStyle, timeStyle, chrono)); + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends a character literal to the formatter. + *

+ * This character will be output during a print. + * + * @param literal the literal to append, not null + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendLiteral(char literal) { + appendInternal(new CharLiteralPrinterParser(literal)); + return this; + } + + /** + * Appends a string literal to the formatter. + *

+ * This string will be output during a print. + *

+ * If the literal is empty, nothing is added to the formatter. + * + * @param literal the literal to append, not null + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendLiteral(String literal) { + Objects.requireNonNull(literal, "literal"); + if (literal.length() > 0) { + if (literal.length() == 1) { + appendInternal(new CharLiteralPrinterParser(literal.charAt(0))); + } else { + appendInternal(new StringLiteralPrinterParser(literal)); + } + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends all the elements of a formatter to the builder. + *

+ * This method has the same effect as appending each of the constituent + * parts of the formatter directly to this builder. + * + * @param formatter the formatter to add, not null + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder append(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + appendInternal(formatter.toPrinterParser(false)); + return this; + } + + /** + * Appends a formatter to the builder which will optionally print/parse. + *

+ * This method has the same effect as appending each of the constituent + * parts directly to this builder surrounded by an {@link #optionalStart()} and + * {@link #optionalEnd()}. + *

+ * The formatter will print if data is available for all the fields contained within it. + * The formatter will parse if the string matches, otherwise no error is returned. + * + * @param formatter the formatter to add, not null + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendOptional(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + appendInternal(formatter.toPrinterParser(true)); + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends the elements defined by the specified pattern to the builder. + *

+ * All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters. + * The characters '{' and '}' are reserved for future use. + * The characters '[' and ']' indicate optional patterns. + * The following pattern letters are defined: + *

+     *  Symbol  Meaning                     Presentation      Examples
+     *  ------  -------                     ------------      -------
+     *   G       era                         number/text       1; 01; AD; Anno Domini
+     *   y       year                        year              2004; 04
+     *   D       day-of-year                 number            189
+     *   M       month-of-year               number/text       7; 07; Jul; July; J
+     *   d       day-of-month                number            10
+     *
+     *   Q       quarter-of-year             number/text       3; 03; Q3
+     *   Y       week-based-year             year              1996; 96
+     *   w       week-of-year                number            27
+     *   W       week-of-month               number            27
+     *   e       localized day-of-week       number            2; Tue; Tuesday; T
+     *   E       day-of-week                 number/text       2; Tue; Tuesday; T
+     *   F       week-of-month               number            3
+     *
+     *   a       am-pm-of-day                text              PM
+     *   h       clock-hour-of-am-pm (1-12)  number            12
+     *   K       hour-of-am-pm (0-11)        number            0
+     *   k       clock-hour-of-am-pm (1-24)  number            0
+     *
+     *   H       hour-of-day (0-23)          number            0
+     *   m       minute-of-hour              number            30
+     *   s       second-of-minute            number            55
+     *   S       fraction-of-second          fraction          978
+     *   A       milli-of-day                number            1234
+     *   n       nano-of-second              number            987654321
+     *   N       nano-of-day                 number            1234000000
+     *
+     *   I       time-zone ID                zoneId            America/Los_Angeles
+     *   z       time-zone name              text              Pacific Standard Time; PST
+     *   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;
+     *   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
+     *
+     *   p       pad next                    pad modifier      1
+     *
+     *   '       escape for text             delimiter
+     *   ''      single quote                literal           '
+     *   [       optional section start
+     *   ]       optional section end
+     *   {}      reserved for future use
+     * 
+ *

+ * The count of pattern letters determine the format. + *

+ * Text: The text style is determined based on the number of pattern letters used. + * Less than 4 pattern letters will use the {@link TextStyle#SHORT short form}. + * Exactly 4 pattern letters will use the {@link TextStyle#FULL full form}. + * Exactly 5 pattern letters will use the {@link TextStyle#NARROW narrow form}. + *

+ * Number: If the count of letters is one, then the value is printed using the minimum number + * of digits and without padding as per {@link #appendValue(java.time.temporal.TemporalField)}. Otherwise, the + * count of digits is used as the width of the output field as per {@link #appendValue(java.time.temporal.TemporalField, int)}. + *

+ * Number/Text: If the count of pattern letters is 3 or greater, use the Text rules above. + * Otherwise use the Number rules above. + *

+ * Fraction: Outputs the nano-of-second field as a fraction-of-second. + * The nano-of-second value has nine digits, thus the count of pattern letters is from 1 to 9. + * If it is less than 9, then the nano-of-second value is truncated, with only the most + * significant digits being output. + * When parsing in strict mode, the number of parsed digits must match the count of pattern letters. + * When parsing in lenient mode, the number of parsed digits must be at least the count of pattern + * letters, up to 9 digits. + *

+ * Year: The count of letters determines the minimum field width below which padding is used. + * If the count of letters is two, then a {@link #appendValueReduced reduced} two digit form is used. + * For printing, this outputs the rightmost two digits. For parsing, this will parse using the + * base value of 2000, resulting in a year within the range 2000 to 2099 inclusive. + * If the count of letters is less than four (but not two), then the sign is only output for negative + * years as per {@link SignStyle#NORMAL}. + * Otherwise, the sign is output if the pad width is exceeded, as per {@link SignStyle#EXCEEDS_PAD} + *

+ * ZoneId: 'I' outputs the zone ID, such as 'Europe/Paris'. + *

+ * Offset X: This formats the offset using 'Z' when the offset is zero. + * One letter outputs just the hour', such as '+01' + * Two letters outputs the hour and minute, without a colon, such as '+0130'. + * Three letters outputs the hour and minute, with a colon, such as '+01:30'. + * Four letters outputs the hour and minute and optional second, without a colon, such as '+013015'. + * Five letters outputs the hour and minute and optional second, with a colon, such as '+01:30:15'. + *

+ * Offset Z: This formats the offset using '+0000' or '+00:00' when the offset is zero. + * One or two letters outputs the hour and minute, without a colon, such as '+0130'. + * Three letters outputs the hour and minute, with a colon, such as '+01:30'. + *

+ * Zone names: Time zone names ('z') cannot be parsed. + *

+ * Optional section: The optional section markers work exactly like calling {@link #optionalStart()} + * and {@link #optionalEnd()}. + *

+ * Pad modifier: Modifies the pattern that immediately follows to be padded with spaces. + * The pad width is determined by the number of pattern letters. + * This is the same as calling {@link #padNext(int)}. + *

+ * For example, 'ppH' outputs the hour-of-day padded on the left with spaces to a width of 2. + *

+ * Any unrecognized letter is an error. + * Any non-letter character, other than '[', ']', '{', '}' and the single quote will be output directly. + * Despite this, it is recommended to use single quotes around all characters that you want to + * output directly to ensure that future changes do not break your application. + *

+ * The pattern string is similar, but not identical, to {@link java.text.SimpleDateFormat SimpleDateFormat}. + * Pattern letters 'E' and 'u' are merged, which changes the meaning of "E" and "EE" to be numeric. + * Pattern letters 'Z' and 'X' are extended. + * Pattern letter 'y' and 'Y' parse years of two digits and more than 4 digits differently. + * Pattern letters 'n', 'A', 'N', 'I' and 'p' are added. + * Number types will reject large numbers. + * The pattern string is also similar, but not identical, to that defined by the + * Unicode Common Locale Data Repository (CLDR). + * + * @param pattern the pattern to add, not null + * @return this, for chaining, not null + * @throws IllegalArgumentException if the pattern is invalid + */ + public DateTimeFormatterBuilder appendPattern(String pattern) { + Objects.requireNonNull(pattern, "pattern"); + parsePattern(pattern); + return this; + } + + private void parsePattern(String pattern) { + for (int pos = 0; pos < pattern.length(); pos++) { + char cur = pattern.charAt(pos); + if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) { + int start = pos++; + for ( ; pos < pattern.length() && pattern.charAt(pos) == cur; pos++); // short loop + int count = pos - start; + // padding + if (cur == 'p') { + int pad = 0; + if (pos < pattern.length()) { + cur = pattern.charAt(pos); + if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) { + pad = count; + start = pos++; + for ( ; pos < pattern.length() && pattern.charAt(pos) == cur; pos++); // short loop + count = pos - start; + } + } + if (pad == 0) { + throw new IllegalArgumentException( + "Pad letter 'p' must be followed by valid pad pattern: " + pattern); + } + padNext(pad); // pad and continue parsing + } + // main rules + TemporalField field = FIELD_MAP.get(cur); + if (field != null) { + parseField(cur, count, field); + } else if (cur == 'z') { + if (count < 4) { + appendZoneText(TextStyle.SHORT); + } else { + appendZoneText(TextStyle.FULL); + } + } else if (cur == 'I') { + appendZoneId(); + } else if (cur == 'Z') { + if (count > 3) { + throw new IllegalArgumentException("Too many pattern letters: " + cur); + } + if (count < 3) { + appendOffset("+HHMM", "+0000"); + } else { + appendOffset("+HH:MM", "+00:00"); + } + } else if (cur == 'X') { + if (count > 5) { + throw new IllegalArgumentException("Too many pattern letters: " + cur); + } + appendOffset(OffsetIdPrinterParser.PATTERNS[count - 1], "Z"); + } else if (cur == 'w' || cur == 'e') { + // Fields defined by Locale + if (count > 1) { + throw new IllegalArgumentException("Too many pattern letters: " + cur); + } + appendInternal(new WeekBasedFieldPrinterParser(cur, count)); + } else if (cur == 'W') { + // Fields defined by Locale + if (count > 2) { + throw new IllegalArgumentException("Too many pattern letters: " + cur); + } + appendInternal(new WeekBasedFieldPrinterParser(cur, count)); + } else { + throw new IllegalArgumentException("Unknown pattern letter: " + cur); + } + pos--; + + } else if (cur == '\'') { + // parse literals + int start = pos++; + for ( ; pos < pattern.length(); pos++) { + if (pattern.charAt(pos) == '\'') { + if (pos + 1 < pattern.length() && pattern.charAt(pos + 1) == '\'') { + pos++; + } else { + break; // end of literal + } + } + } + if (pos >= pattern.length()) { + throw new IllegalArgumentException("Pattern ends with an incomplete string literal: " + pattern); + } + String str = pattern.substring(start + 1, pos); + if (str.length() == 0) { + appendLiteral('\''); + } else { + appendLiteral(str.replace("''", "'")); + } + + } else if (cur == '[') { + optionalStart(); + + } else if (cur == ']') { + if (active.parent == null) { + throw new IllegalArgumentException("Pattern invalid as it contains ] without previous ["); + } + optionalEnd(); + + } else if (cur == '{' || cur == '}') { + throw new IllegalArgumentException("Pattern includes reserved character: '" + cur + "'"); + } else { + appendLiteral(cur); + } + } + } + + private void parseField(char cur, int count, TemporalField field) { + switch (cur) { + case 'y': + case 'Y': + if (count == 2) { + appendValueReduced(field, 2, 2000); + } else if (count < 4) { + appendValue(field, count, 19, SignStyle.NORMAL); + } else { + appendValue(field, count, 19, SignStyle.EXCEEDS_PAD); + } + break; + case 'G': + case 'M': + case 'Q': + case 'E': + switch (count) { + case 1: + appendValue(field); + break; + case 2: + appendValue(field, 2); + break; + case 3: + appendText(field, TextStyle.SHORT); + break; + case 4: + appendText(field, TextStyle.FULL); + break; + case 5: + appendText(field, TextStyle.NARROW); + break; + default: + throw new IllegalArgumentException("Too many pattern letters: " + cur); + } + break; + case 'a': + switch (count) { + case 1: + case 2: + case 3: + appendText(field, TextStyle.SHORT); + break; + case 4: + appendText(field, TextStyle.FULL); + break; + case 5: + appendText(field, TextStyle.NARROW); + break; + default: + throw new IllegalArgumentException("Too many pattern letters: " + cur); + } + break; + case 'S': + appendFraction(NANO_OF_SECOND, count, count, false); + break; + default: + if (count == 1) { + appendValue(field); + } else { + appendValue(field, count); + } + break; + } + } + + /** Map of letters to fields. */ + private static final Map FIELD_MAP = new HashMap<>(); + static { + FIELD_MAP.put('G', ChronoField.ERA); // Java, CLDR (different to both for 1/2 chars) + FIELD_MAP.put('y', ChronoField.YEAR); // CLDR + // FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA); // Java, CLDR // TODO redefine from above + // FIELD_MAP.put('u', ChronoField.YEAR); // CLDR // TODO + // FIELD_MAP.put('Y', ISODateTimeField.WEEK_BASED_YEAR); // Java7, CLDR (needs localized week number) // TODO + FIELD_MAP.put('Q', ISOFields.QUARTER_OF_YEAR); // CLDR (removed quarter from 310) + FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR); // Java, CLDR + // FIELD_MAP.put('w', WeekFields.weekOfYear()); // Java, CLDR (needs localized week number) + // FIELD_MAP.put('W', WeekFields.weekOfMonth()); // Java, CLDR (needs localized week number) + FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR); // Java, CLDR + FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH); // Java, CLDR + FIELD_MAP.put('F', ChronoField.ALIGNED_WEEK_OF_MONTH); // Java, CLDR + FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK); // Java, CLDR (different to both for 1/2 chars) + // FIELD_MAP.put('e', WeekFields.dayOfWeek()); // CLDR (needs localized week number) + FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY); // Java, CLDR + FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY); // Java, CLDR + FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY); // Java, CLDR + FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM); // Java, CLDR + FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM); // Java, CLDR + FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR); // Java, CLDR + FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE); // Java, CLDR + FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND); // CLDR (Java uses milli-of-second number) + FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY); // CLDR + FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND); // 310 + FIELD_MAP.put('N', ChronoField.NANO_OF_DAY); // 310 + // reserved - z,Z,X,I,p + // Java - X - compatible, but extended to 4 and 5 letters + // Java - u - clashes with CLDR, go with CLDR (year-proleptic) here + // CLDR - U - cycle year name, not supported by 310 yet + // CLDR - l - deprecated + // CLDR - j - not relevant + // CLDR - g - modified-julian-day + // CLDR - z - time-zone names // TODO properly + // CLDR - Z - different approach here // TODO bring 310 in line with CLDR + // CLDR - v,V - extended time-zone names + // CLDR - q/c/L - standalone quarter/day-of-week/month + // 310 - I - time-zone id + // 310 - p - prefix for padding + } + + //----------------------------------------------------------------------- + /** + * Causes the next added printer/parser to pad to a fixed width using a space. + *

+ * This padding will pad to a fixed width using spaces. + *

+ * An exception will be thrown during printing if the pad width + * is exceeded. + * + * @param padWidth the pad width, 1 or greater + * @return this, for chaining, not null + * @throws IllegalArgumentException if pad width is too small + */ + public DateTimeFormatterBuilder padNext(int padWidth) { + return padNext(padWidth, ' '); + } + + /** + * Causes the next added printer/parser to pad to a fixed width. + *

+ * This padding is intended for padding other than zero-padding. + * Zero-padding should be achieved using the appendValue methods. + *

+ * An exception will be thrown during printing if the pad width + * is exceeded. + * + * @param padWidth the pad width, 1 or greater + * @param padChar the pad character + * @return this, for chaining, not null + * @throws IllegalArgumentException if pad width is too small + */ + public DateTimeFormatterBuilder padNext(int padWidth, char padChar) { + if (padWidth < 1) { + throw new IllegalArgumentException("The pad width must be at least one but was " + padWidth); + } + active.padNextWidth = padWidth; + active.padNextChar = padChar; + active.valueParserIndex = -1; + return this; + } + + //----------------------------------------------------------------------- + /** + * Mark the start of an optional section. + *

+ * The output of printing can include optional sections, which may be nested. + * An optional section is started by calling this method and ended by calling + * {@link #optionalEnd()} or by ending the build process. + *

+ * All elements in the optional section are treated as optional. + * During printing, the section is only output if data is available in the + * {@code TemporalAccessor} for all the elements in the section. + * During parsing, the whole section may be missing from the parsed string. + *

+ * For example, consider a builder setup as + * {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2)}. + * The optional section ends automatically at the end of the builder. + * During printing, the minute will only be output if its value can be obtained from the date-time. + * During parsing, the input will be successfully parsed whether the minute is present or not. + * + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder optionalStart() { + active.valueParserIndex = -1; + active = new DateTimeFormatterBuilder(active, true); + return this; + } + + /** + * Ends an optional section. + *

+ * The output of printing can include optional sections, which may be nested. + * An optional section is started by calling {@link #optionalStart()} and ended + * using this method (or at the end of the builder). + *

+ * Calling this method without having previously called {@code optionalStart} + * will throw an exception. + * Calling this method immediately after calling {@code optionalStart} has no effect + * on the formatter other than ending the (empty) optional section. + *

+ * All elements in the optional section are treated as optional. + * During printing, the section is only output if data is available in the + * {@code TemporalAccessor} for all the elements in the section. + * During parsing, the whole section may be missing from the parsed string. + *

+ * For example, consider a builder setup as + * {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2).optionalEnd()}. + * During printing, the minute will only be output if its value can be obtained from the date-time. + * During parsing, the input will be successfully parsed whether the minute is present or not. + * + * @return this, for chaining, not null + * @throws IllegalStateException if there was no previous call to {@code optionalStart} + */ + public DateTimeFormatterBuilder optionalEnd() { + if (active.parent == null) { + throw new IllegalStateException("Cannot call optionalEnd() as there was no previous call to optionalStart()"); + } + if (active.printerParsers.size() > 0) { + CompositePrinterParser cpp = new CompositePrinterParser(active.printerParsers, active.optional); + active = active.parent; + appendInternal(cpp); + } else { + active = active.parent; + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Appends a printer and/or parser to the internal list handling padding. + * + * @param pp the printer-parser to add, not null + * @return the index into the active parsers list + */ + private int appendInternal(DateTimePrinterParser pp) { + Objects.requireNonNull(pp, "pp"); + if (active.padNextWidth > 0) { + if (pp != null) { + pp = new PadPrinterParserDecorator(pp, active.padNextWidth, active.padNextChar); + } + active.padNextWidth = 0; + active.padNextChar = 0; + } + active.printerParsers.add(pp); + active.valueParserIndex = -1; + return active.printerParsers.size() - 1; + } + + //----------------------------------------------------------------------- + /** + * Completes this builder by creating the DateTimeFormatter using the default locale. + *

+ * This will create a formatter with the {@link Locale#getDefault(Locale.Category) default FORMAT locale}. + * Numbers will be printed and parsed using the standard non-localized set of symbols. + *

+ * Calling this method will end any open optional sections by repeatedly + * calling {@link #optionalEnd()} before creating the formatter. + *

+ * This builder can still be used after creating the formatter if desired, + * although the state may have been changed by calls to {@code optionalEnd}. + * + * @return the created formatter, not null + */ + public DateTimeFormatter toFormatter() { + return toFormatter(Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Completes this builder by creating the DateTimeFormatter using the specified locale. + *

+ * This will create a formatter with the specified locale. + * Numbers will be printed and parsed using the standard non-localized set of symbols. + *

+ * Calling this method will end any open optional sections by repeatedly + * calling {@link #optionalEnd()} before creating the formatter. + *

+ * This builder can still be used after creating the formatter if desired, + * although the state may have been changed by calls to {@code optionalEnd}. + * + * @param locale the locale to use for formatting, not null + * @return the created formatter, not null + */ + public DateTimeFormatter toFormatter(Locale locale) { + Objects.requireNonNull(locale, "locale"); + while (active.parent != null) { + optionalEnd(); + } + CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false); + return new DateTimeFormatter(pp, locale, DateTimeFormatSymbols.STANDARD, null, null); + } + + //----------------------------------------------------------------------- + /** + * Strategy for printing/parsing date-time information. + *

+ * The printer may print any part, or the whole, of the input date-time object. + * Typically, a complete print is constructed from a number of smaller + * units, each outputting a single field. + *

+ * The parser may parse any piece of text from the input, storing the result + * in the context. Typically, each individual parser will just parse one + * field, such as the day-of-month, storing the value in the context. + * Once the parse is complete, the caller will then convert the context + * to a {@link DateTimeBuilder} to merge the parsed values to create the + * desired object, such as a {@code LocalDate}. + *

+ * The parse position will be updated during the parse. Parsing will start at + * the specified index and the return value specifies the new parse position + * for the next parser. If an error occurs, the returned index will be negative + * and will have the error position encoded using the complement operator. + * + *

Specification for implementors

+ * This interface must be implemented with care to ensure other classes operate correctly. + * All implementations that can be instantiated must be final, immutable and thread-safe. + *

+ * The context is not a thread-safe object and a new instance will be created + * for each print that occurs. The context must not be stored in an instance + * variable or shared with any other threads. + */ + interface DateTimePrinterParser { + + /** + * Prints the date-time object to the buffer. + *

+ * The context holds information to use during the print. + * It also contains the date-time information to be printed. + *

+ * The buffer must not be mutated beyond the content controlled by the implementation. + * + * @param context the context to print using, not null + * @param buf the buffer to append to, not null + * @return false if unable to query the value from the date-time, true otherwise + * @throws DateTimeException if the date-time cannot be printed successfully + */ + boolean print(DateTimePrintContext context, StringBuilder buf); + + /** + * Parses text into date-time information. + *

+ * The context holds information to use during the parse. + * It is also used to store the parsed date-time information. + * + * @param context the context to use and parse into, not null + * @param text the input text to parse, not null + * @param position the position to start parsing at, from 0 to the text length + * @return the new parse position, where negative means an error with the + * error position encoded using the complement ~ operator + * @throws NullPointerException if the context or text is null + * @throws IndexOutOfBoundsException if the position is invalid + */ + int parse(DateTimeParseContext context, CharSequence text, int position); + } + + //----------------------------------------------------------------------- + /** + * Composite printer and parser. + */ + static final class CompositePrinterParser implements DateTimePrinterParser { + private final DateTimePrinterParser[] printerParsers; + private final boolean optional; + + CompositePrinterParser(List printerParsers, boolean optional) { + this(printerParsers.toArray(new DateTimePrinterParser[printerParsers.size()]), optional); + } + + CompositePrinterParser(DateTimePrinterParser[] printerParsers, boolean optional) { + this.printerParsers = printerParsers; + this.optional = optional; + } + + /** + * Returns a copy of this printer-parser with the optional flag changed. + * + * @param optional the optional flag to set in the copy + * @return the new printer-parser, not null + */ + public CompositePrinterParser withOptional(boolean optional) { + if (optional == this.optional) { + return this; + } + return new CompositePrinterParser(printerParsers, optional); + } + + @Override + public boolean print(DateTimePrintContext context, StringBuilder buf) { + int length = buf.length(); + if (optional) { + context.startOptional(); + } + try { + for (DateTimePrinterParser pp : printerParsers) { + if (pp.print(context, buf) == false) { + buf.setLength(length); // reset buffer + return true; + } + } + } finally { + if (optional) { + context.endOptional(); + } + } + return true; + } + + @Override + public int parse(DateTimeParseContext context, CharSequence text, int position) { + if (optional) { + context.startOptional(); + int pos = position; + for (DateTimePrinterParser pp : printerParsers) { + pos = pp.parse(context, text, pos); + if (pos < 0) { + context.endOptional(false); + return position; // return original position + } + } + context.endOptional(true); + return pos; + } else { + for (DateTimePrinterParser pp : printerParsers) { + position = pp.parse(context, text, position); + if (position < 0) { + break; + } + } + return position; + } + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + if (printerParsers != null) { + buf.append(optional ? "[" : "("); + for (DateTimePrinterParser pp : printerParsers) { + buf.append(pp); + } + buf.append(optional ? "]" : ")"); + } + return buf.toString(); + } + } + + //----------------------------------------------------------------------- + /** + * Pads the output to a fixed width. + */ + static final class PadPrinterParserDecorator implements DateTimePrinterParser { + private final DateTimePrinterParser printerParser; + private final int padWidth; + private final char padChar; + + /** + * Constructor. + * + * @param printerParser the printer, not null + * @param padWidth the width to pad to, 1 or greater + * @param padChar the pad character + */ + PadPrinterParserDecorator(DateTimePrinterParser printerParser, int padWidth, char padChar) { + // input checked by DateTimeFormatterBuilder + this.printerParser = printerParser; + this.padWidth = padWidth; + this.padChar = padChar; + } + + @Override + public boolean print(DateTimePrintContext context, StringBuilder buf) { + int preLen = buf.length(); + if (printerParser.print(context, buf) == false) { + return false; + } + int len = buf.length() - preLen; + if (len > padWidth) { + throw new DateTimePrintException( + "Cannot print as output of " + len + " characters exceeds pad width of " + padWidth); + } + for (int i = 0; i < padWidth - len; i++) { + buf.insert(preLen, padChar); + } + return true; + } + + @Override + public int parse(DateTimeParseContext context, CharSequence text, int position) { + if (position > text.length()) { + throw new IndexOutOfBoundsException(); + } + int endPos = position + padWidth; + if (endPos > text.length()) { + return ~position; // not enough characters in the string to meet the parse width + } + int pos = position; + while (pos < endPos && text.charAt(pos) == padChar) { + pos++; + } + text = text.subSequence(0, endPos); + int firstError = 0; + while (pos >= position) { + int resultPos = printerParser.parse(context, text, pos); + if (resultPos < 0) { + // parse of decorated field had an error + if (firstError == 0) { + firstError = resultPos; + } + // loop around in case the decorated parser can handle the padChar at the start + pos--; + continue; + } + if (resultPos != endPos) { + return ~position; // parse of decorated field didn't parse to the end + } + return resultPos; + } + // loop runs at least once, so firstError must be set by the time we get here + return firstError; // return error from first parse of decorated field + } + + @Override + public String toString() { + return "Pad(" + printerParser + "," + padWidth + (padChar == ' ' ? ")" : ",'" + padChar + "')"); + } + } + + //----------------------------------------------------------------------- + /** + * Enumeration to apply simple parse settings. + */ + static enum SettingsParser implements DateTimePrinterParser { + SENSITIVE, + INSENSITIVE, + STRICT, + LENIENT; + + @Override + public boolean print(DateTimePrintContext context, StringBuilder buf) { + return true; // nothing to do here + } + + @Override + public int parse(DateTimeParseContext context, CharSequence text, int position) { + // using ordinals to avoid javac synthetic inner class + switch (ordinal()) { + case 0: context.setCaseSensitive(true); break; + case 1: context.setCaseSensitive(false); break; + case 2: context.setStrict(true); break; + case 3: context.setStrict(false); break; + } + return position; + } + + @Override + public String toString() { + // using ordinals to avoid javac synthetic inner class + switch (ordinal()) { + case 0: return "ParseCaseSensitive(true)"; + case 1: return "ParseCaseSensitive(false)"; + case 2: return "ParseStrict(true)"; + case 3: return "ParseStrict(false)"; + } + throw new IllegalStateException("Unreachable"); + } + } + + //----------------------------------------------------------------------- + /** + * Prints or parses a character literal. + */ + static final class CharLiteralPrinterParser implements DateTimePrinterParser { + private final char literal; + + CharLiteralPrinterParser(char literal) { + this.literal = literal; + } + + @Override + public boolean print(DateTimePrintContext context, StringBuilder buf) { + buf.append(literal); + return true; + } + + @Override + public int parse(DateTimeParseContext context, CharSequence text, int position) { + int length = text.length(); + if (position == length) { + return ~position; + } + char ch = text.charAt(position); + if (ch != literal) { + if (context.isCaseSensitive() || + (Character.toUpperCase(ch) != Character.toUpperCase(literal) && + Character.toLowerCase(ch) != Character.toLowerCase(literal))) { + return ~position; + } + } + return position + 1; + } + + @Override + public String toString() { + if (literal == '\'') { + return "''"; + } + return "'" + literal + "'"; + } + } + + //----------------------------------------------------------------------- + /** + * Prints or parses a string literal. + */ + static final class StringLiteralPrinterParser implements DateTimePrinterParser { + private final String literal; + + StringLiteralPrinterParser(String literal) { + this.literal = literal; // validated by caller + } + + @Override + public boolean print(DateTimePrintContext context, StringBuilder buf) { + buf.append(literal); + return true; + } + + @Override + public int parse(DateTimeParseContext context, CharSequence text, int position) { + int length = text.length(); + if (position > length || position < 0) { + throw new IndexOutOfBoundsException(); + } + if (context.subSequenceEquals(text, position, literal, 0, literal.length()) == false) { + return ~position; + } + return position + literal.length(); + } + + @Override + public String toString() { + String converted = literal.replace("'", "''"); + return "'" + converted + "'"; + } + } + + //----------------------------------------------------------------------- + /** + * Prints and parses a numeric date-time field with optional padding. + */ + static class NumberPrinterParser implements DateTimePrinterParser { + + /** + * Array of 10 to the power of n. + */ + static final int[] EXCEED_POINTS = new int[] { + 0, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + }; + + final TemporalField field; + final int minWidth; + private final int maxWidth; + private final SignStyle signStyle; + private final int subsequentWidth; + + /** + * Constructor. + * + * @param field the field to print, not null + * @param minWidth the minimum field width, from 1 to 19 + * @param maxWidth the maximum field width, from minWidth to 19 + * @param signStyle the positive/negative sign style, not null + */ + NumberPrinterParser(TemporalField field, int minWidth, int maxWidth, SignStyle signStyle) { + // validated by caller + this.field = field; + this.minWidth = minWidth; + this.maxWidth = maxWidth; + this.signStyle = signStyle; + this.subsequentWidth = 0; + } + + /** + * Constructor. + * + * @param field the field to print, not null + * @param minWidth the minimum field width, from 1 to 19 + * @param maxWidth the maximum field width, from minWidth to 19 + * @param signStyle the positive/negative sign style, not null + * @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater, + * -1 if fixed width due to active adjacent parsing + */ + private NumberPrinterParser(TemporalField field, int minWidth, int maxWidth, SignStyle signStyle, int subsequentWidth) { + // validated by caller + this.field = field; + this.minWidth = minWidth; + this.maxWidth = maxWidth; + this.signStyle = signStyle; + this.subsequentWidth = subsequentWidth; + } + + /** + * Returns a new instance with fixed width flag set. + * + * @return a new updated printer-parser, not null + */ + NumberPrinterParser withFixedWidth() { + return new NumberPrinterParser(field, minWidth, maxWidth, signStyle, -1); + } + + /** + * Returns a new instance with an updated subsequent width. + * + * @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater + * @return a new updated printer-parser, not null + */ + NumberPrinterParser withSubsequentWidth(int subsequentWidth) { + return new NumberPrinterParser(field, minWidth, maxWidth, signStyle, this.subsequentWidth + subsequentWidth); + } + + @Override + public boolean print(DateTimePrintContext context, StringBuilder buf) { + Long valueLong = context.getValue(field); + if (valueLong == null) { + return false; + } + long value = getValue(valueLong); + DateTimeFormatSymbols symbols = context.getSymbols(); + String str = (value == Long.MIN_VALUE ? "9223372036854775808" : Long.toString(Math.abs(value))); + if (str.length() > maxWidth) { + throw new DateTimePrintException("Field " + field.getName() + + " cannot be printed as the value " + value + + " exceeds the maximum print width of " + maxWidth); + } + str = symbols.convertNumberToI18N(str); + + if (value >= 0) { + switch (signStyle) { + case EXCEEDS_PAD: + if (minWidth < 19 && value >= EXCEED_POINTS[minWidth]) { + buf.append(symbols.getPositiveSign()); + } + break; + case ALWAYS: + buf.append(symbols.getPositiveSign()); + break; + } + } else { + switch (signStyle) { + case NORMAL: + case EXCEEDS_PAD: + case ALWAYS: + buf.append(symbols.getNegativeSign()); + break; + case NOT_NEGATIVE: + throw new DateTimePrintException("Field " + field.getName() + + " cannot be printed as the value " + value + + " cannot be negative according to the SignStyle"); + } + } + for (int i = 0; i < minWidth - str.length(); i++) { + buf.append(symbols.getZeroDigit()); + } + buf.append(str); + return true; + } + + /** + * Gets the value to output. + * + * @param value the base value of the field, not null + * @return the value + */ + long getValue(long value) { + return value; + } + + boolean isFixedWidth() { + return subsequentWidth == -1; + } + + @Override + public int parse(DateTimeParseContext context, CharSequence text, int position) { + int length = text.length(); + if (position == length) { + return ~position; + } + char sign = text.charAt(position); // IOOBE if invalid position + boolean negative = false; + boolean positive = false; + if (sign == context.getSymbols().getPositiveSign()) { + if (signStyle.parse(true, context.isStrict(), minWidth == maxWidth) == false) { + return ~position; + } + positive = true; + position++; + } else if (sign == context.getSymbols().getNegativeSign()) { + if (signStyle.parse(false, context.isStrict(), minWidth == maxWidth) == false) { + return ~position; + } + negative = true; + position++; + } else { + if (signStyle == SignStyle.ALWAYS && context.isStrict()) { + return ~position; + } + } + int effMinWidth = (context.isStrict() || isFixedWidth() ? minWidth : 1); + int minEndPos = position + effMinWidth; + if (minEndPos > length) { + return ~position; + } + int effMaxWidth = maxWidth + Math.max(subsequentWidth, 0); + long total = 0; + BigInteger totalBig = null; + int pos = position; + for (int pass = 0; pass < 2; pass++) { + int maxEndPos = Math.min(pos + effMaxWidth, length); + while (pos < maxEndPos) { + char ch = text.charAt(pos++); + int digit = context.getSymbols().convertToDigit(ch); + if (digit < 0) { + pos--; + if (pos < minEndPos) { + return ~position; // need at least min width digits + } + break; + } + if ((pos - position) > 18) { + if (totalBig == null) { + totalBig = BigInteger.valueOf(total); + } + totalBig = totalBig.multiply(BigInteger.TEN).add(BigInteger.valueOf(digit)); + } else { + total = total * 10 + digit; + } + } + if (subsequentWidth > 0 && pass == 0) { + // re-parse now we know the correct width + int parseLen = pos - position; + effMaxWidth = Math.max(effMinWidth, parseLen - subsequentWidth); + pos = position; + total = 0; + totalBig = null; + } else { + break; + } + } + if (negative) { + if (totalBig != null) { + if (totalBig.equals(BigInteger.ZERO) && context.isStrict()) { + return ~(position - 1); // minus zero not allowed + } + totalBig = totalBig.negate(); + } else { + if (total == 0 && context.isStrict()) { + return ~(position - 1); // minus zero not allowed + } + total = -total; + } + } else if (signStyle == SignStyle.EXCEEDS_PAD && context.isStrict()) { + int parseLen = pos - position; + if (positive) { + if (parseLen <= minWidth) { + return ~(position - 1); // '+' only parsed if minWidth exceeded + } + } else { + if (parseLen > minWidth) { + return ~position; // '+' must be parsed if minWidth exceeded + } + } + } + if (totalBig != null) { + if (totalBig.bitLength() > 63) { + // overflow, parse 1 less digit + totalBig = totalBig.divide(BigInteger.TEN); + pos--; + } + setValue(context, totalBig.longValue()); + } else { + setValue(context, total); + } + return pos; + } + + /** + * Stores the value. + * + * @param context the context to store into, not null + * @param value the value + */ + void setValue(DateTimeParseContext context, long value) { + context.setParsedField(field, value); + } + + @Override + public String toString() { + if (minWidth == 1 && maxWidth == 19 && signStyle == SignStyle.NORMAL) { + return "Value(" + field.getName() + ")"; + } + if (minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE) { + return "Value(" + field.getName() + "," + minWidth + ")"; + } + return "Value(" + field.getName() + "," + minWidth + "," + maxWidth + "," + signStyle + ")"; + } + } + + //----------------------------------------------------------------------- + /** + * Prints and parses a reduced numeric date-time field. + */ + static final class ReducedPrinterParser extends NumberPrinterParser { + private final int baseValue; + private final int range; + + /** + * Constructor. + * + * @param field the field to print, validated not null + * @param width the field width, from 1 to 18 + * @param baseValue the base value + */ + ReducedPrinterParser(TemporalField field, int width, int baseValue) { + super(field, width, width, SignStyle.NOT_NEGATIVE); + if (width < 1 || width > 18) { + throw new IllegalArgumentException("The width must be from 1 to 18 inclusive but was " + width); + } + if (field.range().isValidValue(baseValue) == false) { + throw new IllegalArgumentException("The base value must be within the range of the field"); + } + this.baseValue = baseValue; + this.range = EXCEED_POINTS[width]; + if ((((long) baseValue) + range) > Integer.MAX_VALUE) { + throw new DateTimeException("Unable to add printer-parser as the range exceeds the capacity of an int"); + } + } + + @Override + long getValue(long value) { + return Math.abs(value % range); + } + + @Override + void setValue(DateTimeParseContext context, long value) { + int lastPart = baseValue % range; + if (baseValue > 0) { + value = baseValue - lastPart + value; + } else { + value = baseValue - lastPart - value; + } + if (value < baseValue) { + value += range; + } + context.setParsedField(field, value); + } + + @Override + NumberPrinterParser withFixedWidth() { + return this; + } + + @Override + boolean isFixedWidth() { + return true; + } + + @Override + public String toString() { + return "ReducedValue(" + field.getName() + "," + minWidth + "," + baseValue + ")"; + } + } + + //----------------------------------------------------------------------- + /** + * Prints and parses a numeric date-time field with optional padding. + */ + static final class FractionPrinterParser implements DateTimePrinterParser { + private final TemporalField field; + private final int minWidth; + private final int maxWidth; + private final boolean decimalPoint; + + /** + * Constructor. + * + * @param field the field to output, not null + * @param minWidth the minimum width to output, from 0 to 9 + * @param maxWidth the maximum width to output, from 0 to 9 + * @param decimalPoint whether to output the localized decimal point symbol + */ + FractionPrinterParser(TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) { + Objects.requireNonNull(field, "field"); + if (field.range().isFixed() == false) { + throw new IllegalArgumentException("Field must have a fixed set of values: " + field.getName()); + } + if (minWidth < 0 || minWidth > 9) { + throw new IllegalArgumentException("Minimum width must be from 0 to 9 inclusive but was " + minWidth); + } + if (maxWidth < 1 || maxWidth > 9) { + throw new IllegalArgumentException("Maximum width must be from 1 to 9 inclusive but was " + maxWidth); + } + if (maxWidth < minWidth) { + throw new IllegalArgumentException("Maximum width must exceed or equal the minimum width but " + + maxWidth + " < " + minWidth); + } + this.field = field; + this.minWidth = minWidth; + this.maxWidth = maxWidth; + this.decimalPoint = decimalPoint; + } + + @Override + public boolean print(DateTimePrintContext context, StringBuilder buf) { + Long value = context.getValue(field); + if (value == null) { + return false; + } + DateTimeFormatSymbols symbols = context.getSymbols(); + BigDecimal fraction = convertToFraction(value); + if (fraction.scale() == 0) { // scale is zero if value is zero + if (minWidth > 0) { + if (decimalPoint) { + buf.append(symbols.getDecimalSeparator()); + } + for (int i = 0; i < minWidth; i++) { + buf.append(symbols.getZeroDigit()); + } + } + } else { + int outputScale = Math.min(Math.max(fraction.scale(), minWidth), maxWidth); + fraction = fraction.setScale(outputScale, RoundingMode.FLOOR); + String str = fraction.toPlainString().substring(2); + str = symbols.convertNumberToI18N(str); + if (decimalPoint) { + buf.append(symbols.getDecimalSeparator()); + } + buf.append(str); + } + return true; + } + + @Override + public int parse(DateTimeParseContext context, CharSequence text, int position) { + int effectiveMin = (context.isStrict() ? minWidth : 0); + int effectiveMax = (context.isStrict() ? maxWidth : 9); + int length = text.length(); + if (position == length) { + // valid if whole field is optional, invalid if minimum width + return (effectiveMin > 0 ? ~position : position); + } + if (decimalPoint) { + if (text.charAt(position) != context.getSymbols().getDecimalSeparator()) { + // valid if whole field is optional, invalid if minimum width + return (effectiveMin > 0 ? ~position : position); + } + position++; + } + int minEndPos = position + effectiveMin; + if (minEndPos > length) { + return ~position; // need at least min width digits + } + int maxEndPos = Math.min(position + effectiveMax, length); + int total = 0; // can use int because we are only parsing up to 9 digits + int pos = position; + while (pos < maxEndPos) { + char ch = text.charAt(pos++); + int digit = context.getSymbols().convertToDigit(ch); + if (digit < 0) { + if (pos < minEndPos) { + return ~position; // need at least min width digits + } + pos--; + break; + } + total = total * 10 + digit; + } + BigDecimal fraction = new BigDecimal(total).movePointLeft(pos - position); + long value = convertFromFraction(fraction); + context.setParsedField(field, value); + return pos; + } + + /** + * Converts a value for this field to a fraction between 0 and 1. + *

+ * The fractional value is between 0 (inclusive) and 1 (exclusive). + * It can only be returned if the {@link java.time.temporal.TemporalField#range() value range} is fixed. + * The fraction is obtained by calculation from the field range using 9 decimal + * places and a rounding mode of {@link RoundingMode#FLOOR FLOOR}. + * The calculation is inaccurate if the values do not run continuously from smallest to largest. + *

+ * For example, the second-of-minute value of 15 would be returned as 0.25, + * assuming the standard definition of 60 seconds in a minute. + * + * @param value the value to convert, must be valid for this rule + * @return the value as a fraction within the range, from 0 to 1, not null + * @throws DateTimeException if the value cannot be converted to a fraction + */ + private BigDecimal convertToFraction(long value) { + ValueRange range = field.range(); + range.checkValidValue(value, field); + BigDecimal minBD = BigDecimal.valueOf(range.getMinimum()); + BigDecimal rangeBD = BigDecimal.valueOf(range.getMaximum()).subtract(minBD).add(BigDecimal.ONE); + BigDecimal valueBD = BigDecimal.valueOf(value).subtract(minBD); + BigDecimal fraction = valueBD.divide(rangeBD, 9, RoundingMode.FLOOR); + // stripTrailingZeros bug + return fraction.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : fraction.stripTrailingZeros(); + } + + /** + * Converts a fraction from 0 to 1 for this field to a value. + *

+ * The fractional value must be between 0 (inclusive) and 1 (exclusive). + * It can only be returned if the {@link java.time.temporal.TemporalField#range() value range} is fixed. + * The value is obtained by calculation from the field range and a rounding + * mode of {@link RoundingMode#FLOOR FLOOR}. + * The calculation is inaccurate if the values do not run continuously from smallest to largest. + *

+ * For example, the fractional second-of-minute of 0.25 would be converted to 15, + * assuming the standard definition of 60 seconds in a minute. + * + * @param fraction the fraction to convert, not null + * @return the value of the field, valid for this rule + * @throws DateTimeException if the value cannot be converted + */ + private long convertFromFraction(BigDecimal fraction) { + ValueRange range = field.range(); + BigDecimal minBD = BigDecimal.valueOf(range.getMinimum()); + BigDecimal rangeBD = BigDecimal.valueOf(range.getMaximum()).subtract(minBD).add(BigDecimal.ONE); + BigDecimal valueBD = fraction.multiply(rangeBD).setScale(0, RoundingMode.FLOOR).add(minBD); + return valueBD.longValueExact(); + } + + @Override + public String toString() { + String decimal = (decimalPoint ? ",DecimalPoint" : ""); + return "Fraction(" + field.getName() + "," + minWidth + "," + maxWidth + decimal + ")"; + } + } + + //----------------------------------------------------------------------- + /** + * Prints or parses field text. + */ + static final class TextPrinterParser implements DateTimePrinterParser { + private final TemporalField field; + private final TextStyle textStyle; + private final DateTimeTextProvider provider; + /** + * The cached number printer parser. + * Immutable and volatile, so no synchronization needed. + */ + private volatile NumberPrinterParser numberPrinterParser; + + /** + * Constructor. + * + * @param field the field to output, not null + * @param textStyle the text style, not null + * @param provider the text provider, not null + */ + TextPrinterParser(TemporalField field, TextStyle textStyle, DateTimeTextProvider provider) { + // validated by caller + this.field = field; + this.textStyle = textStyle; + this.provider = provider; + } + + @Override + public boolean print(DateTimePrintContext context, StringBuilder buf) { + Long value = context.getValue(field); + if (value == null) { + return false; + } + String text = null; + if (field == ChronoField.ERA) { + Chrono chrono = context.getTemporal().query(Queries.chrono()); + if (chrono == null) { + chrono = ISOChrono.INSTANCE; + } + text = provider.getEraText(chrono, value, textStyle, context.getLocale()); + } else { + text = provider.getText(field, value, textStyle, context.getLocale()); + } + if (text == null) { + return numberPrinterParser().print(context, buf); + } + buf.append(text); + return true; + } + + @Override + public int parse(DateTimeParseContext context, CharSequence parseText, int position) { + int length = parseText.length(); + if (position < 0 || position > length) { + throw new IndexOutOfBoundsException(); + } + TextStyle style = (context.isStrict() ? textStyle : null); + Iterator> it = provider.getTextIterator(field, style, context.getLocale()); + if (it != null) { + while (it.hasNext()) { + Entry entry = it.next(); + String itText = entry.getKey(); + if (context.subSequenceEquals(itText, 0, parseText, position, itText.length())) { + context.setParsedField(field, entry.getValue()); + return position + itText.length(); + } + } + if (context.isStrict()) { + return ~position; + } + } + return numberPrinterParser().parse(context, parseText, position); + } + + /** + * Create and cache a number printer parser. + * @return the number printer parser for this field, not null + */ + private NumberPrinterParser numberPrinterParser() { + if (numberPrinterParser == null) { + numberPrinterParser = new NumberPrinterParser(field, 1, 19, SignStyle.NORMAL); + } + return numberPrinterParser; + } + + @Override + public String toString() { + if (textStyle == TextStyle.FULL) { + return "Text(" + field.getName() + ")"; + } + return "Text(" + field.getName() + "," + textStyle + ")"; + } + } + + //----------------------------------------------------------------------- + /** + * Prints or parses an ISO-8601 instant. + */ + static final class InstantPrinterParser implements DateTimePrinterParser { + // days in a 400 year cycle = 146097 + // days in a 10,000 year cycle = 146097 * 25 + // seconds per day = 86400 + private static final long SECONDS_PER_10000_YEARS = 146097L * 25L * 86400L; + private static final long SECONDS_0000_TO_1970 = ((146097L * 5L) - (30L * 365L + 7L)) * 86400L; + private static final CompositePrinterParser PARSER = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(DateTimeFormatters.isoLocalDate()).appendLiteral('T') + .append(DateTimeFormatters.isoLocalTime()).appendLiteral('Z') + .toFormatter().toPrinterParser(false); + + InstantPrinterParser() { + } + + @Override + public boolean print(DateTimePrintContext context, StringBuilder buf) { + // use INSTANT_SECONDS, thus this code is not bound by Instant.MAX + Long inSecs = context.getValue(INSTANT_SECONDS); + Long inNanos = context.getValue(NANO_OF_SECOND); + if (inSecs == null || inNanos == null) { + return false; + } + long inSec = inSecs; + int inNano = NANO_OF_SECOND.checkValidIntValue(inNanos); + if (inSec >= -SECONDS_0000_TO_1970) { + // current era + long zeroSecs = inSec - SECONDS_PER_10000_YEARS + SECONDS_0000_TO_1970; + long hi = Math.floorDiv(zeroSecs, SECONDS_PER_10000_YEARS) + 1; + long lo = Math.floorMod(zeroSecs, SECONDS_PER_10000_YEARS); + LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, inNano, ZoneOffset.UTC); + if (hi > 0) { + buf.append('+').append(hi); + } + buf.append(ldt).append('Z'); + } else { + // before current era + long zeroSecs = inSec + SECONDS_0000_TO_1970; + long hi = zeroSecs / SECONDS_PER_10000_YEARS; + long lo = zeroSecs % SECONDS_PER_10000_YEARS; + LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, inNano, ZoneOffset.UTC); + int pos = buf.length(); + buf.append(ldt).append('Z'); + if (hi < 0) { + if (ldt.getYear() == -10_000) { + buf.replace(pos, pos + 2, Long.toString(hi - 1)); + } else if (lo == 0) { + buf.insert(pos, hi); + } else { + buf.insert(pos + 1, Math.abs(hi)); + } + } + } + return true; + } + + @Override + public int parse(DateTimeParseContext context, CharSequence text, int position) { + // new context to avoid overwriting fields like year/month/day + DateTimeParseContext newContext = context.copy(); + int pos = PARSER.parse(newContext, text, position); + if (pos < 0) { + return pos; + } + // parser restricts most fields to 2 digits, so definitely int + // correctly parsed nano is also guaranteed to be valid + long yearParsed = newContext.getParsed(YEAR); + int month = newContext.getParsed(MONTH_OF_YEAR).intValue(); + int day = newContext.getParsed(DAY_OF_MONTH).intValue(); + int hour = newContext.getParsed(HOUR_OF_DAY).intValue(); + int min = newContext.getParsed(MINUTE_OF_HOUR).intValue(); + Long secVal = newContext.getParsed(SECOND_OF_MINUTE); + Long nanoVal = newContext.getParsed(NANO_OF_SECOND); + int sec = (secVal != null ? secVal.intValue() : 0); + int nano = (nanoVal != null ? nanoVal.intValue() : 0); + int year = (int) yearParsed % 10_000; + long instantSecs; + try { + LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, min, sec, 0); + instantSecs = ldt.toEpochSecond(ZoneOffset.UTC); + instantSecs += Math.multiplyExact(yearParsed / 10_000L, SECONDS_PER_10000_YEARS); + } catch (RuntimeException ex) { + return ~position; + } + context.setParsedField(INSTANT_SECONDS, instantSecs); + context.setParsedField(NANO_OF_SECOND, nano); + return text.length(); + } + + @Override + public String toString() { + return "Instant()"; + } + } + + //----------------------------------------------------------------------- + /** + * Prints or parses an offset ID. + */ + static final class OffsetIdPrinterParser implements DateTimePrinterParser { + static final String[] PATTERNS = new String[] { + "+HH", "+HHMM", "+HH:MM", "+HHMMss", "+HH:MM:ss", "+HHMMSS", "+HH:MM:SS", + }; // order used in pattern builder + static final OffsetIdPrinterParser INSTANCE_ID = new OffsetIdPrinterParser("Z", "+HH:MM:ss"); + + private final String noOffsetText; + private final int type; + + /** + * Constructor. + * + * @param noOffsetText the text to use for UTC, not null + * @param pattern the pattern + */ + OffsetIdPrinterParser(String noOffsetText, String pattern) { + Objects.requireNonNull(noOffsetText, "noOffsetText"); + Objects.requireNonNull(pattern, "pattern"); + this.noOffsetText = noOffsetText; + this.type = checkPattern(pattern); + } + + private int checkPattern(String pattern) { + for (int i = 0; i < PATTERNS.length; i++) { + if (PATTERNS[i].equals(pattern)) { + return i; + } + } + throw new IllegalArgumentException("Invalid zone offset pattern: " + pattern); + } + + @Override + public boolean print(DateTimePrintContext context, StringBuilder buf) { + Long offsetSecs = context.getValue(OFFSET_SECONDS); + if (offsetSecs == null) { + return false; + } + int totalSecs = Math.toIntExact(offsetSecs); + if (totalSecs == 0) { + buf.append(noOffsetText); + } else { + int absHours = Math.abs((totalSecs / 3600) % 100); // anything larger than 99 silently dropped + int absMinutes = Math.abs((totalSecs / 60) % 60); + int absSeconds = Math.abs(totalSecs % 60); + buf.append(totalSecs < 0 ? "-" : "+") + .append((char) (absHours / 10 + '0')).append((char) (absHours % 10 + '0')); + if (type >= 1) { + buf.append((type % 2) == 0 ? ":" : "") + .append((char) (absMinutes / 10 + '0')).append((char) (absMinutes % 10 + '0')); + if (type >= 5 || (type >= 3 && absSeconds > 0)) { + buf.append((type % 2) == 0 ? ":" : "") + .append((char) (absSeconds / 10 + '0')).append((char) (absSeconds % 10 + '0')); + } + } + } + return true; + } + + @Override + public int parse(DateTimeParseContext context, CharSequence text, int position) { + int length = text.length(); + int noOffsetLen = noOffsetText.length(); + if (noOffsetLen == 0) { + if (position == length) { + context.setParsedField(OFFSET_SECONDS, 0); + return position; + } + } else { + if (position == length) { + return ~position; + } + if (context.subSequenceEquals(text, position, noOffsetText, 0, noOffsetLen)) { + context.setParsedField(OFFSET_SECONDS, 0); + return position + noOffsetLen; + } + } + + // parse normal plus/minus offset + char sign = text.charAt(position); // IOOBE if invalid position + if (sign == '+' || sign == '-') { + // starts + int negative = (sign == '-' ? -1 : 1); + int[] array = new int[4]; + array[0] = position + 1; + if (parseNumber(array, 1, text, true) || + parseNumber(array, 2, text, type > 0) || + parseNumber(array, 3, text, false)) { + return ~position; + } + long offsetSecs = negative * (array[1] * 3600L + array[2] * 60L + array[3]); + context.setParsedField(OFFSET_SECONDS, offsetSecs); + return array[0]; + } else { + // handle special case of empty no offset text + if (noOffsetLen == 0) { + context.setParsedField(OFFSET_SECONDS, 0); + return position + noOffsetLen; + } + return ~position; + } + } + + /** + * Parse a two digit zero-prefixed number. + * + * @param array the array of parsed data, 0=pos,1=hours,2=mins,3=secs, not null + * @param arrayIndex the index to parse the value into + * @param parseText the offset ID, not null + * @param required whether this number is required + * @return true if an error occurred + */ + private boolean parseNumber(int[] array, int arrayIndex, CharSequence parseText, boolean required) { + if ((type + 3) / 2 < arrayIndex) { + return false; // ignore seconds/minutes + } + int pos = array[0]; + if ((type % 2) == 0 && arrayIndex > 1) { + if (pos + 1 > parseText.length() || parseText.charAt(pos) != ':') { + return required; + } + pos++; + } + if (pos + 2 > parseText.length()) { + return required; + } + char ch1 = parseText.charAt(pos++); + char ch2 = parseText.charAt(pos++); + if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') { + return required; + } + int value = (ch1 - 48) * 10 + (ch2 - 48); + if (value < 0 || value > 59) { + return required; + } + array[arrayIndex] = value; + array[0] = pos; + return false; + } + + @Override + public String toString() { + String converted = noOffsetText.replace("'", "''"); + return "Offset('" + converted + "'," + PATTERNS[type] + ")"; + } + } + + //----------------------------------------------------------------------- + /** + * Prints or parses a zone ID. + */ + static final class ZoneTextPrinterParser implements DateTimePrinterParser { + + /** The text style to output. */ + private final TextStyle textStyle; + + ZoneTextPrinterParser(TextStyle textStyle) { + this.textStyle = Objects.requireNonNull(textStyle, "textStyle"); + } + + private static final int STD = 0; + private static final int DST = 1; + private static final int GENERIC = 2; + + private static final Map>> cache = + new ConcurrentHashMap<>(); + + private static String getDisplayName(String id, int type, TextStyle style, Locale locale) { + if (style == TextStyle.NARROW) { + return null; + } + String[] names; + SoftReference> ref = cache.get(id); + Map perLocale; + if (ref == null || (perLocale = ref.get()) == null || + (names = perLocale.get(locale)) == null) { + names = TimeZoneNameUtility.retrieveDisplayNames(id, locale); + if (names == null) { + return null; + } + names = Arrays.copyOfRange(names, 0, 7); + names[5] = + TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.LONG,locale); + if (names[5] == null) { + names[5] = names[0]; // use the id + } + names[6] = + TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.SHORT,locale); + if (names[6] == null) { + names[6] = names[0]; + } + perLocale = new ConcurrentHashMap<>(); + perLocale.put(locale, names); + ref = new SoftReference<>(perLocale); + cache.put(id, ref); + } + switch (type) { + case STD: + return names[style.ordinal() + 1]; + case DST: + return names[style.ordinal() + 3]; + } + return names[style.ordinal() + 5]; + } + + @Override + public boolean print(DateTimePrintContext context, StringBuilder buf) { + ZoneId zone = context.getValue(Queries.zoneId()); + if (zone == null) { + return false; + } + if (zone instanceof ZoneOffset) { + buf.append(zone.getId()); + } else { + TemporalAccessor dt = context.getTemporal(); + Instant instant = null; + if (dt.isSupported(ChronoField.INSTANT_SECONDS)) { + instant = Instant.from(dt); + } + String name = getDisplayName(zone.getId(), + instant == null ? GENERIC + : (zone.getRules().isDaylightSavings(instant) ? DST : STD), + textStyle, context.getLocale()); + if (name != null) { + buf.append(name); + } else { + buf.append(zone.getId()); + } + } + return true; + } + + @Override + public int parse(DateTimeParseContext context, CharSequence text, int position) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return "ZoneText(" + textStyle + ")"; + } + } + + //----------------------------------------------------------------------- + /** + * Prints or parses a zone ID. + */ + static final class ZoneIdPrinterParser implements DateTimePrinterParser { + private final TemporalQuery query; + private final String description; + + ZoneIdPrinterParser(TemporalQuery query, String description) { + this.query = query; + this.description = description; + } + + //----------------------------------------------------------------------- + @Override + public boolean print(DateTimePrintContext context, StringBuilder buf) { + ZoneId zone = context.getValue(query); + if (zone == null) { + return false; + } + buf.append(zone.getId()); + return true; + } + + //----------------------------------------------------------------------- + /** + * The cached tree to speed up parsing. + */ + private static volatile Entry cachedPrefixTree; + private static volatile Entry cachedPrefixTreeCI; + + /** + * This implementation looks for the longest matching string. + * For example, parsing Etc/GMT-2 will return Etc/GMC-2 rather than just + * Etc/GMC although both are valid. + */ + @Override + public int parse(DateTimeParseContext context, CharSequence text, int position) { + int length = text.length(); + if (position > length) { + throw new IndexOutOfBoundsException(); + } + + // handle fixed time-zone IDs + if ((text.length() - position) >= 1) { + char nextChar = text.charAt(position); + if (nextChar == '+' || nextChar == '-') { + DateTimeParseContext newContext = context.copy(); + int endPos = OffsetIdPrinterParser.INSTANCE_ID.parse(newContext, text, position); + if (endPos < 0) { + return endPos; + } + int offset = (int) (long) newContext.getParsed(OFFSET_SECONDS); + ZoneId zone = ZoneOffset.ofTotalSeconds(offset); + context.setParsed(zone); + return endPos; + } + } + + // prepare parse tree + Set regionIds = ZoneRulesProvider.getAvailableZoneIds(); + final int regionIdsSize = regionIds.size(); + Entry cached = context.isCaseSensitive() + ? cachedPrefixTree : cachedPrefixTreeCI; + if (cached == null || cached.getKey() != regionIdsSize) { + synchronized (this) { + cached = context.isCaseSensitive() ? cachedPrefixTree : cachedPrefixTreeCI; + if (cached == null || cached.getKey() != regionIdsSize) { + cached = new SimpleImmutableEntry<>(regionIdsSize, + PrefixTree.newTree(regionIds, context.isCaseSensitive() + ? PrefixTree.STRICT : PrefixTree.CASE_INSENSITIVE)); + if (context.isCaseSensitive()) { + cachedPrefixTree = cached; + } else { + cachedPrefixTreeCI = cached; + } + } + } + } + PrefixTree tree = cached.getValue(); + + // parse + String parsedZoneId = tree.match(text, position, length); + if (parsedZoneId == null || regionIds.contains(parsedZoneId) == false) { + if (text.charAt(position) == 'Z') { + context.setParsed(ZoneOffset.UTC); + return position + 1; + } + return ~position; + } + context.setParsed(ZoneId.of(parsedZoneId)); + return position + parsedZoneId.length(); + } + + + @Override + public String toString() { + return description; + } + } + + //----------------------------------------------------------------------- + /** + * A String based prefix tree for parsing time-zone names. + */ + static class PrefixTree { + protected String key; + protected String value; + protected char c0; // performance optimization to avoid the + // boundary check cost of key.charat(0) + protected PrefixTree child; + protected PrefixTree sibling; + + static final int STRICT = 1; + static final int CASE_INSENSITIVE = 2; + static final int LENIENT = 3; + + private PrefixTree(String k, String v, PrefixTree child) { + this.key = k; + this.value = v; + this.child = child; + if (k.length() == 0){ + c0 = 0xffff; + } else { + c0 = key.charAt(0); + } + } + + /** + * Creates a new prefix parsing tree. + * + * @param type the type of the prefix tree. One of the three supported + * types, STRICT, CASE_INSENSITIVE and LENIENT + * @return the tree, not null + */ + public static PrefixTree newTree(int type) { + PrefixTree tree; + switch(type) { + case STRICT: + tree = new PrefixTree("", null, null); + break; + case CASE_INSENSITIVE: + tree = new CI("", null, null); + break; + case LENIENT: + tree = new LENIENT("", null, null); + break; + default: + throw new IllegalArgumentException("Unknown type"); + } + return tree; + } + + /** + * Creates a new prefix parsing tree. + * + * @param keys a set of strings to build the prefix parsing tree, not null + * @param type the type of the prefix tree. One of the three supported + * types, STRICT, CASE_INSENSITIVE and LENIENT + * @return the tree, not null + */ + public static PrefixTree newTree(Set keys, int type) { + PrefixTree tree = newTree(type); + for (String k : keys) { + tree.add0(k, k); + } + return tree; + } + + /** + * Adds a pair of {key, value} into the prefix tree. + * + * @param k the key, not null + * @param v the value, not null + * @return true if the pair is added successfully + */ + public boolean add(String k, String v) { + return add0(k, v); + } + + private boolean add0(String k, String v) { + k = toKey(k); + int prefixLen = prefixLength(k); + if (prefixLen == key.length()) { + if (prefixLen < k.length()) { // down the tree + String subKey = k.substring(prefixLen); + PrefixTree c = child; + while (c != null) { + if (isEqual(c.c0, subKey.charAt(0))) { + return c.add0(subKey, v); + } + c = c.sibling; + } + // add the node as the child of the current node + c = newNode(subKey, v, null); + c.sibling = child; + child = c; + return true; + } + // have an existing already, keep it. + if (value != null) { + return false; + } + value = v; + return true; + } + // split the existing node + PrefixTree n1 = newNode(key.substring(prefixLen), value, child); + key = k.substring(0, prefixLen); + child = n1; + if (prefixLen < k.length()) { + PrefixTree n2 = newNode(k.substring(prefixLen), v, null); + child.sibling = n2; + value = null; + } else { + value = v; + } + return true; + } + + /** + * Match text with the prefix tree. + * + * @param text the input text to parse, not null + * @param off the offset position to start parsing at + * @param end the end position to stop parsing + * @return the resulting string, or null if no match found. + */ + public String match(CharSequence text, int off, int end) { + if (!prefixOf(text, off, end)){ + return null; + } + if (child != null && (off += key.length()) != end) { + PrefixTree c = child; + do { + if (isEqual(c.c0, text.charAt(off))) { + String found = c.match(text, off, end); + if (found != null) { + return found; + } + return value; + } + c = c.sibling; + } while (c != null); + } + return value; + } + + /** + * Match text with the prefix tree. + * + * @param text the input text to parse, not null + * @param pos the position to start parsing at, from 0 to the text + * length. Upon return, position will be updated to the new parse + * position, or unchanged, if no match found. + * @return the resulting string, or null if no match found. + */ + public String match(CharSequence text, ParsePosition pos) { + int off = pos.getIndex(); + int end = text.length(); + if (!prefixOf(text, off, end)){ + return null; + } + off += key.length(); + if (child != null && off != end) { + PrefixTree c = child; + do { + if (isEqual(c.c0, text.charAt(off))) { + pos.setIndex(off); + String found = c.match(text, pos); + if (found != null) { + return found; + } + break; + } + c = c.sibling; + } while (c != null); + } + pos.setIndex(off); + return value; + } + + protected String toKey(String k) { + return k; + } + + protected PrefixTree newNode(String k, String v, PrefixTree child) { + return new PrefixTree(k, v, child); + } + + protected boolean isEqual(char c1, char c2) { + return c1 == c2; + } + + protected boolean prefixOf(CharSequence text, int off, int end) { + if (text instanceof String) { + return ((String)text).startsWith(key, off); + } + int len = key.length(); + if (len > end - off) { + return false; + } + int off0 = 0; + while (len-- > 0) { + if (!isEqual(key.charAt(off0++), text.charAt(off++))) { + return false; + } + } + return true; + } + + private int prefixLength(String k) { + int off = 0; + while (off < k.length() && off < key.length()) { + if (!isEqual(k.charAt(off), key.charAt(off))) { + return off; + } + off++; + } + return off; + } + + /** + * Case Insensitive prefix tree. + */ + private static class CI extends PrefixTree { + + private CI(String k, String v, PrefixTree child) { + super(k, v, child); + } + + @Override + protected CI newNode(String k, String v, PrefixTree child) { + return new CI(k, v, child); + } + + @Override + protected boolean isEqual(char c1, char c2) { + return c1 == c2 || + Character.toUpperCase(c1) == Character.toUpperCase(c2) || + Character.toLowerCase(c1) == Character.toLowerCase(c2); + } + + @Override + protected boolean prefixOf(CharSequence text, int off, int end) { + int len = key.length(); + if (len > end - off) { + return false; + } + int off0 = 0; + while (len-- > 0) { + if (!isEqual(key.charAt(off0++), text.charAt(off++))) { + return false; + } + } + return true; + } + } + + /** + * Lenient prefix tree. Case insensitive and ignores characters + * like space, underscore and slash. + */ + private static class LENIENT extends CI { + + private LENIENT(String k, String v, PrefixTree child) { + super(k, v, child); + } + + @Override + protected CI newNode(String k, String v, PrefixTree child) { + return new LENIENT(k, v, child); + } + + private boolean isLenientChar(char c) { + return c == ' ' || c == '_' || c == '/'; + } + + protected String toKey(String k) { + for (int i = 0; i < k.length(); i++) { + if (isLenientChar(k.charAt(i))) { + StringBuilder sb = new StringBuilder(k.length()); + sb.append(k, 0, i); + i++; + while (i < k.length()) { + if (!isLenientChar(k.charAt(i))) { + sb.append(k.charAt(i)); + } + i++; + } + return sb.toString(); + } + } + return k; + } + + @Override + public String match(CharSequence text, ParsePosition pos) { + int off = pos.getIndex(); + int end = text.length(); + int len = key.length(); + int koff = 0; + while (koff < len && off < end) { + if (isLenientChar(text.charAt(off))) { + off++; + continue; + } + if (!isEqual(key.charAt(koff++), text.charAt(off++))) { + return null; + } + } + if (koff != len) { + return null; + } + if (child != null && off != end) { + int off0 = off; + while (off0 < end && isLenientChar(text.charAt(off0))) { + off0++; + } + if (off0 < end) { + PrefixTree c = child; + do { + if (isEqual(c.c0, text.charAt(off0))) { + pos.setIndex(off0); + String found = c.match(text, pos); + if (found != null) { + return found; + } + break; + } + c = c.sibling; + } while (c != null); + } + } + pos.setIndex(off); + return value; + } + } + } + + //----------------------------------------------------------------------- + /** + * Prints or parses a chronology. + */ + static final class ChronoPrinterParser implements DateTimePrinterParser { + /** The text style to output, null means the ID. */ + private final TextStyle textStyle; + + ChronoPrinterParser(TextStyle textStyle) { + // validated by caller + this.textStyle = textStyle; + } + + @Override + public boolean print(DateTimePrintContext context, StringBuilder buf) { + Chrono chrono = context.getValue(Queries.chrono()); + if (chrono == null) { + return false; + } + if (textStyle == null) { + buf.append(chrono.getId()); + } else { + buf.append(chrono.getId()); // TODO: Use symbols + } + return true; + } + + @Override + public int parse(DateTimeParseContext context, CharSequence text, int position) { + return ~position; // TODO, including case insensitive + } + } + + //----------------------------------------------------------------------- + /** + * Prints or parses a localized pattern. + */ + static final class LocalizedPrinterParser implements DateTimePrinterParser { + private final FormatStyle dateStyle; + private final FormatStyle timeStyle; + private final Chrono chrono; + + /** + * Constructor. + * + * @param dateStyle the date style to use, may be null + * @param timeStyle the time style to use, may be null + * @param chrono the chronology to use, not null + */ + LocalizedPrinterParser(FormatStyle dateStyle, FormatStyle timeStyle, Chrono chrono) { + // validated by caller + this.dateStyle = dateStyle; + this.timeStyle = timeStyle; + this.chrono = chrono; + } + + @Override + public boolean print(DateTimePrintContext context, StringBuilder buf) { + return formatter(context.getLocale()).toPrinterParser(false).print(context, buf); + } + + @Override + public int parse(DateTimeParseContext context, CharSequence text, int position) { + return formatter(context.getLocale()).toPrinterParser(false).parse(context, text, position); + } + + /** + * Gets the formatter to use. + * + * @param locale the locale to use, not null + * @return the formatter, not null + * @throws IllegalArgumentException if the formatter cannot be found + */ + private DateTimeFormatter formatter(Locale locale) { + return DateTimeFormatStyleProvider.getInstance() + .getFormatter(dateStyle, timeStyle, chrono, locale); + } + + @Override + public String toString() { + return "Localized(" + (dateStyle != null ? dateStyle : "") + "," + + (timeStyle != null ? timeStyle : "") + "," + chrono.getId() + ")"; + } + } + + + //----------------------------------------------------------------------- + /** + * Prints or parses a localized pattern from a localized field. + * The specific formatter and parameters is not selected until the + * the field is to be printed or parsed. + * The locale is needed to select the proper WeekFields from which + * the field for day-of-week, week-of-month, or week-of-year is selected. + */ + static final class WeekBasedFieldPrinterParser implements DateTimePrinterParser { + private char chr; + private int count; + + /** + * Constructor. + * + * @param chr the pattern format letter that added this PrinterParser. + * @param count the repeat count of the format letter + */ + WeekBasedFieldPrinterParser(char chr, int count) { + this.chr = chr; + this.count = count; + } + + @Override + public boolean print(DateTimePrintContext context, StringBuilder buf) { + return printerParser(context.getLocale()).print(context, buf); + } + + @Override + public int parse(DateTimeParseContext context, CharSequence text, int position) { + return printerParser(context.getLocale()).parse(context, text, position); + } + + /** + * Gets the printerParser to use based on the field and the locale. + * + * @param locale the locale to use, not null + * @return the formatter, not null + * @throws IllegalArgumentException if the formatter cannot be found + */ + private DateTimePrinterParser printerParser(Locale locale) { + WeekFields weekDef = WeekFields.of(locale); + TemporalField field = null; + switch (chr) { + case 'e': + field = weekDef.dayOfWeek(); + break; + case 'w': + field = weekDef.weekOfMonth(); + break; + case 'W': + field = weekDef.weekOfYear(); + break; + default: + throw new IllegalStateException("unreachable"); + } + return new NumberPrinterParser(field, (count == 2 ? 2 : 1), 2, SignStyle.NOT_NEGATIVE); + } + + @Override + public String toString() { + return String.format("WeekBased(%c%d)", chr, count); + } + } + + + //------------------------------------------------------------------------- + /** + * Length comparator. + */ + static final Comparator LENGTH_SORT = new Comparator() { + @Override + public int compare(String str1, String str2) { + return str1.length() == str2.length() ? str1.compareTo(str2) : str1.length() - str2.length(); + } + }; + +} diff --git a/src/share/classes/java/time/format/DateTimeFormatters.java b/src/share/classes/java/time/format/DateTimeFormatters.java new file mode 100644 index 0000000000000000000000000000000000000000..d1d6efd43a0db97e5680ee5ad9d00816ca5544e9 --- /dev/null +++ b/src/share/classes/java/time/format/DateTimeFormatters.java @@ -0,0 +1,972 @@ +/* + * 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 java.time.format; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoField.YEAR; + +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.ChronoField; +import java.time.temporal.ISOFields; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +/** + * Provides common implementations of {@code DateTimeFormatter}. + *

+ * This utility class provides three different ways to obtain a formatter. + *

    + *
  • Using pattern letters, such as {@code yyyy-MMM-dd} + *
  • Using localized styles, such as {@code long} or {@code medium} + *
  • Using predefined constants, such as {@code isoLocalDate()} + *

+ * + *

Specification for implementors

+ * This is a thread-safe utility class. + * All returned formatters are immutable and thread-safe. + * + * @since 1.8 + */ +public final class DateTimeFormatters { + + /** + * Private constructor since this is a utility class. + */ + private DateTimeFormatters() { + } + + //----------------------------------------------------------------------- + /** + * Creates a formatter using the specified pattern. + *

+ * This method will create a formatter based on a simple pattern of letters and symbols. + * For example, {@code d MMM yyyy} will format 2011-12-03 as '3 Dec 2011'. + *

+ * The returned formatter will use the default locale, but this can be changed + * using {@link DateTimeFormatter#withLocale(Locale)}. + *

+ * All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters. + * The following pattern letters are defined: + *

+     *  Symbol  Meaning                     Presentation      Examples
+     *  ------  -------                     ------------      -------
+     *   G       era                         number/text       1; 01; AD; Anno Domini
+     *   y       year                        year              2004; 04
+     *   D       day-of-year                 number            189
+     *   M       month-of-year               number/text       7; 07; Jul; July; J
+     *   d       day-of-month                number            10
+     *
+     *   Q       quarter-of-year             number/text       3; 03; Q3
+     *   Y       week-based-year             year              1996; 96
+     *   w       week-of-year                number            27
+     *   W       week-of-month               number            27
+     *   e       localized day-of-week       number            2; Tue; Tuesday; T
+     *   E       day-of-week                 number/text       2; Tue; Tuesday; T
+     *   F       week-of-month               number            3
+     *
+     *   a       am-pm-of-day                text              PM
+     *   h       clock-hour-of-am-pm (1-12)  number            12
+     *   K       hour-of-am-pm (0-11)        number            0
+     *   k       clock-hour-of-am-pm (1-24)  number            0
+     *
+     *   H       hour-of-day (0-23)          number            0
+     *   m       minute-of-hour              number            30
+     *   s       second-of-minute            number            55
+     *   S       fraction-of-second          fraction          978
+     *   A       milli-of-day                number            1234
+     *   n       nano-of-second              number            987654321
+     *   N       nano-of-day                 number            1234000000
+     *
+     *   I       time-zone ID                zoneId            America/Los_Angeles
+     *   z       time-zone name              text              Pacific Standard Time; PST
+     *   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;
+     *   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
+     *
+     *   p       pad next                    pad modifier      1
+     *
+     *   '       escape for text             delimiter
+     *   ''      single quote                literal           '
+     *   [       optional section start
+     *   ]       optional section end
+     *   {}      reserved for future use
+     * 
+ *

+ * The count of pattern letters determine the format. + *

+ * Text: The text style is determined based on the number of pattern letters used. + * Less than 4 pattern letters will use the {@link TextStyle#SHORT short form}. + * Exactly 4 pattern letters will use the {@link TextStyle#FULL full form}. + * Exactly 5 pattern letters will use the {@link TextStyle#NARROW narrow form}. + *

+ * Number: If the count of letters is one, then the value is printed using the minimum number + * of digits and without padding as per {@link DateTimeFormatterBuilder#appendValue(java.time.temporal.TemporalField)}. + * Otherwise, the count of digits is used as the width of the output field as per + * {@link DateTimeFormatterBuilder#appendValue(java.time.temporal.TemporalField, int)}. + *

+ * Number/Text: If the count of pattern letters is 3 or greater, use the Text rules above. + * Otherwise use the Number rules above. + *

+ * Fraction: Outputs the nano-of-second field as a fraction-of-second. + * The nano-of-second value has nine digits, thus the count of pattern letters is from 1 to 9. + * If it is less than 9, then the nano-of-second value is truncated, with only the most + * significant digits being output. + * When parsing in strict mode, the number of parsed digits must match the count of pattern letters. + * When parsing in lenient mode, the number of parsed digits must be at least the count of pattern + * letters, up to 9 digits. + *

+ * Year: The count of letters determines the minimum field width below which padding is used. + * If the count of letters is two, then a {@link DateTimeFormatterBuilder#appendValueReduced reduced} + * two digit form is used. + * For printing, this outputs the rightmost two digits. For parsing, this will parse using the + * base value of 2000, resulting in a year within the range 2000 to 2099 inclusive. + * If the count of letters is less than four (but not two), then the sign is only output for negative + * years as per {@link SignStyle#NORMAL}. + * Otherwise, the sign is output if the pad width is exceeded, as per {@link SignStyle#EXCEEDS_PAD} + *

+ * ZoneId: 'I' outputs the zone ID, such as 'Europe/Paris'. + *

+ * Offset X: This formats the offset using 'Z' when the offset is zero. + * One letter outputs just the hour', such as '+01' + * Two letters outputs the hour and minute, without a colon, such as '+0130'. + * Three letters outputs the hour and minute, with a colon, such as '+01:30'. + * Four letters outputs the hour and minute and optional second, without a colon, such as '+013015'. + * Five letters outputs the hour and minute and optional second, with a colon, such as '+01:30:15'. + *

+ * Offset Z: This formats the offset using '+0000' or '+00:00' when the offset is zero. + * One or two letters outputs the hour and minute, without a colon, such as '+0130'. + * Three letters outputs the hour and minute, with a colon, such as '+01:30'. + *

+ * Zone names: Time zone names ('z') cannot be parsed. + *

+ * Optional section: The optional section markers work exactly like calling + * {@link DateTimeFormatterBuilder#optionalStart()} and {@link DateTimeFormatterBuilder#optionalEnd()}. + *

+ * Pad modifier: Modifies the pattern that immediately follows to be padded with spaces. + * The pad width is determined by the number of pattern letters. + * This is the same as calling {@link DateTimeFormatterBuilder#padNext(int)}. + *

+ * For example, 'ppH' outputs the hour-of-day padded on the left with spaces to a width of 2. + *

+ * Any unrecognized letter is an error. + * Any non-letter character, other than '[', ']', '{', '}' and the single quote will be output directly. + * Despite this, it is recommended to use single quotes around all characters that you want to + * output directly to ensure that future changes do not break your application. + *

+ * The pattern string is similar, but not identical, to {@link java.text.SimpleDateFormat SimpleDateFormat}. + * Pattern letters 'E' and 'u' are merged, which changes the meaning of "E" and "EE" to be numeric. + * Pattern letters 'Z' and 'X' are extended. + * Pattern letter 'y' and 'Y' parse years of two digits and more than 4 digits differently. + * Pattern letters 'n', 'A', 'N', 'I' and 'p' are added. + * Number types will reject large numbers. + * The pattern string is also similar, but not identical, to that defined by the + * Unicode Common Locale Data Repository (CLDR). + * + * @param pattern the pattern to use, not null + * @return the formatter based on the pattern, not null + * @throws IllegalArgumentException if the pattern is invalid + * @see DateTimeFormatterBuilder#appendPattern(String) + */ + public static DateTimeFormatter pattern(String pattern) { + return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(); + } + + /** + * Creates a formatter using the specified pattern. + *

+ * This method will create a formatter based on a simple pattern of letters and symbols. + * For example, {@code d MMM yyyy} will format 2011-12-03 as '3 Dec 2011'. + *

+ * See {@link #pattern(String)} for details of the pattern. + *

+ * The returned formatter will use the specified locale, but this can be changed + * using {@link DateTimeFormatter#withLocale(Locale)}. + * + * @param pattern the pattern to use, not null + * @param locale the locale to use, not null + * @return the formatter based on the pattern, not null + * @throws IllegalArgumentException if the pattern is invalid + * @see DateTimeFormatterBuilder#appendPattern(String) + */ + public static DateTimeFormatter pattern(String pattern, Locale locale) { + return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale); + } + + //----------------------------------------------------------------------- + /** + * Returns a locale specific date format. + *

+ * This returns a formatter that will print/parse a date. + * The exact format pattern used varies by locale. + *

+ * The locale is determined from the formatter. The formatter returned directly by + * this method will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}. + * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)} + * on the result of this method. + *

+ * Note that the localized pattern is looked up lazily. + * This {@code DateTimeFormatter} holds the style required and the locale, + * looking up the pattern required on demand. + * + * @param dateStyle the formatter style to obtain, not null + * @return the date formatter, not null + */ + public static DateTimeFormatter localizedDate(FormatStyle dateStyle) { + Objects.requireNonNull(dateStyle, "dateStyle"); + return new DateTimeFormatterBuilder().appendLocalized(dateStyle, null).toFormatter(); + } + + /** + * Returns a locale specific time format. + *

+ * This returns a formatter that will print/parse a time. + * The exact format pattern used varies by locale. + *

+ * The locale is determined from the formatter. The formatter returned directly by + * this method will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}. + * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)} + * on the result of this method. + *

+ * Note that the localized pattern is looked up lazily. + * This {@code DateTimeFormatter} holds the style required and the locale, + * looking up the pattern required on demand. + * + * @param timeStyle the formatter style to obtain, not null + * @return the time formatter, not null + */ + public static DateTimeFormatter localizedTime(FormatStyle timeStyle) { + Objects.requireNonNull(timeStyle, "timeStyle"); + return new DateTimeFormatterBuilder().appendLocalized(null, timeStyle).toFormatter(); + } + + /** + * Returns a locale specific date-time format, which is typically of short length. + *

+ * This returns a formatter that will print/parse a date-time. + * The exact format pattern used varies by locale. + *

+ * The locale is determined from the formatter. The formatter returned directly by + * this method will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}. + * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)} + * on the result of this method. + *

+ * Note that the localized pattern is looked up lazily. + * This {@code DateTimeFormatter} holds the style required and the locale, + * looking up the pattern required on demand. + * + * @param dateTimeStyle the formatter style to obtain, not null + * @return the date-time formatter, not null + */ + public static DateTimeFormatter localizedDateTime(FormatStyle dateTimeStyle) { + Objects.requireNonNull(dateTimeStyle, "dateTimeStyle"); + return new DateTimeFormatterBuilder().appendLocalized(dateTimeStyle, dateTimeStyle).toFormatter(); + } + + /** + * Returns a locale specific date and time format. + *

+ * This returns a formatter that will print/parse a date-time. + * The exact format pattern used varies by locale. + *

+ * The locale is determined from the formatter. The formatter returned directly by + * this method will use the {@link Locale#getDefault() default FORMAT locale}. + * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)} + * on the result of this method. + *

+ * Note that the localized pattern is looked up lazily. + * This {@code DateTimeFormatter} holds the style required and the locale, + * looking up the pattern required on demand. + * + * @param dateStyle the date formatter style to obtain, not null + * @param timeStyle the time formatter style to obtain, not null + * @return the date, time or date-time formatter, not null + */ + public static DateTimeFormatter localizedDateTime(FormatStyle dateStyle, FormatStyle timeStyle) { + Objects.requireNonNull(dateStyle, "dateStyle"); + Objects.requireNonNull(timeStyle, "timeStyle"); + return new DateTimeFormatterBuilder().appendLocalized(dateStyle, timeStyle).toFormatter(); + } + + //----------------------------------------------------------------------- + /** + * Returns the ISO date formatter that prints/parses a date without an offset, + * such as '2011-12-03'. + *

+ * This returns an immutable formatter capable of printing and parsing + * the ISO-8601 extended local date format. + * The format consists of: + *

    + *
  • Four digits or more for the {@link ChronoField#YEAR year}. + * Years in the range 0000 to 9999 will be pre-padded by zero to ensure four digits. + * Years outside that range will have a prefixed positive or negative symbol. + *
  • A dash + *
  • Two digits for the {@link ChronoField#MONTH_OF_YEAR month-of-year}. + * This is pre-padded by zero to ensure two digits. + *
  • A dash + *
  • Two digits for the {@link ChronoField#DAY_OF_MONTH day-of-month}. + * This is pre-padded by zero to ensure two digits. + *

+ * + * @return the ISO local date formatter, not null + */ + public static DateTimeFormatter isoLocalDate() { + return ISO_LOCAL_DATE; + } + + /** Singleton date formatter. */ + private static final DateTimeFormatter ISO_LOCAL_DATE; + static { + ISO_LOCAL_DATE = new DateTimeFormatterBuilder() + .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) + .appendLiteral('-') + .appendValue(MONTH_OF_YEAR, 2) + .appendLiteral('-') + .appendValue(DAY_OF_MONTH, 2) + .toFormatter(); + } + + //----------------------------------------------------------------------- + /** + * Returns the ISO date formatter that prints/parses a date with an offset, + * such as '2011-12-03+01:00'. + *

+ * This returns an immutable formatter capable of printing and parsing + * the ISO-8601 extended offset date format. + * The format consists of: + *

    + *
  • The {@link #isoLocalDate()} + *
  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then + * they will be handled even though this is not part of the ISO-8601 standard. + * Parsing is case insensitive. + *

+ * + * @return the ISO offset date formatter, not null + */ + public static DateTimeFormatter isoOffsetDate() { + return ISO_OFFSET_DATE; + } + + /** Singleton date formatter. */ + private static final DateTimeFormatter ISO_OFFSET_DATE; + static { + ISO_OFFSET_DATE = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE) + .appendOffsetId() + .toFormatter(); + } + + //----------------------------------------------------------------------- + /** + * Returns the ISO date formatter that prints/parses a date with the + * offset if available, such as '2011-12-03' or '2011-12-03+01:00'. + *

+ * This returns an immutable formatter capable of printing and parsing + * the ISO-8601 extended date format. + * The format consists of: + *

    + *
  • The {@link #isoLocalDate()} + *
  • If the offset is not available to print/parse then the format is complete. + *
  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then + * they will be handled even though this is not part of the ISO-8601 standard. + * Parsing is case insensitive. + *

+ * As this formatter has an optional element, it may be necessary to parse using + * {@link DateTimeFormatter#parseBest}. + * + * @return the ISO date formatter, not null + */ + public static DateTimeFormatter isoDate() { + return ISO_DATE; + } + + /** Singleton date formatter. */ + private static final DateTimeFormatter ISO_DATE; + static { + ISO_DATE = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE) + .optionalStart() + .appendOffsetId() + .toFormatter(); + } + + //----------------------------------------------------------------------- + /** + * Returns the ISO time formatter that prints/parses a time without an offset, + * such as '10:15' or '10:15:30'. + *

+ * This returns an immutable formatter capable of printing and parsing + * the ISO-8601 extended local time format. + * The format consists of: + *

    + *
  • Two digits for the {@link ChronoField#HOUR_OF_DAY hour-of-day}. + * This is pre-padded by zero to ensure two digits. + *
  • A colon + *
  • Two digits for the {@link ChronoField#MINUTE_OF_HOUR minute-of-hour}. + * This is pre-padded by zero to ensure two digits. + *
  • If the second-of-minute is not available to print/parse then the format is complete. + *
  • A colon + *
  • Two digits for the {@link ChronoField#SECOND_OF_MINUTE second-of-minute}. + * This is pre-padded by zero to ensure two digits. + *
  • If the nano-of-second is zero or not available to print/parse then the format is complete. + *
  • A decimal point + *
  • One to nine digits for the {@link ChronoField#NANO_OF_SECOND nano-of-second}. + * As many digits will be printed as required. + *

+ * + * @return the ISO local time formatter, not null + */ + public static DateTimeFormatter isoLocalTime() { + return ISO_LOCAL_TIME; + } + + /** Singleton date formatter. */ + private static final DateTimeFormatter ISO_LOCAL_TIME; + static { + ISO_LOCAL_TIME = new DateTimeFormatterBuilder() + .appendValue(HOUR_OF_DAY, 2) + .appendLiteral(':') + .appendValue(MINUTE_OF_HOUR, 2) + .optionalStart() + .appendLiteral(':') + .appendValue(SECOND_OF_MINUTE, 2) + .optionalStart() + .appendFraction(NANO_OF_SECOND, 0, 9, true) + .toFormatter(); + } + + //----------------------------------------------------------------------- + /** + * Returns the ISO time formatter that prints/parses a time with an offset, + * such as '10:15+01:00' or '10:15:30+01:00'. + *

+ * This returns an immutable formatter capable of printing and parsing + * the ISO-8601 extended offset time format. + * The format consists of: + *

    + *
  • The {@link #isoLocalTime()} + *
  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then + * they will be handled even though this is not part of the ISO-8601 standard. + * Parsing is case insensitive. + *

+ * + * @return the ISO offset time formatter, not null + */ + public static DateTimeFormatter isoOffsetTime() { + return ISO_OFFSET_TIME; + } + + /** Singleton date formatter. */ + private static final DateTimeFormatter ISO_OFFSET_TIME; + static { + ISO_OFFSET_TIME = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_TIME) + .appendOffsetId() + .toFormatter(); + } + + //----------------------------------------------------------------------- + /** + * Returns the ISO time formatter that prints/parses a time, with the + * offset if available, such as '10:15', '10:15:30' or '10:15:30+01:00'. + *

+ * This returns an immutable formatter capable of printing and parsing + * the ISO-8601 extended offset time format. + * The format consists of: + *

    + *
  • The {@link #isoLocalTime()} + *
  • If the offset is not available to print/parse then the format is complete. + *
  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then + * they will be handled even though this is not part of the ISO-8601 standard. + * Parsing is case insensitive. + *

+ * As this formatter has an optional element, it may be necessary to parse using + * {@link DateTimeFormatter#parseBest}. + * + * @return the ISO time formatter, not null + */ + public static DateTimeFormatter isoTime() { + return ISO_TIME; + } + + /** Singleton date formatter. */ + private static final DateTimeFormatter ISO_TIME; + static { + ISO_TIME = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_TIME) + .optionalStart() + .appendOffsetId() + .toFormatter(); + } + + //----------------------------------------------------------------------- + /** + * Returns the ISO date formatter that prints/parses a date-time + * without an offset, such as '2011-12-03T10:15:30'. + *

+ * This returns an immutable formatter capable of printing and parsing + * the ISO-8601 extended offset date-time format. + * The format consists of: + *

    + *
  • The {@link #isoLocalDate()} + *
  • The letter 'T'. Parsing is case insensitive. + *
  • The {@link #isoLocalTime()} + *

+ * + * @return the ISO local date-time formatter, not null + */ + public static DateTimeFormatter isoLocalDateTime() { + return ISO_LOCAL_DATE_TIME; + } + + /** Singleton date formatter. */ + private static final DateTimeFormatter ISO_LOCAL_DATE_TIME; + static { + ISO_LOCAL_DATE_TIME = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE) + .appendLiteral('T') + .append(ISO_LOCAL_TIME) + .toFormatter(); + } + + //----------------------------------------------------------------------- + /** + * Returns the ISO date formatter that prints/parses a date-time + * with an offset, such as '2011-12-03T10:15:30+01:00'. + *

+ * This returns an immutable formatter capable of printing and parsing + * the ISO-8601 extended offset date-time format. + * The format consists of: + *

    + *
  • The {@link #isoLocalDateTime()} + *
  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then + * they will be handled even though this is not part of the ISO-8601 standard. + * Parsing is case insensitive. + *

+ * + * @return the ISO offset date-time formatter, not null + */ + public static DateTimeFormatter isoOffsetDateTime() { + return ISO_OFFSET_DATE_TIME; + } + + /** Singleton date formatter. */ + private static final DateTimeFormatter ISO_OFFSET_DATE_TIME; + static { + ISO_OFFSET_DATE_TIME = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_TIME) + .appendOffsetId() + .toFormatter(); + } + + //----------------------------------------------------------------------- + /** + * Returns the ISO date formatter that prints/parses a date-time with + * offset and zone, such as '2011-12-03T10:15:30+01:00[Europe/Paris]'. + *

+ * This returns an immutable formatter capable of printing and parsing + * a format that extends the ISO-8601 extended offset date-time format + * to add the time-zone. + * The format consists of: + *

    + *
  • The {@link #isoOffsetDateTime()} + *
  • If the zone ID is not available or is a {@code ZoneOffset} then the format is complete. + *
  • An open square bracket '['. + *
  • The {@link ZoneId#getId() zone ID}. This is not part of the ISO-8601 standard. + * Parsing is case sensitive. + *
  • A close square bracket ']'. + *

+ * + * @return the ISO zoned date-time formatter, not null + */ + public static DateTimeFormatter isoZonedDateTime() { + return ISO_ZONED_DATE_TIME; + } + + /** Singleton date formatter. */ + private static final DateTimeFormatter ISO_ZONED_DATE_TIME; + static { + ISO_ZONED_DATE_TIME = new DateTimeFormatterBuilder() + .append(ISO_OFFSET_DATE_TIME) + .optionalStart() + .appendLiteral('[') + .parseCaseSensitive() + .appendZoneRegionId() + .appendLiteral(']') + .toFormatter(); + } + + //----------------------------------------------------------------------- + /** + * Returns the ISO date formatter that prints/parses a date-time + * with the offset and zone if available, such as '2011-12-03T10:15:30', + * '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30+01:00[Europe/Paris]'. + *

+ * This returns an immutable formatter capable of printing and parsing + * the ISO-8601 extended offset date-time format. + * The format consists of: + *

    + *
  • The {@link #isoLocalDateTime()} + *
  • If the offset is not available to print/parse then the format is complete. + *
  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then + * they will be handled even though this is not part of the ISO-8601 standard. + *
  • If the zone ID is not available or is a {@code ZoneOffset} then the format is complete. + *
  • An open square bracket '['. + *
  • The {@link ZoneId#getId() zone ID}. This is not part of the ISO-8601 standard. + * Parsing is case sensitive. + *
  • A close square bracket ']'. + *

+ * As this formatter has an optional element, it may be necessary to parse using + * {@link DateTimeFormatter#parseBest}. + * + * @return the ISO date-time formatter, not null + */ + public static DateTimeFormatter isoDateTime() { + return ISO_DATE_TIME; + } + + /** Singleton date formatter. */ + private static final DateTimeFormatter ISO_DATE_TIME; + static { + ISO_DATE_TIME = new DateTimeFormatterBuilder() + .append(ISO_LOCAL_DATE_TIME) + .optionalStart() + .appendOffsetId() + .optionalStart() + .appendLiteral('[') + .parseCaseSensitive() + .appendZoneRegionId() + .appendLiteral(']') + .toFormatter(); + } + + //----------------------------------------------------------------------- + /** + * Returns the ISO date formatter that prints/parses the ordinal date + * without an offset, such as '2012-337'. + *

+ * This returns an immutable formatter capable of printing and parsing + * the ISO-8601 extended ordinal date format. + * The format consists of: + *

    + *
  • Four digits or more for the {@link ChronoField#YEAR year}. + * Years in the range 0000 to 9999 will be pre-padded by zero to ensure four digits. + * Years outside that range will have a prefixed positive or negative symbol. + *
  • A dash + *
  • Three digits for the {@link ChronoField#DAY_OF_YEAR day-of-year}. + * This is pre-padded by zero to ensure three digits. + *
  • If the offset is not available to print/parse then the format is complete. + *
  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then + * they will be handled even though this is not part of the ISO-8601 standard. + * Parsing is case insensitive. + *

+ * As this formatter has an optional element, it may be necessary to parse using + * {@link DateTimeFormatter#parseBest}. + * + * @return the ISO ordinal date formatter, not null + */ + public static DateTimeFormatter isoOrdinalDate() { + return ISO_ORDINAL_DATE; + } + + /** Singleton date formatter. */ + private static final DateTimeFormatter ISO_ORDINAL_DATE; + static { + ISO_ORDINAL_DATE = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) + .appendLiteral('-') + .appendValue(DAY_OF_YEAR, 3) + .optionalStart() + .appendOffsetId() + .toFormatter(); + } + + //----------------------------------------------------------------------- + /** + * Returns the ISO date formatter that prints/parses the week-based date + * without an offset, such as '2012-W48-6'. + *

+ * This returns an immutable formatter capable of printing and parsing + * the ISO-8601 extended week-based date format. + * The format consists of: + *

    + *
  • Four digits or more for the {@link ISOFields#WEEK_BASED_YEAR week-based-year}. + * Years in the range 0000 to 9999 will be pre-padded by zero to ensure four digits. + * Years outside that range will have a prefixed positive or negative symbol. + *
  • A dash + *
  • The letter 'W'. Parsing is case insensitive. + *
  • Two digits for the {@link ISOFields#WEEK_OF_WEEK_BASED_YEAR week-of-week-based-year}. + * This is pre-padded by zero to ensure three digits. + *
  • A dash + *
  • One digit for the {@link ChronoField#DAY_OF_WEEK day-of-week}. + * The value run from Monday (1) to Sunday (7). + *
  • If the offset is not available to print/parse then the format is complete. + *
  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then + * they will be handled even though this is not part of the ISO-8601 standard. + * Parsing is case insensitive. + *

+ * As this formatter has an optional element, it may be necessary to parse using + * {@link DateTimeFormatter#parseBest}. + * + * @return the ISO week-based date formatter, not null + */ + public static DateTimeFormatter isoWeekDate() { + return ISO_WEEK_DATE; + } + + /** Singleton date formatter. */ + private static final DateTimeFormatter ISO_WEEK_DATE; + static { + ISO_WEEK_DATE = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .appendValue(ISOFields.WEEK_BASED_YEAR, 4, 10, SignStyle.EXCEEDS_PAD) + .appendLiteral("-W") + .appendValue(ISOFields.WEEK_OF_WEEK_BASED_YEAR, 2) + .appendLiteral('-') + .appendValue(DAY_OF_WEEK, 1) + .optionalStart() + .appendOffsetId() + .toFormatter(); + } + + //----------------------------------------------------------------------- + /** + * Returns the ISO instant formatter that prints/parses an instant in UTC. + *

+ * This returns an immutable formatter capable of printing and parsing + * the ISO-8601 instant format. + * The format consists of: + *

    + *
  • The {@link #isoOffsetDateTime()} where the instant is converted from + * {@link ChronoField#INSTANT_SECONDS} and {@link ChronoField#NANO_OF_SECOND} + * using the {@code UTC} offset. Parsing is case insensitive. + *

+ * + * @return the ISO instant formatter, not null + */ + public static DateTimeFormatter isoInstant() { + return ISO_INSTANT; + } + + /** Singleton formatter. */ + private static final DateTimeFormatter ISO_INSTANT; + static { + ISO_INSTANT = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .appendInstant() + .toFormatter(); + } + + //----------------------------------------------------------------------- + /** + * Returns the ISO date formatter that prints/parses a date without an offset, + * such as '20111203'. + *

+ * This returns an immutable formatter capable of printing and parsing + * the ISO-8601 basic local date format. + * The format consists of: + *

    + *
  • Four digits for the {@link ChronoField#YEAR year}. + * Only years in the range 0000 to 9999 are supported. + *
  • Two digits for the {@link ChronoField#MONTH_OF_YEAR month-of-year}. + * This is pre-padded by zero to ensure two digits. + *
  • Two digits for the {@link ChronoField#DAY_OF_MONTH day-of-month}. + * This is pre-padded by zero to ensure two digits. + *
  • If the offset is not available to print/parse then the format is complete. + *
  • The {@link ZoneOffset#getId() offset ID} without colons. If the offset has + * seconds then they will be handled even though this is not part of the ISO-8601 standard. + * Parsing is case insensitive. + *

+ * As this formatter has an optional element, it may be necessary to parse using + * {@link DateTimeFormatter#parseBest}. + * + * @return the ISO basic local date formatter, not null + */ + public static DateTimeFormatter basicIsoDate() { + return BASIC_ISO_DATE; + } + + /** Singleton date formatter. */ + private static final DateTimeFormatter BASIC_ISO_DATE; + static { + BASIC_ISO_DATE = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .appendValue(YEAR, 4) + .appendValue(MONTH_OF_YEAR, 2) + .appendValue(DAY_OF_MONTH, 2) + .optionalStart() + .appendOffset("+HHMMss", "Z") + .toFormatter(); + } + + //----------------------------------------------------------------------- + /** + * Returns the RFC-1123 date-time formatter, such as 'Tue, 3 Jun 2008 11:05:30 GMT'. + *

+ * This returns an immutable formatter capable of printing and parsing + * most of the RFC-1123 format. + * RFC-1123 updates RFC-822 changing the year from two digits to four. + * This implementation requires a four digit year. + * This implementation also does not handle North American or military zone + * names, only 'GMT' and offset amounts. + *

+ * The format consists of: + *

    + *
  • If the day-of-week is not available to print/parse then jump to day-of-month. + *
  • Three letter {@link ChronoField#DAY_OF_WEEK day-of-week} in English. + *
  • A comma + *
  • A space + *
  • One or two digits for the {@link ChronoField#DAY_OF_MONTH day-of-month}. + *
  • A space + *
  • Three letter {@link ChronoField#MONTH_OF_YEAR month-of-year} in English. + *
  • A space + *
  • Four digits for the {@link ChronoField#YEAR year}. + * Only years in the range 0000 to 9999 are supported. + *
  • A space + *
  • Two digits for the {@link ChronoField#HOUR_OF_DAY hour-of-day}. + * This is pre-padded by zero to ensure two digits. + *
  • A colon + *
  • Two digits for the {@link ChronoField#MINUTE_OF_HOUR minute-of-hour}. + * This is pre-padded by zero to ensure two digits. + *
  • If the second-of-minute is not available to print/parse then jump to the next space. + *
  • A colon + *
  • Two digits for the {@link ChronoField#SECOND_OF_MINUTE second-of-minute}. + * This is pre-padded by zero to ensure two digits. + *
  • A space + *
  • The {@link ZoneOffset#getId() offset ID} without colons or seconds. + * An offset of zero uses "GMT". North American zone names and military zone names are not handled. + *

+ * Parsing is case insensitive. + * + * @return the RFC-1123 formatter, not null + */ + public static DateTimeFormatter rfc1123() { + return RFC_1123_DATE_TIME; + } + + /** Singleton date formatter. */ + private static final DateTimeFormatter RFC_1123_DATE_TIME; + static { + // manually code maps to ensure correct data always used + // (locale data can be changed by application code) + Map dow = new HashMap<>(); + dow.put(1L, "Mon"); + dow.put(2L, "Tue"); + dow.put(3L, "Wed"); + dow.put(4L, "Thu"); + dow.put(5L, "Fri"); + dow.put(6L, "Sat"); + dow.put(7L, "Sun"); + Map moy = new HashMap<>(); + moy.put(1L, "Jan"); + moy.put(2L, "Feb"); + moy.put(3L, "Mar"); + moy.put(4L, "Apr"); + moy.put(5L, "May"); + moy.put(6L, "Jun"); + moy.put(7L, "Jul"); + moy.put(8L, "Aug"); + moy.put(9L, "Sep"); + moy.put(10L, "Oct"); + moy.put(11L, "Nov"); + moy.put(12L, "Dec"); + RFC_1123_DATE_TIME = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .parseLenient() + .optionalStart() + .appendText(DAY_OF_WEEK, dow) + .appendLiteral(", ") + .optionalEnd() + .appendValue(DAY_OF_MONTH, 1, 2, SignStyle.NOT_NEGATIVE) + .appendLiteral(' ') + .appendText(MONTH_OF_YEAR, moy) + .appendLiteral(' ') + .appendValue(YEAR, 4) // 2 digit year not handled + .appendLiteral(' ') + .appendValue(HOUR_OF_DAY, 2) + .appendLiteral(':') + .appendValue(MINUTE_OF_HOUR, 2) + .optionalStart() + .appendLiteral(':') + .appendValue(SECOND_OF_MINUTE, 2) + .optionalEnd() + .appendLiteral(' ') + .appendOffset("+HHMM", "GMT") // should handle UT/Z/EST/EDT/CST/CDT/MST/MDT/PST/MDT + .toFormatter(); + } + +} diff --git a/src/share/classes/java/time/format/DateTimeParseContext.java b/src/share/classes/java/time/format/DateTimeParseContext.java new file mode 100644 index 0000000000000000000000000000000000000000..6da730ea1d4cddca1941cebbd1ee69bd7030b37e --- /dev/null +++ b/src/share/classes/java/time/format/DateTimeParseContext.java @@ -0,0 +1,409 @@ +/* + * 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 java.time.format; + +import java.time.temporal.TemporalField; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +/** + * Context object used during date and time parsing. + *

+ * This class represents the current state of the parse. + * It has the ability to store and retrieve the parsed values and manage optional segments. + * It also provides key information to the parsing methods. + *

+ * Once parsing is complete, the {@link #toBuilder()} is typically used + * to obtain a builder that can combine the separate parsed fields into meaningful values. + * + *

Specification for implementors

+ * This class is a mutable context intended for use from a single thread. + * Usage of the class is thread-safe within standard parsing as a new instance of this class + * is automatically created for each parse and parsing is single-threaded + * + * @since 1.8 + */ +final class DateTimeParseContext { + + /** + * The formatter, not null. + */ + private DateTimeFormatter formatter; + /** + * Whether to parse using case sensitively. + */ + private boolean caseSensitive = true; + /** + * Whether to parse using strict rules. + */ + private boolean strict = true; + /** + * The list of parsed data. + */ + private final ArrayList parsed = new ArrayList<>(); + + /** + * Creates a new instance of the context. + * + * @param formatter the formatter controlling the parse, not null + */ + DateTimeParseContext(DateTimeFormatter formatter) { + super(); + this.formatter = formatter; + parsed.add(new Parsed()); + } + + /** + * Creates a copy of this context. + */ + DateTimeParseContext copy() { + return new DateTimeParseContext(formatter); + } + + //----------------------------------------------------------------------- + /** + * Gets the locale. + *

+ * This locale is used to control localization in the parse except + * where localization is controlled by the symbols. + * + * @return the locale, not null + */ + public Locale getLocale() { + return formatter.getLocale(); + } + + /** + * Gets the formatting symbols. + *

+ * The symbols control the localization of numeric parsing. + * + * @return the formatting symbols, not null + */ + public DateTimeFormatSymbols getSymbols() { + return formatter.getSymbols(); + } + + //----------------------------------------------------------------------- + /** + * Checks if parsing is case sensitive. + * + * @return true if parsing is case sensitive, false if case insensitive + */ + public boolean isCaseSensitive() { + return caseSensitive; + } + + /** + * Sets whether the parsing is case sensitive or not. + * + * @param caseSensitive changes the parsing to be case sensitive or not from now on + */ + public void setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + } + + /** + * Helper to compare two {@code CharSequence} instances. + * This uses {@link #isCaseSensitive()}. + * + * @param cs1 the first character sequence, not null + * @param offset1 the offset into the first sequence, valid + * @param cs2 the second character sequence, not null + * @param offset2 the offset into the second sequence, valid + * @param length the length to check, valid + * @return true if equal + */ + public boolean subSequenceEquals(CharSequence cs1, int offset1, CharSequence cs2, int offset2, int length) { + if (offset1 + length > cs1.length() || offset2 + length > cs2.length()) { + return false; + } + if (isCaseSensitive()) { + for (int i = 0; i < length; i++) { + char ch1 = cs1.charAt(offset1 + i); + char ch2 = cs2.charAt(offset2 + i); + if (ch1 != ch2) { + return false; + } + } + } else { + for (int i = 0; i < length; i++) { + char ch1 = cs1.charAt(offset1 + i); + char ch2 = cs2.charAt(offset2 + i); + if (ch1 != ch2 && Character.toUpperCase(ch1) != Character.toUpperCase(ch2) && + Character.toLowerCase(ch1) != Character.toLowerCase(ch2)) { + return false; + } + } + } + return true; + } + + //----------------------------------------------------------------------- + /** + * Checks if parsing is strict. + *

+ * Strict parsing requires exact matching of the text and sign styles. + * + * @return true if parsing is strict, false if lenient + */ + public boolean isStrict() { + return strict; + } + + /** + * Sets whether parsing is strict or lenient. + * + * @param strict changes the parsing to be strict or lenient from now on + */ + public void setStrict(boolean strict) { + this.strict = strict; + } + + //----------------------------------------------------------------------- + /** + * Starts the parsing of an optional segment of the input. + */ + void startOptional() { + parsed.add(currentParsed().copy()); + } + + /** + * Ends the parsing of an optional segment of the input. + * + * @param successful whether the optional segment was successfully parsed + */ + void endOptional(boolean successful) { + if (successful) { + parsed.remove(parsed.size() - 2); + } else { + parsed.remove(parsed.size() - 1); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the currently active temporal objects. + * + * @return the current temporal objects, not null + */ + private Parsed currentParsed() { + return parsed.get(parsed.size() - 1); + } + + //----------------------------------------------------------------------- + /** + * Gets the first value that was parsed for the specified field. + *

+ * This searches the results of the parse, returning the first value found + * for the specified field. No attempt is made to derive a value. + * The field may have an out of range value. + * For example, the day-of-month might be set to 50, or the hour to 1000. + * + * @param field the field to query from the map, null returns null + * @return the value mapped to the specified field, null if field was not parsed + */ + public Long getParsed(TemporalField field) { + for (Object obj : currentParsed().parsed) { + if (obj instanceof FieldValue) { + FieldValue fv = (FieldValue) obj; + if (fv.field.equals(field)) { + return fv.value; + } + } + } + return null; + } + + /** + * Gets the first value that was parsed for the specified type. + *

+ * This searches the results of the parse, returning the first date-time found + * of the specified type. No attempt is made to derive a value. + * + * @param clazz the type to query from the map, not null + * @return the temporal object, null if it was not parsed + */ + @SuppressWarnings("unchecked") + public T getParsed(Class clazz) { + for (Object obj : currentParsed().parsed) { + if (clazz.isInstance(obj)) { + return (T) obj; + } + } + return null; + } + + /** + * Gets the list of parsed temporal information. + * + * @return the list of parsed temporal objects, not null, no nulls + */ + List getParsed() { + // package scoped for testing + return currentParsed().parsed; + } + + /** + * Stores the parsed field. + *

+ * This stores a field-value pair that has been parsed. + * The value stored may be out of range for the field - no checks are performed. + * + * @param field the field to set in the field-value map, not null + * @param value the value to set in the field-value map + */ + public void setParsedField(TemporalField field, long value) { + Objects.requireNonNull(field, "field"); + currentParsed().parsed.add(new FieldValue(field, value)); + } + + /** + * Stores the parsed complete object. + *

+ * This stores a complete object that has been parsed. + * No validation is performed on the date-time other than ensuring it is not null. + * + * @param object the parsed object, not null + */ + public void setParsed(Object object) { + Objects.requireNonNull(object, "object"); + currentParsed().parsed.add(object); + } + + //----------------------------------------------------------------------- + /** + * Returns a {@code DateTimeBuilder} that can be used to interpret + * the results of the parse. + *

+ * This method is typically used once parsing is complete to obtain the parsed data. + * Parsing will typically result in separate fields, such as year, month and day. + * The returned builder can be used to combine the parsed data into meaningful + * objects such as {@code LocalDate}, potentially applying complex processing + * to handle invalid parsed data. + * + * @return a new builder with the results of the parse, not null + */ + public DateTimeBuilder toBuilder() { + List cals = currentParsed().parsed; + DateTimeBuilder builder = new DateTimeBuilder(); + for (Object obj : cals) { + if (obj instanceof FieldValue) { + FieldValue fv = (FieldValue) obj; + builder.addFieldValue(fv.field, fv.value); + } else { + builder.addCalendrical(obj); + } + } + return builder; + } + + //----------------------------------------------------------------------- + /** + * Returns a string version of the context for debugging. + * + * @return a string representation of the context data, not null + */ + @Override + public String toString() { + return currentParsed().toString(); + } + + //----------------------------------------------------------------------- + /** + * Temporary store of parsed data. + */ + private static final class Parsed { + final List parsed = new ArrayList<>(); + private Parsed() { + } + protected Parsed copy() { + Parsed cloned = new Parsed(); + cloned.parsed.addAll(this.parsed); + return cloned; + } + @Override + public String toString() { + return parsed.toString(); + } + } + + //----------------------------------------------------------------------- + /** + * Temporary store of a field-value pair. + */ + private static final class FieldValue { + final TemporalField field; + final long value; + private FieldValue(TemporalField field, long value) { + this.field = field; + this.value = value; + } + @Override + public String toString() { + return field.getName() + ' ' + value; + } + } + +} diff --git a/src/share/classes/java/time/format/DateTimeParseException.java b/src/share/classes/java/time/format/DateTimeParseException.java new file mode 100644 index 0000000000000000000000000000000000000000..37b72a2f92a76cb4a10e138c6964187cfa608b7c --- /dev/null +++ b/src/share/classes/java/time/format/DateTimeParseException.java @@ -0,0 +1,138 @@ +/* + * 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 java.time.format; + +import java.time.DateTimeException; + +/** + * An exception thrown when an error occurs during parsing. + *

+ * This exception includes the text being parsed and the error index. + * + *

Specification for implementors

+ * This class is intended for use in a single thread. + * + * @since 1.8 + */ +public class DateTimeParseException extends DateTimeException { + + /** + * Serialization version. + */ + private static final long serialVersionUID = 4304633501674722597L; + + /** + * The text that was being parsed. + */ + private final String parsedString; + /** + * The error index in the text. + */ + private final int errorIndex; + + /** + * Constructs a new exception with the specified message. + * + * @param message the message to use for this exception, may be null + * @param parsedData the parsed text, should not be null + * @param errorIndex the index in the parsed string that was invalid, should be a valid index + */ + public DateTimeParseException(String message, CharSequence parsedData, int errorIndex) { + super(message); + this.parsedString = parsedData.toString(); + this.errorIndex = errorIndex; + } + + /** + * Constructs a new exception with the specified message and cause. + * + * @param message the message to use for this exception, may be null + * @param parsedData the parsed text, should not be null + * @param errorIndex the index in the parsed string that was invalid, should be a valid index + * @param cause the cause exception, may be null + */ + public DateTimeParseException(String message, CharSequence parsedData, int errorIndex, Throwable cause) { + super(message, cause); + this.parsedString = parsedData.toString(); + this.errorIndex = errorIndex; + } + + //----------------------------------------------------------------------- + /** + * Returns the string that was being parsed. + * + * @return the string that was being parsed, should not be null. + */ + public String getParsedString() { + return parsedString; + } + + /** + * Returns the index where the error was found. + * + * @return the index in the parsed string that was invalid, should be a valid index + */ + public int getErrorIndex() { + return errorIndex; + } + +} diff --git a/src/share/classes/java/time/format/DateTimePrintContext.java b/src/share/classes/java/time/format/DateTimePrintContext.java new file mode 100644 index 0000000000000000000000000000000000000000..6314c0de7af9002049e99cea249305865ef381b2 --- /dev/null +++ b/src/share/classes/java/time/format/DateTimePrintContext.java @@ -0,0 +1,281 @@ +/* + * 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) 2011-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 java.time.format; + +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.INSTANT_SECONDS; + +import java.time.DateTimeException; +import java.time.Instant; +import java.time.ZoneId; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.Queries; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.ValueRange; +import java.util.Locale; +import java.util.Objects; + +/** + * Context object used during date and time printing. + *

+ * This class provides a single wrapper to items used in the print. + * + *

Specification for implementors

+ * This class is a mutable context intended for use from a single thread. + * Usage of the class is thread-safe within standard printing as the framework creates + * a new instance of the class for each print and printing is single-threaded. + * + * @since 1.8 + */ +final class DateTimePrintContext { + + /** + * The temporal being output. + */ + private TemporalAccessor temporal; + /** + * The formatter, not null. + */ + private DateTimeFormatter formatter; + /** + * Whether the current formatter is optional. + */ + private int optional; + + /** + * Creates a new instance of the context. + * + * @param temporal the temporal object being output, not null + * @param formatter the formatter controlling the print, not null + */ + DateTimePrintContext(TemporalAccessor temporal, DateTimeFormatter formatter) { + super(); + this.temporal = adjust(temporal, formatter); + this.formatter = formatter; + } + + private static TemporalAccessor adjust(final TemporalAccessor temporal, DateTimeFormatter formatter) { + // normal case first + Chrono overrideChrono = formatter.getChrono(); + ZoneId overrideZone = formatter.getZone(); + if (overrideChrono == null && overrideZone == null) { + return temporal; + } + + // ensure minimal change + Chrono temporalChrono = Chrono.from(temporal); // default to ISO, handles Instant + ZoneId temporalZone = temporal.query(Queries.zone()); // zone then offset, handles OffsetDateTime + if (temporal.isSupported(EPOCH_DAY) == false || Objects.equals(overrideChrono, temporalChrono)) { + overrideChrono = null; + } + if (temporal.isSupported(INSTANT_SECONDS) == false || Objects.equals(overrideZone, temporalZone)) { + overrideZone = null; + } + if (overrideChrono == null && overrideZone == null) { + return temporal; + } + + // make adjustment + if (overrideChrono != null && overrideZone != null) { + return overrideChrono.zonedDateTime(Instant.from(temporal), overrideZone); + } else if (overrideZone != null) { + return temporalChrono.zonedDateTime(Instant.from(temporal), overrideZone); + } else { // overrideChrono != null + // need class here to handle non-standard cases like OffsetDate + final ChronoLocalDate date = overrideChrono.date(temporal); + return new TemporalAccessor() { + @Override + public boolean isSupported(TemporalField field) { + return temporal.isSupported(field); + } + @Override + public ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + if (((ChronoField) field).isDateField()) { + return date.range(field); + } else { + return temporal.range(field); + } + } + return field.doRange(this); + } + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + if (((ChronoField) field).isDateField()) { + return date.getLong(field); + } else { + return temporal.getLong(field); + } + } + return field.doGet(this); + } + @Override + public R query(TemporalQuery query) { + if (query == Queries.zoneId() || query == Queries.chrono() || query == Queries.precision()) { + return temporal.query(query); + } + return query.queryFrom(this); + } + }; + } + } + + //----------------------------------------------------------------------- + /** + * Gets the temporal object being output. + * + * @return the temporal object, not null + */ + TemporalAccessor getTemporal() { + return temporal; + } + + /** + * Gets the locale. + *

+ * This locale is used to control localization in the print output except + * where localization is controlled by the symbols. + * + * @return the locale, not null + */ + Locale getLocale() { + return formatter.getLocale(); + } + + /** + * Gets the formatting symbols. + *

+ * The symbols control the localization of numeric output. + * + * @return the formatting symbols, not null + */ + DateTimeFormatSymbols getSymbols() { + return formatter.getSymbols(); + } + + //----------------------------------------------------------------------- + /** + * Starts the printing of an optional segment of the input. + */ + void startOptional() { + this.optional++; + } + + /** + * Ends the printing of an optional segment of the input. + */ + void endOptional() { + this.optional--; + } + + /** + * Gets a value using a query. + * + * @param query the query to use, not null + * @return the result, null if not found and optional is true + * @throws DateTimeException if the type is not available and the section is not optional + */ + R getValue(TemporalQuery query) { + R result = temporal.query(query); + if (result == null && optional == 0) { + throw new DateTimeException("Unable to extract value: " + temporal.getClass()); + } + return result; + } + + /** + * Gets the value of the specified field. + *

+ * This will return the value for the specified field. + * + * @param field the field to find, not null + * @return the value, null if not found and optional is true + * @throws DateTimeException if the field is not available and the section is not optional + */ + Long getValue(TemporalField field) { + try { + return temporal.getLong(field); + } catch (DateTimeException ex) { + if (optional > 0) { + return null; + } + throw ex; + } + } + + //----------------------------------------------------------------------- + /** + * Returns a string version of the context for debugging. + * + * @return a string representation of the context, not null + */ + @Override + public String toString() { + return temporal.toString(); + } + +} diff --git a/src/share/classes/java/time/format/DateTimePrintException.java b/src/share/classes/java/time/format/DateTimePrintException.java new file mode 100644 index 0000000000000000000000000000000000000000..e03a3e51e8cd3c8849f9f57f9f75908559210156 --- /dev/null +++ b/src/share/classes/java/time/format/DateTimePrintException.java @@ -0,0 +1,129 @@ +/* + * 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 java.time.format; + +import java.io.IOException; +import java.time.DateTimeException; + +/** + * An exception thrown when an error occurs during printing. + *

+ * This will be triggered by violations specific to printing or an IO exception. + * + *

Specification for implementors

+ * This class is intended for use in a single thread. + * + * @since 1.8 + */ +public class DateTimePrintException extends DateTimeException { + + /** + * Serialization version. + */ + private static final long serialVersionUID = 2263939197574006408L; + + /** + * Constructs a new exception with the specified message. + * + * @param message the message to use for this exception, may be null + */ + public DateTimePrintException(String message) { + super(message, null); + } + + /** + * Constructs a new 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 DateTimePrintException(String message, Throwable cause) { + super(message, cause); + } + + //----------------------------------------------------------------------- + /** + * Checks if the cause of this exception was an IOException, and if so + * re-throws it + *

+ * This method is useful if you call a printer with an open stream or + * writer and want to ensure that IOExceptions are not lost. + *

+     * try {
+     *   printer.print(writer, dateTime);
+     * } catch (DateTimePrintException ex) {
+     *   ex.rethrowIOException();
+     *   // if code reaches here exception was caused by date-time issues
+     * }
+     * 
+ * Note that calling this method will re-throw the original IOException, + * causing this DateTimePrintException to be lost. + * + * @throws IOException if the cause of this exception is an IOException + */ + public void rethrowIOException() throws IOException { + if (getCause() instanceof IOException) { + throw (IOException) getCause(); + } + } + +} diff --git a/src/share/classes/java/time/format/DateTimeTextProvider.java b/src/share/classes/java/time/format/DateTimeTextProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..042b6136a5a4ee2d0edeb0f5ffe12186ce381dec --- /dev/null +++ b/src/share/classes/java/time/format/DateTimeTextProvider.java @@ -0,0 +1,380 @@ +/* + * 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) 2011-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 java.time.format; + +import static java.time.temporal.ChronoField.AMPM_OF_DAY; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; + +import java.time.temporal.Chrono; +import java.time.temporal.ISOChrono; +import java.time.calendar.JapaneseChrono; +import java.time.calendar.ThaiBuddhistChrono; +import java.time.temporal.TemporalField; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.spi.CalendarNameProvider; + +import sun.util.locale.provider.LocaleProviderAdapter; +import sun.util.locale.provider.CalendarDataUtility; + +/** + * A provider to obtain the textual form of a date-time field. + * + *

Specification for implementors

+ * Implementations must be thread-safe. + * Implementations should cache the textual information. + * + * @since 1.8 + */ +class DateTimeTextProvider { + + /** Cache. */ + private static final ConcurrentMap, Object> CACHE = new ConcurrentHashMap<>(16, 0.75f, 2); + /** Comparator. */ + private static final Comparator> COMPARATOR = new Comparator>() { + @Override + public int compare(Entry obj1, Entry obj2) { + return obj2.getKey().length() - obj1.getKey().length(); // longest to shortest + } + }; + + DateTimeTextProvider() {} + + /** + * Gets the provider of text. + * + * @return the provider, not null + */ + static DateTimeTextProvider getInstance() { + return new DateTimeTextProvider(); + } + + /** + * Gets the text for the specified field, locale and style + * for the purpose of printing. + *

+ * The text associated with the value is returned. + * The null return value should be used if there is no applicable text, or + * if the text would be a numeric representation of the value. + * + * @param field the field to get text for, not null + * @param value the field value to get text for, not null + * @param style the style to get text for, not null + * @param locale the locale to get text for, not null + * @return the text for the field value, null if no text found + */ + public String getText(TemporalField field, long value, TextStyle style, Locale locale) { + Object store = findStore(field, locale); + if (store instanceof LocaleStore) { + return ((LocaleStore) store).getText(value, style); + } + return null; + } + + private static int toStyle(TextStyle style) { + if (style == TextStyle.FULL) { + return Calendar.LONG_FORMAT; + } else if (style == TextStyle.SHORT) { + return Calendar.SHORT_FORMAT; + } + return Calendar.NARROW_STANDALONE; + } + + /** + * Gets the era text for the specified chrono, value, style and locale + * for the purpose of printing. + *

+ * The era text associated with the value is returned. + * The null return value should be used if there is no applicable text, or + * if the text would be a numeric representation of the value. + * + * @param chrono the chrono to get text for, not null + * @param value the field value to get text for, not null + * @param style the style to get text for, not null + * @param locale the locale to get text for, not null + * @return the era text for the value, null if no text found + */ + public String getEraText(Chrono chrono, long value, TextStyle style, Locale locale) { + String type = null; + if (chrono == ISOChrono.INSTANCE) { + type = "gregory"; + } else if (chrono == JapaneseChrono.INSTANCE) { + type = "japanese"; + if (value == -999) { + value = 0; + } else { + value += 2; + } + } else if (chrono == ThaiBuddhistChrono.INSTANCE) { + type = "buddhist"; + } else { + return null; + } + return CalendarDataUtility.retrieveFieldValueName( + type, Calendar.ERA, (int)value, toStyle(style), locale); + } + + /** + * Gets an iterator of text to field for the specified field, locale and style + * for the purpose of parsing. + *

+ * The iterator must be returned in order from the longest text to the shortest. + *

+ * The null return value should be used if there is no applicable parsable text, or + * if the text would be a numeric representation of the value. + * Text can only be parsed if all the values for that field-style-locale combination are unique. + * + * @param field the field to get text for, not null + * @param style the style to get text for, null for all parsable text + * @param locale the locale to get text for, not null + * @return the iterator of text to field pairs, in order from longest text to shortest text, + * null if the field or style is not parsable + */ + public Iterator> getTextIterator(TemporalField field, TextStyle style, Locale locale) { + Object store = findStore(field, locale); + if (store instanceof LocaleStore) { + return ((LocaleStore) store).getTextIterator(style); + } + return null; + } + + private Object findStore(TemporalField field, Locale locale) { + Entry key = createEntry(field, locale); + Object store = CACHE.get(key); + if (store == null) { + store = createStore(field, locale); + CACHE.putIfAbsent(key, store); + store = CACHE.get(key); + } + return store; + } + + private static int toWeekDay(int calWeekDay) { + if (calWeekDay == Calendar.SUNDAY) { + return 7; + } else { + return calWeekDay - 1; + } + } + + private Object createStore(TemporalField field, Locale locale) { + CalendarNameProvider provider = LocaleProviderAdapter.getAdapter(CalendarNameProvider.class, locale) + .getCalendarNameProvider(); + Map> styleMap = new HashMap<>(); + if (field == MONTH_OF_YEAR) { + Map map = new HashMap<>(); + for (Entry entry : + provider.getDisplayNames("gregory", Calendar.MONTH, Calendar.LONG_FORMAT, locale).entrySet()) { + map.put((long)(entry.getValue() + 1), entry.getKey()); + } + styleMap.put(TextStyle.FULL, map); + + map = new HashMap<>(); + for (Entry entry : + provider.getDisplayNames("gregory", Calendar.MONTH, Calendar.SHORT_FORMAT, locale).entrySet()) { + map.put((long)(entry.getValue() + 1), entry.getKey()); + } + styleMap.put(TextStyle.SHORT, map); + + map = new HashMap<>(); + for (int month = Calendar.JANUARY; month <= Calendar.DECEMBER; month++) { + String name = provider.getDisplayName("gregory", Calendar.MONTH, month, Calendar.NARROW_STANDALONE, locale); + if (name != null) { + map.put((long)(month + 1), name); + } + } + if (map.size() != 0) { + styleMap.put(TextStyle.NARROW, map); + } + return new LocaleStore(styleMap); + } + if (field == DAY_OF_WEEK) { + Map map = new HashMap<>(); + for (Entry entry : + provider.getDisplayNames("gregory", Calendar.DAY_OF_WEEK, Calendar.LONG_FORMAT, locale).entrySet()) { + map.put((long)toWeekDay(entry.getValue()), entry.getKey()); + } + styleMap.put(TextStyle.FULL, map); + map = new HashMap<>(); + for (Entry entry : + provider.getDisplayNames("gregory", Calendar.DAY_OF_WEEK, Calendar.SHORT_FORMAT, locale).entrySet()) { + map.put((long)toWeekDay(entry.getValue()), entry.getKey()); + } + styleMap.put(TextStyle.SHORT, map); + map = new HashMap<>(); + for (int wday = Calendar.SUNDAY; wday <= Calendar.SATURDAY; wday++) { + map.put((long)toWeekDay(wday), + provider.getDisplayName("gregory", Calendar.DAY_OF_WEEK, wday, Calendar.NARROW_FORMAT, locale)); + } + styleMap.put(TextStyle.NARROW, map); + return new LocaleStore(styleMap); + } + if (field == AMPM_OF_DAY) { + Map map = new HashMap<>(); + for (Entry entry : + provider.getDisplayNames("gregory", Calendar.AM_PM, Calendar.LONG_FORMAT, locale).entrySet()) { + map.put((long)entry.getValue(), entry.getKey()); + } + styleMap.put(TextStyle.FULL, map); + styleMap.put(TextStyle.SHORT, map); // re-use, as we don't have different data + return new LocaleStore(styleMap); + } + return ""; // null marker for map + } + + /** + * Helper method to create an immutable entry. + * + * @param text the text, not null + * @param field the field, not null + * @return the entry, not null + */ + private static Entry createEntry(A text, B field) { + return new SimpleImmutableEntry<>(text, field); + } + + /** + * Stores the text for a single locale. + *

+ * Some fields have a textual representation, such as day-of-week or month-of-year. + * These textual representations can be captured in this class for printing + * and parsing. + *

+ * This class is immutable and thread-safe. + */ + static final class LocaleStore { + /** + * Map of value to text. + */ + private final Map> valueTextMap; + /** + * Parsable data. + */ + private final Map>> parsable; + + /** + * Constructor. + * + * @param valueTextMap the map of values to text to store, assigned and not altered, not null + */ + LocaleStore(Map> valueTextMap) { + this.valueTextMap = valueTextMap; + Map>> map = new HashMap<>(); + List> allList = new ArrayList<>(); + for (TextStyle style : valueTextMap.keySet()) { + Map> reverse = new HashMap<>(); + for (Map.Entry entry : valueTextMap.get(style).entrySet()) { + if (reverse.put(entry.getValue(), createEntry(entry.getValue(), entry.getKey())) != null) { + // TODO: BUG: this has no effect + continue; // not parsable, try next style + } + } + List> list = new ArrayList<>(reverse.values()); + Collections.sort(list, COMPARATOR); + map.put(style, list); + allList.addAll(list); + map.put(null, allList); + } + Collections.sort(allList, COMPARATOR); + this.parsable = map; + } + + /** + * Gets the text for the specified field value, locale and style + * for the purpose of printing. + * + * @param value the value to get text for, not null + * @param style the style to get text for, not null + * @return the text for the field value, null if no text found + */ + String getText(long value, TextStyle style) { + Map map = valueTextMap.get(style); + return map != null ? map.get(value) : null; + } + + /** + * Gets an iterator of text to field for the specified style for the purpose of parsing. + *

+ * The iterator must be returned in order from the longest text to the shortest. + * + * @param style the style to get text for, null for all parsable text + * @return the iterator of text to field pairs, in order from longest text to shortest text, + * null if the style is not parsable + */ + Iterator> getTextIterator(TextStyle style) { + List> list = parsable.get(style); + return list != null ? list.iterator() : null; + } + } +} diff --git a/src/share/classes/java/time/format/FormatStyle.java b/src/share/classes/java/time/format/FormatStyle.java new file mode 100644 index 0000000000000000000000000000000000000000..58cebe48cbb62ae5ce78f2f8befbad9596d59896 --- /dev/null +++ b/src/share/classes/java/time/format/FormatStyle.java @@ -0,0 +1,99 @@ +/* + * 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 java.time.format; + +/** + * Enumeration of the style of a localized date, time or date-time formatter. + *

+ * These styles are used when obtaining a date-time style from configuration. + * See {@link DateTimeFormatters} and {@link DateTimeFormatterBuilder} for usage. + * + *

Specification for implementors

+ * This is an immutable and thread-safe enum. + * + * @since 1.8 + */ +public enum FormatStyle { + // ordered from large to small + + /** + * Full text style, with the most detail. + * For example, the format might be 'Tuesday, April 12, 1952 AD' or '3:30:42pm PST'. + */ + FULL, + /** + * Long text style, with lots of detail. + * For example, the format might be 'January 12, 1952'. + */ + LONG, + /** + * Medium text style, with some detail. + * For example, the format might be 'Jan 12, 1952'. + */ + MEDIUM, + /** + * Short text style, typically numeric. + * For example, the format might be '12.13.52' or '3:30pm'. + */ + SHORT; + +} diff --git a/src/share/classes/java/time/format/SignStyle.java b/src/share/classes/java/time/format/SignStyle.java new file mode 100644 index 0000000000000000000000000000000000000000..2f8c57aecb39844a867dc7393d6f489ed37b3c54 --- /dev/null +++ b/src/share/classes/java/time/format/SignStyle.java @@ -0,0 +1,139 @@ +/* + * 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 java.time.format; + +/** + * Enumeration of ways to handle the positive/negative sign. + *

+ * The formatting engine allows the positive and negative signs of numbers + * to be controlled using this enum. + * See {@link DateTimeFormatterBuilder} for usage. + * + *

Specification for implementors

+ * This is an immutable and thread-safe enum. + * + * @since 1.8 + */ +public enum SignStyle { + + /** + * Style to output the sign only if the value is negative. + *

+ * In strict parsing, the negative sign will be accepted and the positive sign rejected. + * In lenient parsing, any sign will be accepted. + */ + NORMAL, + /** + * Style to always output the sign, where zero will output '+'. + *

+ * In strict parsing, the absence of a sign will be rejected. + * In lenient parsing, any sign will be accepted, with the absence + * of a sign treated as a positive number. + */ + ALWAYS, + /** + * Style to never output sign, only outputting the absolute value. + *

+ * In strict parsing, any sign will be rejected. + * In lenient parsing, any sign will be accepted unless the width is fixed. + */ + NEVER, + /** + * Style to block negative values, throwing an exception on printing. + *

+ * In strict parsing, any sign will be rejected. + * In lenient parsing, any sign will be accepted unless the width is fixed. + */ + NOT_NEGATIVE, + /** + * Style to always output the sign if the value exceeds the pad width. + * A negative value will always output the '-' sign. + *

+ * In strict parsing, the sign will be rejected unless the pad width is exceeded. + * In lenient parsing, any sign will be accepted, with the absence + * of a sign treated as a positive number. + */ + EXCEEDS_PAD; + + /** + * Parse helper. + * + * @param positive true if positive sign parsed, false for negative sign + * @param strict true if strict, false if lenient + * @param fixedWidth true if fixed width, false if not + * @return + */ + boolean parse(boolean positive, boolean strict, boolean fixedWidth) { + switch (ordinal()) { + case 0: // NORMAL + // valid if negative or (positive and lenient) + return !positive || !strict; + case 1: // ALWAYS + case 4: // EXCEEDS_PAD + return true; + default: + // valid if lenient and not fixed width + return !strict && !fixedWidth; + } + } + +} diff --git a/src/share/classes/java/time/format/TextStyle.java b/src/share/classes/java/time/format/TextStyle.java new file mode 100644 index 0000000000000000000000000000000000000000..3f8c0875afe2fb431109ed29b97719c7b0ca2463 --- /dev/null +++ b/src/share/classes/java/time/format/TextStyle.java @@ -0,0 +1,93 @@ +/* + * 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 java.time.format; + +/** + * Enumeration of the style of text output to use. + *

+ * This defines the "size" of the text to be output. + * + *

Specification for implementors

+ * This is immutable and thread-safe enum. + * + * @since 1.8 + */ +public enum TextStyle { + // ordered from large to small + + /** + * Full text, typically the full description. + * For example, day-of-week Monday might output "Monday". + */ + FULL, + /** + * Short text, typically an abbreviation. + * For example, day-of-week Monday might output "Mon". + */ + SHORT, + /** + * Narrow text, typically a single letter. + * For example, day-of-week Monday might output "M". + */ + NARROW; + +} diff --git a/src/share/classes/java/time/format/package-info.java b/src/share/classes/java/time/format/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..9f96532f667126293f23ee88e6fb0d8a87390098 --- /dev/null +++ b/src/share/classes/java/time/format/package-info.java @@ -0,0 +1,95 @@ +/* + * 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) 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. + */ + +/** + *

+ * Provides classes to print and parse dates and times. + *

+ *

+ * Printing and parsing is based around the + * {@link java.time.format.DateTimeFormatter DateTimeFormatter} class. + * Instances are generally obtained from + * {@link java.time.format.DateTimeFormatters DateTimeFormatters}, however + * {@link java.time.format.DateTimeFormatterBuilder DateTimeFormatterBuilder} + * can be used if more power is needed. + *

+ *

+ * Localization occurs by calling + * {@link java.time.format.DateTimeFormatter#withLocale(java.util.Locale) withLocale(Locale)} + * on the formatter. Further customization is possible using + * {@link java.time.format.DateTimeFormatSymbols DateTimeFormatSymbols}. + *

+ * + *

Package specification

+ *

+ * Unless otherwise noted, passing a null argument to a constructor or method in any class or interface + * in this package will cause a {@link java.lang.NullPointerException NullPointerException} to be thrown. + * The Javadoc "@param" definition is used to summarise the null-behavior. + * The "@throws {@link java.lang.NullPointerException}" is not explicitly documented in each method. + *

+ *

+ * All calculations should check for numeric overflow and throw either an {@link java.lang.ArithmeticException} + * or a {@link java.time.DateTimeException}. + *

+ * @since JDK1.8 + */ +package java.time.format; diff --git a/src/share/classes/java/time/overview.html b/src/share/classes/java/time/overview.html new file mode 100644 index 0000000000000000000000000000000000000000..c522c4b4708e58a81dcfef3ff989e4d8066f62b0 --- /dev/null +++ b/src/share/classes/java/time/overview.html @@ -0,0 +1,174 @@ + + +

+ A new Date and Time API for Java. + The design includes a relatively large number of classes and methods, + however each follows a common design language, especially in method prefixes. + Once the prefixes are understood, the API is relatively simple to comprehend. +

+

+ The Java Time API is composed of several packages, each with a primary function: +

+

+ {@link java.time} contains the main API based on the ISO-8601 standard. + The classes defined here represent the principal date-time concepts, + including instants, durations, dates, times, time-zones and periods. + They are based on the ISO calendar system, which is the de facto world + calendar following the proleptic Gregorian rules. + All the classes are immutable and thread-safe. +

+

+ {@link java.time.temporal} contains the API for accessing the fields and units + of date-time. Units are measurable, such as years, months and hours. + For example, the expression "2 hours later" uses the hours unit. + By contrast, fields are mini-calculations, defining a value. + For example, month-of-year, day-of-week and hour-of-day are all fields. + The set of supported units and fields can be extended by applications if desired. +

+

+ It also contains the basic part of the calendar neutral API. + This is intended for use by applications that need to use localized calendars. + Ensure that you read the class documentation of {@link java.time.temporal.ChronoLocalDate} + before using non-ISO calendar systems. +

+

+ {@link java.time.format} contains the API to print and parse fields into date-time + objects and to customize parsing and printing. + Formatters can be created in a variety of ways, including constants, patterns, + localized styles and a builder. + Formatters are immutable and thread-safe. +

+

+ {@link java.time.zone} contains the API to handle time-zones. + Detailed information is made available about the rules of each time-zone. +

+

+ The {@link java.time.calendar} package contains alternate calendar systems. + This is intended for use by applications that need to use localized calendars. + Support is provided for the Hijrah, Japanese, Minguo, and Thai Buddhist Calendars. +

+

Design notes

+

+ Where possible, the API avoids the use of null. + All methods define whether they accept or return null in the Javadoc. + As a general rule, methods do not accept or return null. + A key exception is any method that takes an object and returns a boolean, for the purpose + of checking or validating, will generally return false for null. +

+

+ The API is designed to be type-safe where reasonable in the main high-level API. + Thus, there are separate classes for the distinct concepts of date, time and date-time, plus variants + for offset and time-zones. The core 7 date-time classes, plus Instant, handle the needs of most applications. + Further classes handle other combinations - year, year-month and month-day in a type-safe manner. +

+

+ In a language like Java, the use of many different types tends to cause API bloat. + This is handled here through the use of common method naming patterns throughout the API. + The common prefixes are 'of', 'get', 'is', 'with', 'plus', 'minus', 'to' and 'at'. + See {@link java.time.LocalDate} for an example of each of these methods. +

+

+ Following type-safety to its logical conclusion would result in more classes, especially for time - + hour-minute, hour-minute-second and hour-minute-second-nanosecond. + While logically pure, this was not possible in practice, as the additional classes would have + excessively complicated the API. Notably, there would be additional combinations at the offset + and date-time levels, such as offset-date-hour-minute. + To avoid this explosion of types, {@link java.time.LocalTime} is used for all precisions of time. + By contrast, some additional classes were used for dates, such as {@link java.time.temporal.YearMonth}. + This proved necessary, as the API for year-month is significantly different to that for a date, whereas + an absence of nanoseconds in a time can be approximated by returning zero. +

+

+ Similarly, full type-safety might argue for a separate class for each field in date-time, + such as a class for HourOfDay and another for DayOfMonth. + This approach was tried, but was excessively complicated in the Java language, lacking usability. + A similar problem occurs with periods. + There is a case for a separate class for each period unit, such as a type for Years and a type for Minutes. + However, this yields a lot of classes and a problem of type conversion. + As such, general access to fields and units is not wrapped in a class. +

+

+ Multiple calendar systems is an awkward addition to the design challenges. + The first principal is that most users want the standard ISO calendar system. + As such, the main classes are ISO-only. The second principal is that most of those that want a + non-ISO calendar system want it for user interaction, thus it is a UI localization issue. + As such, date and time objects should be held as ISO objects in the data model and persistent + storage, only being converted to and from a local calendar for display. + The calendar system would be stored separately in the user preferences. +

+

+ There are, however, some limited use cases where users believe they need to store and use + dates in arbitrary calendar systems throughout the application. + This is supported by {@link java.time.temporal.ChronoLocalDate}, however it is vital to read + all the associated warnings in the Javadoc of that interface before using it. + In summary, applications that require general interoperation between multiple calendar systems + typically need to be written in a very different way to those only using the ISO calendar, + thus most applications should just use ISO and avoid {@code ChronoLocalDate}. +

+

+ Throughout all of this, a key goal was to allow date-time fields and units to be defined by applications. + This has been achieved having tried many different designs. +

+ diff --git a/src/share/classes/java/time/package-info.java b/src/share/classes/java/time/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..9d06ff9352cf05eb46ca7b75f19cead8cae0a24b --- /dev/null +++ b/src/share/classes/java/time/package-info.java @@ -0,0 +1,291 @@ +/* + * 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) 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. + */ + +/** + *

+ * The main API for dates, times, instants, and durations. + *

+ *

+ * The classes defined here represent the principal date-time concepts, + * including instants, durations, dates, times, time-zones and periods. + * They are based on the ISO calendar system, which is the de facto world + * calendar following the proleptic Gregorian rules. + * All the classes are immutable and thread-safe. + *

+ *

+ * Each date time instance is composed of fields that are conveniently + * made available by the APIs. For lower level access to the fields refer + * to the {@code java.time.temporal} package. + * Each class includes support for printing and parsing all manner of dates and times. + * Refer to the {@code java.time.format} package for customization options. + *

+ *

+ * The {@code java.time.temporal} package also contains the calendar neutral API + * {@link java.time.temporal.ChronoLocalDate ChronoLocalDate}, + * {@link java.time.temporal.ChronoLocalDateTime ChronoLocalDateTime}, + * {@link java.time.temporal.ChronoZonedDateTime ChronoZonedDateTime} and + * {@link java.time.temporal.Era Era}. + * This is intended for use by applications that need to use localized calendars. + * It is recommended that applications use the ISO-8601 dates and time classes from + * this package across system boundaries, such as to the database or across the network. + * The calendar neutral API should be reserved for interactions with users. + *

+ * + *

Dates and Times

+ *

+ * {@link java.time.Instant} is essentially a numeric timestamp. + * The current Instant can be retrieved from a {@link java.time.Clock}. + * This is useful for logging and persistence of a point in time + * and has in the past been associated with storing the result + * from {@link java.lang.System#currentTimeMillis()}. + *

+ *

+ * {@link java.time.LocalDate} stores a date without a time. + * This stores a date like '2010-12-03' and could be used to store a birthday. + *

+ *

+ * {@link java.time.LocalTime} stores a time without a date. + * This stores a time like '11:30' and could be used to store an opening or closing time. + *

+ *

+ * {@link java.time.LocalDateTime} stores a date and time. + * This stores a date-time like '2010-12-03T11:30'. + *

+ *

+ * {@link java.time.ZonedDateTime} stores a date and time with a time-zone. + * This is useful if you want to perform accurate calculations of + * dates and times taking into account the {@link java.time.ZoneId}, such as 'Europe/Paris'. + * Where possible, it is recommended to use a simpler class without a time-zone. + * The widespread use of time-zones tends to add considerable complexity to an application. + *

+ * + *

Duration and Period

+ *

+ * Beyond dates and times, the API also allows the storage of period and durations of time. + * A {@link java.time.Duration} is a simple measure of time along the time-line in nanoseconds. + * A {@link java.time.Period} expresses an amount of time in units meaningful to humans, such as years or hours. + *

+ * + *

Additional value types

+ *

+ * {@link java.time.Month} stores a month on its own. + * This stores a single month-of-year in isolation, such as 'DECEMBER'. + *

+ *

+ * {@link java.time.DayOfWeek} stores a day-of-week on its own. + * This stores a single day-of-week in isolation, such as 'TUESDAY'. + *

+ *

+ * {@link java.time.temporal.Year} stores a year on its own. + * This stores a single year in isolation, such as '2010'. + *

+ *

+ * {@link java.time.temporal.YearMonth} stores a year and month without a day or time. + * This stores a year and month, such as '2010-12' and could be used for a credit card expiry. + *

+ *

+ * {@link java.time.temporal.MonthDay} stores a month and day without a year or time. + * This stores a month and day-of-month, such as '--12-03' and + * could be used to store an annual event like a birthday without storing the year. + *

+ *

+ * {@link java.time.temporal.OffsetTime} stores a time and offset from UTC without a date. + * This stores a date like '11:30+01:00'. + * The {@link java.time.ZoneOffset ZoneOffset} is of the form '+01:00'. + *

+ *

+ * {@link java.time.temporal.OffsetDate} stores a date and offset from UTC without a time. + * This stores a time like '2010-12-03+01:00'. + *

+ *

+ * {@link java.time.temporal.OffsetDateTime} stores a date and time and offset from UTC. + * This stores a date-time like '2010-12-03T11:30+01:00'. + * This is sometimes found in XML messages and other forms of persistence, + * but contains less information than a full time-zone. + *

+ * + *

Package specification

+ *

+ * Unless otherwise noted, passing a null argument to a constructor or method in any class or interface + * in this package will cause a {@link java.lang.NullPointerException NullPointerException} to be thrown. + * The Javadoc "@param" definition is used to summarise the null-behavior. + * The "@throws {@link java.lang.NullPointerException}" is not explicitly documented in each method. + *

+ *

+ * All calculations should check for numeric overflow and throw either an {@link java.lang.ArithmeticException} + * or a {@link java.time.DateTimeException}. + *

+ * + *

Design notes (non normative)

+ *

+ * The API has been designed to reject null early and to be clear about this behavior. + * A key exception is any method that takes an object and returns a boolean, for the purpose + * of checking or validating, will generally return false for null. + *

+ *

+ * The API is designed to be type-safe where reasonable in the main high-level API. + * Thus, there are separate classes for the distinct concepts of date, time and date-time, + * plus variants for offset and time-zone. + * This can seem like a lot of classes, but most applications can begin with just five date/time types. + *

    + *
  • {@link java.time.Instant} - a timestamp
  • + *
  • {@link java.time.LocalDate} - a date without a time, or any reference to an offset or time-zone
  • + *
  • {@link java.time.LocalTime} - a time without a date, or any reference to an offset or time-zone
  • + *
  • {@link java.time.LocalDateTime} - combines date and time, but still without any offset or time-zone
  • + *
  • {@link java.time.ZonedDateTime} - a "full" date-time with time-zone and resolved offset from UTC/Greenwich
  • + *
+ *

+ * {@code Instant} is the closest equivalent class to {@code java.util.Date}. + * {@code ZonedDateTime} is the closest equivalent class to {@code java.util.GregorianCalendar}. + *

+ *

+ * Where possible, applications should use {@code LocalDate}, {@code LocalTime} and {@code LocalDateTime} + * to better model the domain. For example, a birthday should be stored in a code {@code LocalDate}. + * Bear in mind that any use of a {@linkplain java.time.ZoneId time-zone}, such as 'Europe/Paris', adds + * considerable complexity to a calculation. + * Many applications can be written only using {@code LocalDate}, {@code LocalTime} and {@code Instant}, + * with the time-zone added at the user interface (UI) layer. + *

+ *

+ * The offset-based date-time types, {@code OffsetDate}, {@code OffsetTime} and {@code OffsetDateTime}, + * are intended primarily for use with network protocols and database access. + * For example, most databases cannot automatically store a time-zone like 'Europe/Paris', but + * they can store an offset like '+02:00'. + *

+ *

+ * Classes are also provided for the most important sub-parts of a date, including {@code Month}, + * {@code DayOfWeek}, {@code Year}, {@code YearMonth} and {@code MonthDay}. + * These can be used to model more complex date-time concepts. + * For example, {@code YearMonth} is useful for representing a credit card expiry. + *

+ *

+ * Note that while there are a large number of classes representing different aspects of dates, + * there are relatively few dealing with different aspects of time. + * Following type-safety to its logical conclusion would have resulted in classes for + * hour-minute, hour-minute-second and hour-minute-second-nanosecond. + * While logically pure, this was not a practical option as it would have almost tripled the + * number of classes due to the combinations of date and time. + * Thus, {@code LocalTime} is used for all precisions of time, with zeroes used to imply lower precision. + *

+ *

+ * Following full type-safety to its ultimate conclusion might also argue for a separate class + * for each field in date-time, such as a class for HourOfDay and another for DayOfMonth. + * This approach was tried, but was excessively complicated in the Java language, lacking usability. + * A similar problem occurs with periods. + * There is a case for a separate class for each period unit, such as a type for Years and a type for Minutes. + * However, this yields a lot of classes and a problem of type conversion. + * Thus, the set of date-time types provided is a compromise between purity and practicality. + *

+ *

+ * The API has a relatively large surface area in terms of number of methods. + * This is made manageable through the use of consistent method prefixes. + *

    + *
  • {@code of} - static factory method
  • + *
  • {@code parse} - static factory method focussed on parsing
  • + *
  • {@code get} - gets the value of something
  • + *
  • {@code is} - checks if something is true
  • + *
  • {@code with} - the immutable equivalent of a setter
  • + *
  • {@code plus} - adds an amount to an object
  • + *
  • {@code minus} - subtracts an amount from an object
  • + *
  • {@code to} - converts this object to another type
  • + *
  • {@code at} - combines this object with another, such as {@code date.atTime(time)}
  • + *
+ *

+ * Multiple calendar systems is an awkward addition to the design challenges. + * The first principal is that most users want the standard ISO calendar system. + * As such, the main classes are ISO-only. The second principal is that most of those that want a + * non-ISO calendar system want it for user interaction, thus it is a UI localization issue. + * As such, date and time objects should be held as ISO objects in the data model and persistent + * storage, only being converted to and from a local calendar for display. + * The calendar system would be stored separately in the user preferences. + *

+ *

+ * There are, however, some limited use cases where users believe they need to store and use + * dates in arbitrary calendar systems throughout the application. + * This is supported by {@link java.time.temporal.ChronoLocalDate}, however it is vital to read + * all the associated warnings in the Javadoc of that interface before using it. + * In summary, applications that require general interoperation between multiple calendar systems + * typically need to be written in a very different way to those only using the ISO calendar, + * thus most applications should just use ISO and avoid {@code ChronoLocalDate}. + *

+ *

+ * The API is also designed for user extensibility, as there are many ways of calculating time. + * The {@linkplain java.time.temporal.TemporalField field} and {@linkplain java.time.temporal.TemporalUnit unit} + * API, accessed via {@link java.time.temporal.TemporalAccessor TemporalAccessor} and + * {@link java.time.temporal.Temporal Temporal} provide considerable flexibility to applications. + * In addition, the {@link java.time.temporal.TemporalQuery TemporalQuery} and + * {@link java.time.temporal.TemporalAdjuster TemporalAdjuster} interfaces provide day-to-day + * power, allowing code to read close to business requirements: + *

+ *
+ *   LocalDate customerBirthday = customer.loadBirthdayFromDatabase();
+ *   LocalDate today = LocalDate.now();
+ *   if (customerBirthday.equals(today)) {
+ *     LocalDate specialOfferExpiryDate = today.plusWeeks(2).with(next(FRIDAY));
+ *     customer.sendBirthdaySpecialOffer(specialOfferExpiryDate);
+ *   }
+ *
+ * 
+ * + * @since JDK1.8 + */ +package java.time; diff --git a/src/share/classes/java/time/temporal/Adjusters.java b/src/share/classes/java/time/temporal/Adjusters.java new file mode 100644 index 0000000000000000000000000000000000000000..45ccfd1a9f5c7485bd44d9d3e3c8d0023f82810e --- /dev/null +++ b/src/share/classes/java/time/temporal/Adjusters.java @@ -0,0 +1,502 @@ +/* + * 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 java.time.temporal; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.YEARS; + +import java.time.DayOfWeek; +import java.util.Objects; + +/** + * Common implementations of {@code TemporalAdjuster}. + *

+ * This class provides common implementations of {@link TemporalAdjuster}. + * They are especially useful to document the intent of business logic and + * often link well to requirements. + * For example, these two pieces of code do the same thing, but the second + * one is clearer (assuming that there is a static import of this class): + *

+ *  // direct manipulation
+ *  date.withDayOfMonth(1).plusMonths(1).minusDays(1);
+ *  // use of an adjuster from this class
+ *  date.with(lastDayOfMonth());
+ * 
+ * There are two equivalent ways of using a {@code TemporalAdjuster}. + * The first is to invoke the method on the interface directly. + * The second is to use {@link Temporal#with(TemporalAdjuster)}: + *
+ *   // these two lines are equivalent, but the second approach is recommended
+ *   dateTime = adjuster.adjustInto(dateTime);
+ *   dateTime = dateTime.with(adjuster);
+ * 
+ * It is recommended to use the second approach, {@code with(TemporalAdjuster)}, + * as it is a lot clearer to read in code. + * + *

Specification for implementors

+ * This is a thread-safe utility class. + * All returned adjusters are immutable and thread-safe. + * + * @since 1.8 + */ +public final class Adjusters { + + /** + * Private constructor since this is a utility class. + */ + private Adjusters() { + } + + //----------------------------------------------------------------------- + /** + * Returns the "first day of month" adjuster, which returns a new date set to + * the first day of the current month. + *

+ * The ISO calendar system behaves as follows:
+ * The input 2011-01-15 will return 2011-01-01.
+ * The input 2011-02-15 will return 2011-02-01. + *

+ * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

+     *  temporal.with(DAY_OF_MONTH, 1);
+     * 
+ * + * @return the first day-of-month adjuster, not null + */ + public static TemporalAdjuster firstDayOfMonth() { + return Impl.FIRST_DAY_OF_MONTH; + } + + /** + * Returns the "last day of month" adjuster, which returns a new date set to + * the last day of the current month. + *

+ * The ISO calendar system behaves as follows:
+ * The input 2011-01-15 will return 2011-01-31.
+ * The input 2011-02-15 will return 2011-02-28.
+ * The input 2012-02-15 will return 2012-02-29 (leap year).
+ * The input 2011-04-15 will return 2011-04-30. + *

+ * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

+     *  long lastDay = temporal.range(DAY_OF_MONTH).getMaximum();
+     *  temporal.with(DAY_OF_MONTH, lastDay);
+     * 
+ * + * @return the last day-of-month adjuster, not null + */ + public static TemporalAdjuster lastDayOfMonth() { + return Impl.LAST_DAY_OF_MONTH; + } + + /** + * Returns the "first day of next month" adjuster, which returns a new date set to + * the first day of the next month. + *

+ * The ISO calendar system behaves as follows:
+ * The input 2011-01-15 will return 2011-02-01.
+ * The input 2011-02-15 will return 2011-03-01. + *

+ * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

+     *  temporal.with(DAY_OF_MONTH, 1).plus(1, MONTHS);
+     * 
+ * + * @return the first day of next month adjuster, not null + */ + public static TemporalAdjuster firstDayOfNextMonth() { + return Impl.FIRST_DAY_OF_NEXT_MONTH; + } + + //----------------------------------------------------------------------- + /** + * Returns the "first day of year" adjuster, which returns a new date set to + * the first day of the current year. + *

+ * The ISO calendar system behaves as follows:
+ * The input 2011-01-15 will return 2011-01-01.
+ * The input 2011-02-15 will return 2011-01-01.
+ *

+ * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

+     *  temporal.with(DAY_OF_YEAR, 1);
+     * 
+ * + * @return the first day-of-year adjuster, not null + */ + public static TemporalAdjuster firstDayOfYear() { + return Impl.FIRST_DAY_OF_YEAR; + } + + /** + * Returns the "last day of year" adjuster, which returns a new date set to + * the last day of the current year. + *

+ * The ISO calendar system behaves as follows:
+ * The input 2011-01-15 will return 2011-12-31.
+ * The input 2011-02-15 will return 2011-12-31.
+ *

+ * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

+     *  long lastDay = temporal.range(DAY_OF_YEAR).getMaximum();
+     *  temporal.with(DAY_OF_YEAR, lastDay);
+     * 
+ * + * @return the last day-of-year adjuster, not null + */ + public static TemporalAdjuster lastDayOfYear() { + return Impl.LAST_DAY_OF_YEAR; + } + + /** + * Returns the "first day of next year" adjuster, which returns a new date set to + * the first day of the next year. + *

+ * The ISO calendar system behaves as follows:
+ * The input 2011-01-15 will return 2012-01-01. + *

+ * The behavior is suitable for use with most calendar systems. + * It is equivalent to: + *

+     *  temporal.with(DAY_OF_YEAR, 1).plus(1, YEARS);
+     * 
+ * + * @return the first day of next month adjuster, not null + */ + public static TemporalAdjuster firstDayOfNextYear() { + return Impl.FIRST_DAY_OF_NEXT_YEAR; + } + + //----------------------------------------------------------------------- + /** + * Enum implementing the adjusters. + */ + private static class Impl implements TemporalAdjuster { + /** First day of month adjuster. */ + private static final Impl FIRST_DAY_OF_MONTH = new Impl(0); + /** Last day of month adjuster. */ + private static final Impl LAST_DAY_OF_MONTH = new Impl(1); + /** First day of next month adjuster. */ + private static final Impl FIRST_DAY_OF_NEXT_MONTH = new Impl(2); + /** First day of year adjuster. */ + private static final Impl FIRST_DAY_OF_YEAR = new Impl(3); + /** Last day of year adjuster. */ + private static final Impl LAST_DAY_OF_YEAR = new Impl(4); + /** First day of next month adjuster. */ + private static final Impl FIRST_DAY_OF_NEXT_YEAR = new Impl(5); + /** The ordinal. */ + private final int ordinal; + private Impl(int ordinal) { + this.ordinal = ordinal; + } + @Override + public Temporal adjustInto(Temporal temporal) { + switch (ordinal) { + case 0: return temporal.with(DAY_OF_MONTH, 1); + case 1: return temporal.with(DAY_OF_MONTH, temporal.range(DAY_OF_MONTH).getMaximum()); + case 2: return temporal.with(DAY_OF_MONTH, 1).plus(1, MONTHS); + case 3: return temporal.with(DAY_OF_YEAR, 1); + case 4: return temporal.with(DAY_OF_YEAR, temporal.range(DAY_OF_YEAR).getMaximum()); + case 5: return temporal.with(DAY_OF_YEAR, 1).plus(1, YEARS); + } + throw new IllegalStateException("Unreachable"); + } + } + + //----------------------------------------------------------------------- + /** + * Returns the first in month adjuster, which returns a new date + * in the same month with the first matching day-of-week. + * This is used for expressions like 'first Tuesday in March'. + *

+ * The ISO calendar system behaves as follows:
+ * The input 2011-12-15 for (MONDAY) will return 2011-12-05.
+ * The input 2011-12-15 for (FRIDAY) will return 2011-12-02.
+ *

+ * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields + * and the {@code DAYS} unit, and assumes a seven day week. + * + * @param dayOfWeek the day-of-week, not null + * @return the first in month adjuster, not null + */ + public static TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek) { + Objects.requireNonNull(dayOfWeek, "dayOfWeek"); + return new DayOfWeekInMonth(1, dayOfWeek); + } + + /** + * Returns the last in month adjuster, which returns a new date + * in the same month with the last matching day-of-week. + * This is used for expressions like 'last Tuesday in March'. + *

+ * The ISO calendar system behaves as follows:
+ * The input 2011-12-15 for (MONDAY) will return 2011-12-26.
+ * The input 2011-12-15 for (FRIDAY) will return 2011-12-30.
+ *

+ * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields + * and the {@code DAYS} unit, and assumes a seven day week. + * + * @param dayOfWeek the day-of-week, not null + * @return the first in month adjuster, not null + */ + public static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek) { + Objects.requireNonNull(dayOfWeek, "dayOfWeek"); + return new DayOfWeekInMonth(-1, dayOfWeek); + } + + /** + * Returns the day-of-week in month adjuster, which returns a new date + * in the same month with the ordinal day-of-week. + * This is used for expressions like the 'second Tuesday in March'. + *

+ * The ISO calendar system behaves as follows:
+ * The input 2011-12-15 for (1,TUESDAY) will return 2011-12-06.
+ * The input 2011-12-15 for (2,TUESDAY) will return 2011-12-13.
+ * The input 2011-12-15 for (3,TUESDAY) will return 2011-12-20.
+ * The input 2011-12-15 for (4,TUESDAY) will return 2011-12-27.
+ * The input 2011-12-15 for (5,TUESDAY) will return 2012-01-03.
+ * The input 2011-12-15 for (-1,TUESDAY) will return 2011-12-27 (last in month).
+ * The input 2011-12-15 for (-4,TUESDAY) will return 2011-12-06 (3 weeks before last in month).
+ * The input 2011-12-15 for (-5,TUESDAY) will return 2011-11-29 (4 weeks before last in month).
+ * The input 2011-12-15 for (0,TUESDAY) will return 2011-11-29 (last in previous month).
+ *

+ * For a positive or zero ordinal, the algorithm is equivalent to finding the first + * day-of-week that matches within the month and then adding a number of weeks to it. + * For a negative ordinal, the algorithm is equivalent to finding the last + * day-of-week that matches within the month and then subtracting a number of weeks to it. + * The ordinal number of weeks is not validated and is interpreted leniently + * according to this algorithm. This definition means that an ordinal of zero finds + * the last matching day-of-week in the previous month. + *

+ * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} and {@code DAY_OF_MONTH} fields + * and the {@code DAYS} unit, and assumes a seven day week. + * + * @param ordinal the week within the month, unbound but typically from -5 to 5 + * @param dayOfWeek the day-of-week, not null + * @return the day-of-week in month adjuster, not null + * @throws IllegalArgumentException if the ordinal is invalid + */ + public static TemporalAdjuster dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek) { + Objects.requireNonNull(dayOfWeek, "dayOfWeek"); + return new DayOfWeekInMonth(ordinal, dayOfWeek); + } + + /** + * Class implementing day-of-week in month adjuster. + */ + private static final class DayOfWeekInMonth implements TemporalAdjuster { + /** The ordinal. */ + private final int ordinal; + /** The day-of-week value, from 1 to 7. */ + private final int dowValue; + + private DayOfWeekInMonth(int ordinal, DayOfWeek dow) { + super(); + this.ordinal = ordinal; + this.dowValue = dow.getValue(); + } + @Override + public Temporal adjustInto(Temporal temporal) { + if (ordinal >= 0) { + Temporal temp = temporal.with(DAY_OF_MONTH, 1); + int curDow = temp.get(DAY_OF_WEEK); + int dowDiff = (dowValue - curDow + 7) % 7; + dowDiff += (ordinal - 1L) * 7L; // safe from overflow + return temp.plus(dowDiff, DAYS); + } else { + Temporal temp = temporal.with(DAY_OF_MONTH, temporal.range(DAY_OF_MONTH).getMaximum()); + int curDow = temp.get(DAY_OF_WEEK); + int daysDiff = dowValue - curDow; + daysDiff = (daysDiff == 0 ? 0 : (daysDiff > 0 ? daysDiff - 7 : daysDiff)); + daysDiff -= (-ordinal - 1L) * 7L; // safe from overflow + return temp.plus(daysDiff, DAYS); + } + } + } + + //----------------------------------------------------------------------- + /** + * Returns the next day-of-week adjuster, which adjusts the date to the + * first occurrence of the specified day-of-week after the date being adjusted. + *

+ * The ISO calendar system behaves as follows:
+ * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-17 (two days later).
+ * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-19 (four days later).
+ * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-22 (seven days later). + *

+ * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, + * and assumes a seven day week. + * + * @param dayOfWeek the day-of-week to move the date to, not null + * @return the next day-of-week adjuster, not null + */ + public static TemporalAdjuster next(DayOfWeek dayOfWeek) { + return new RelativeDayOfWeek(2, dayOfWeek); + } + + /** + * Returns the next-or-same day-of-week adjuster, which 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. + *

+ * The ISO calendar system behaves as follows:
+ * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-17 (two days later).
+ * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-19 (four days later).
+ * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-15 (same as input). + *

+ * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, + * and assumes a seven day week. + * + * @param dayOfWeek the day-of-week to check for or move the date to, not null + * @return the next-or-same day-of-week adjuster, not null + */ + public static TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek) { + return new RelativeDayOfWeek(0, dayOfWeek); + } + + /** + * Returns the previous day-of-week adjuster, which adjusts the date to the + * first occurrence of the specified day-of-week before the date being adjusted. + *

+ * The ISO calendar system behaves as follows:
+ * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-10 (five days earlier).
+ * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-12 (three days earlier).
+ * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-08 (seven days earlier). + *

+ * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, + * and assumes a seven day week. + * + * @param dayOfWeek the day-of-week to move the date to, not null + * @return the previous day-of-week adjuster, not null + */ + public static TemporalAdjuster previous(DayOfWeek dayOfWeek) { + return new RelativeDayOfWeek(3, dayOfWeek); + } + + /** + * Returns the previous-or-same day-of-week adjuster, which 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. + *

+ * The ISO calendar system behaves as follows:
+ * The input 2011-01-15 (a Saturday) for parameter (MONDAY) will return 2011-01-10 (five days earlier).
+ * The input 2011-01-15 (a Saturday) for parameter (WEDNESDAY) will return 2011-01-12 (three days earlier).
+ * The input 2011-01-15 (a Saturday) for parameter (SATURDAY) will return 2011-01-15 (same as input). + *

+ * The behavior is suitable for use with most calendar systems. + * It uses the {@code DAY_OF_WEEK} field and the {@code DAYS} unit, + * and assumes a seven day week. + * + * @param dayOfWeek the day-of-week to check for or move the date to, not null + * @return the previous-or-same day-of-week adjuster, not null + */ + public static TemporalAdjuster previousOrSame(DayOfWeek dayOfWeek) { + return new RelativeDayOfWeek(1, dayOfWeek); + } + + /** + * Implementation of next, previous or current day-of-week. + */ + private static final class RelativeDayOfWeek implements TemporalAdjuster { + /** Whether the current date is a valid answer. */ + private final int relative; + /** The day-of-week value, from 1 to 7. */ + private final int dowValue; + + private RelativeDayOfWeek(int relative, DayOfWeek dayOfWeek) { + Objects.requireNonNull(dayOfWeek, "dayOfWeek"); + this.relative = relative; + this.dowValue = dayOfWeek.getValue(); + } + + @Override + public Temporal adjustInto(Temporal temporal) { + int calDow = temporal.get(DAY_OF_WEEK); + if (relative < 2 && calDow == dowValue) { + return temporal; + } + if ((relative & 1) == 0) { + int daysDiff = calDow - dowValue; + return temporal.plus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, DAYS); + } else { + int daysDiff = dowValue - calDow; + return temporal.minus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, DAYS); + } + } + } + +} diff --git a/src/share/classes/java/time/temporal/Chrono.java b/src/share/classes/java/time/temporal/Chrono.java new file mode 100644 index 0000000000000000000000000000000000000000..ff4660884f83ed4f09d175e2d684b0d98bfffdfa --- /dev/null +++ b/src/share/classes/java/time/temporal/Chrono.java @@ -0,0 +1,858 @@ +/* + * 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) 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 java.time.temporal; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.time.Clock; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.calendar.HijrahChrono; +import java.time.calendar.JapaneseChrono; +import java.time.calendar.MinguoChrono; +import java.time.calendar.ThaiBuddhistChrono; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.TextStyle; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A calendar system, used to organize and identify dates. + *

+ * The main date and time API is built on the ISO calendar system. + * This class operates behind the scenes to represent the general concept of a calendar system. + * For example, the Japanese, Minguo, Thai Buddhist and others. + *

+ * Most other calendar systems also operate on the shared concepts of year, month and day, + * linked to the cycles of the Earth around the Sun, and the Moon around the Earth. + * These shared concepts are defined by {@link ChronoField} and are availalbe + * for use by any {@code Chrono} implementation: + *

+ *   LocalDate isoDate = ...
+ *   ChronoLocalDate<ThaiBuddhistChrono> thaiDate = ...
+ *   int isoYear = isoDate.get(ChronoField.YEAR);
+ *   int thaiYear = thaiDate.get(ChronoField.YEAR);
+ * 
+ * As shown, although the date objects are in different calendar systems, represented by different + * {@code Chrono} instances, both can be queried using the same constant on {@code ChronoField}. + * For a full discussion of the implications of this, see {@link ChronoLocalDate}. + * In general, the advice is to use the known ISO-based {@code LocalDate}, rather than + * {@code ChronoLocalDate}. + *

+ * While a {@code Chrono} object typically uses {@code ChronoField} and is based on + * an era, year-of-era, month-of-year, day-of-month model of a date, this is not required. + * A {@code Chrono} instance may represent a totally different kind of calendar system, + * such as the Mayan. + *

+ * In practical terms, the {@code Chrono} instance also acts as a factory. + * The {@link #of(String)} method allows an instance to be looked up by identifier, + * while the {@link #ofLocale(Locale)} method allows lookup by locale. + *

+ * The {@code Chrono} instance provides a set of methods to create {@code ChronoLocalDate} instances. + * The date classes are used to manipulate specific dates. + *

    + *
  • {@link #dateNow() dateNow()} + *
  • {@link #dateNow(Clock) dateNow(clock)} + *
  • {@link #dateNow(ZoneId) dateNow(zone)} + *
  • {@link #date(int, int, int) date(yearProleptic, month, day)} + *
  • {@link #date(Era, int, int, int) date(era, yearOfEra, month, day)} + *
  • {@link #dateYearDay(int, int) dateYearDay(yearProleptic, dayOfYear)} + *
  • {@link #dateYearDay(Era, int, int) dateYearDay(era, yearOfEra, dayOfYear)} + *
  • {@link #date(TemporalAccessor) date(TemporalAccessor)} + *

+ * + *

Adding New Calendars

+ * The set of available chronologies can be extended by applications. + * Adding a new calendar system requires the writing of an implementation of + * {@code Chrono}, {@code ChronoLocalDate} and {@code Era}. + * The majority of the logic specific to the calendar system will be in + * {@code ChronoLocalDate}. The {@code Chrono} subclass acts as a factory. + *

+ * To permit the discovery of additional chronologies, the {@link java.util.ServiceLoader ServiceLoader} + * is used. A file must be added to the {@code META-INF/services} directory with the + * name 'java.time.temporal.Chrono' listing the implementation classes. + * See the ServiceLoader for more details on service loading. + * For lookup by id or calendarType, the system provided calendars are found + * first followed by application provided calendars. + *

+ * Each chronology must define a chronology ID that is unique within the system. + * If the chronology represents a calendar system defined by the + * Unicode Locale Data Markup Language (LDML) specification then that + * calendar type should also be specified. + * + *

Specification for implementors

+ * This class must be implemented with care to ensure other classes operate correctly. + * All implementations that can be instantiated must be final, immutable and thread-safe. + * Subclasses should be Serializable wherever possible. + * + * @param the type of the implementing subclass + * @since 1.8 + */ +public abstract class Chrono> implements Comparable> { + + /** + * Map of available calendars by ID. + */ + private static final ConcurrentHashMap> CHRONOS_BY_ID = new ConcurrentHashMap<>(); + /** + * Map of available calendars by calendar type. + */ + private static final ConcurrentHashMap> CHRONOS_BY_TYPE = new ConcurrentHashMap<>(); + + /** + * Register a Chrono by ID and type for lookup by {@link #of(java.lang.String)}. + * Chronos must not be registered until they are completely constructed. + * Specifically, not in the constructor of Chrono. + * @param chrono the chronology to register; not null + */ + private static void registerChrono(Chrono chrono) { + Chrono prev = CHRONOS_BY_ID.putIfAbsent(chrono.getId(), chrono); + if (prev == null) { + String type = chrono.getCalendarType(); + if (type != null) { + CHRONOS_BY_TYPE.putIfAbsent(type, chrono); + } + } + } + + /** + * Initialization of the maps from id and type to Chrono. + * The ServiceLoader is used to find and register any implementations + * of {@link javax.time.temporal.Chrono} found in the bootclass loader. + * The built-in chronologies are registered explicitly. + * Calendars configured via the Thread's context classloader are local + * to that thread and are ignored. + *

+ * The initialization is done only once using the registration + * of the ISOChrono as the test and the final step. + * Multiple threads may perform the initialization concurrently. + * Only the first registration of each Chrono is retained by the + * ConcurrentHashMap. + * @return true if the cache was initialized + */ + private static boolean initCache() { + if (CHRONOS_BY_ID.get("ISO") == null) { + // Initialization is incomplete + @SuppressWarnings("rawtypes") + ServiceLoader loader = ServiceLoader.load(Chrono.class, null); + for (Chrono chrono : loader) { + registerChrono(chrono); + } + + // Register these calendars; the ServiceLoader configuration is not used + registerChrono(HijrahChrono.INSTANCE); + registerChrono(JapaneseChrono.INSTANCE); + registerChrono(MinguoChrono.INSTANCE); + registerChrono(ThaiBuddhistChrono.INSTANCE); + + // finally, register ISOChrono to mark initialization is complete + registerChrono(ISOChrono.INSTANCE); + return true; + } + return false; + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Chrono} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code Chrono}. + * If the specified temporal object does not have a chronology, {@link ISOChrono} is returned. + *

+ * The conversion will obtain the chronology using {@link Queries#chrono()}. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used in queries via method reference, {@code Chrono::from}. + * + * @param temporal the temporal to convert, not null + * @return the chronology, not null + * @throws DateTimeException if unable to convert to an {@code Chrono} + */ + public static Chrono from(TemporalAccessor temporal) { + Objects.requireNonNull(temporal, "temporal"); + Chrono obj = temporal.query(Queries.chrono()); + return (obj != null ? obj : ISOChrono.INSTANCE); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Chrono} from a locale. + *

+ * The locale can be used to identify a calendar. + * This uses {@link Locale#getUnicodeLocaleType(String)} to obtain the "ca" key + * to identify the calendar system. + *

+ * If the locale does not contain calendar system information, the standard + * ISO calendar system is used. + * + * @param locale the locale to use to obtain the calendar system, not null + * @return the calendar system associated with the locale, not null + * @throws DateTimeException if the locale-specified calendar cannot be found + */ + public static Chrono ofLocale(Locale locale) { + Objects.requireNonNull(locale, "locale"); + String type = locale.getUnicodeLocaleType("ca"); + if (type == null) { + return ISOChrono.INSTANCE; + } else if ("iso".equals(type) || "iso8601".equals(type)) { + return ISOChrono.INSTANCE; + } else { + Chrono chrono = CHRONOS_BY_TYPE.get(type); + if (chrono == null) { + throw new DateTimeException("Unknown calendar system: " + type); + } + return chrono; + } + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Chrono} from a chronology ID or + * calendar system type. + *

+ * This returns a chronology based on either the ID or the type. + * The {@link #getId() chronology ID} uniquely identifies the chronology. + * The {@link #getCalendarType() calendar system type} is defined by the LDML specification. + *

+ * The chronology may be a system chronology or a chronology + * provided by the application via ServiceLoader configuration. + *

+ * Since some calendars can be customized, the ID or type typically refers + * to the default customization. For example, the Gregorian calendar can have multiple + * cutover dates from the Julian, but the lookup only provides the default cutover date. + * + * @param id the chronology ID or calendar system type, not null + * @return the chronology with the identifier requested, not null + * @throws DateTimeException if the chronology cannot be found + */ + public static Chrono of(String id) { + Objects.requireNonNull(id, "id"); + do { + Chrono chrono = of0(id); + if (chrono != null) { + return chrono; + } + // If not found, do the initialization (once) and repeat the lookup + } while (initCache()); + + // Look for a Chrono using ServiceLoader of the Thread's ContextClassLoader + // Application provided Chronologies must not be cached + @SuppressWarnings("rawtypes") + ServiceLoader loader = ServiceLoader.load(Chrono.class); + for (Chrono chrono : loader) { + if (id.equals(chrono.getId()) || id.equals(chrono.getCalendarType())) { + return chrono; + } + } + throw new DateTimeException("Unknown chronology: " + id); + } + + /** + * Obtains an instance of {@code Chrono} from a chronology ID or + * calendar system type. + * + * @param id the chronology ID or calendar system type, not null + * @return the chronology with the identifier requested, or {@code null} if not found + */ + private static Chrono of0(String id) { + Chrono chrono = CHRONOS_BY_ID.get(id); + if (chrono == null) { + chrono = CHRONOS_BY_TYPE.get(id); + } + return chrono; + } + + /** + * Returns the available chronologies. + *

+ * Each returned {@code Chrono} is available for use in the system. + * The set of chronologies includes the system chronologies and + * any chronologies provided by the application via ServiceLoader + * configuration. + * + * @return the independent, modifiable set of the available chronology IDs, not null + */ + public static Set> getAvailableChronologies() { + initCache(); // force initialization + HashSet> chronos = new HashSet<>(CHRONOS_BY_ID.values()); + + /// Add in Chronologies from the ServiceLoader configuration + @SuppressWarnings("rawtypes") + ServiceLoader loader = ServiceLoader.load(Chrono.class); + for (Chrono chrono : loader) { + chronos.add(chrono); + } + return chronos; + } + + //----------------------------------------------------------------------- + /** + * Obtains a local date-time from the a date and time. + *

+ * This combines a {@link ChronoLocalDate}, which provides the {@code Chrono}, + * with a {@link LocalTime} to produce a {@link ChronoLocalDateTime}. + *

+ * This method is intended for chronology implementations. + * It uses a standard implementation that is shared for all chronologies. + * + * @param the chronology of the date + * @param date the date, not null + * @param time the time, not null + * @return the local date-time combining the input date and time, not null + */ + public static > ChronoLocalDateTime dateTime(ChronoLocalDate date, LocalTime time) { + return ChronoLocalDateTimeImpl.of(date, time); + } + + //----------------------------------------------------------------------- + /** + * Creates an instance. + */ + protected Chrono() { + } + + //----------------------------------------------------------------------- + /** + * Casts the {@code Temporal} to {@code ChronoLocalDate} with the same chronology. + * + * @param temporal a date-time to cast, not null + * @return the date-time checked and cast to {@code ChronoLocalDate}, not null + * @throws ClassCastException if the date-time cannot be cast to ChronoLocalDate + * or the chronology is not equal this Chrono + */ + ChronoLocalDate ensureChronoLocalDate(Temporal temporal) { + @SuppressWarnings("unchecked") + ChronoLocalDate other = (ChronoLocalDate) temporal; + if (this.equals(other.getChrono()) == false) { + throw new ClassCastException("Chrono mismatch, expected: " + getId() + ", actual: " + other.getChrono().getId()); + } + return other; + } + + /** + * Casts the {@code Temporal} to {@code ChronoLocalDateTime} with the same chronology. + * + * @param temporal a date-time to cast, not null + * @return the date-time checked and cast to {@code ChronoLocalDateTime}, not null + * @throws ClassCastException if the date-time cannot be cast to ChronoLocalDateTimeImpl + * or the chronology is not equal this Chrono + */ + ChronoLocalDateTimeImpl ensureChronoLocalDateTime(Temporal temporal) { + @SuppressWarnings("unchecked") + ChronoLocalDateTimeImpl other = (ChronoLocalDateTimeImpl) temporal; + if (this.equals(other.getDate().getChrono()) == false) { + throw new ClassCastException("Chrono mismatch, required: " + getId() + + ", supplied: " + other.getDate().getChrono().getId()); + } + return other; + } + + /** + * Casts the {@code Temporal} to {@code ChronoZonedDateTimeImpl} with the same chronology. + * + * @param temporal a date-time to cast, not null + * @return the date-time checked and cast to {@code ChronoZonedDateTimeImpl}, not null + * @throws ClassCastException if the date-time cannot be cast to ChronoZonedDateTimeImpl + * or the chronology is not equal this Chrono + */ + ChronoZonedDateTimeImpl ensureChronoZonedDateTime(Temporal temporal) { + @SuppressWarnings("unchecked") + ChronoZonedDateTimeImpl other = (ChronoZonedDateTimeImpl) temporal; + if (this.equals(other.getDate().getChrono()) == false) { + throw new ClassCastException("Chrono mismatch, required: " + getId() + + ", supplied: " + other.getDate().getChrono().getId()); + } + return other; + } + + //----------------------------------------------------------------------- + /** + * Gets the ID of the chronology. + *

+ * The ID uniquely identifies the {@code Chrono}. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * + * @return the chronology ID, not null + * @see #getCalendarType() + */ + public abstract String getId(); + + /** + * Gets the calendar type of the underlying calendar system. + *

+ * The calendar type is an identifier defined by the + * Unicode Locale Data Markup Language (LDML) specification. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * It can also be used as part of a locale, accessible via + * {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'. + * + * @return the calendar system type, null if the calendar is not defined by LDML + * @see #getId() + */ + public abstract String getCalendarType(); + + //----------------------------------------------------------------------- + /** + * Obtains a local date in this chronology from the era, year-of-era, + * month-of-year and day-of-month fields. + * + * @param era the era of the correct type for the chronology, not null + * @param yearOfEra the chronology year-of-era + * @param month the chronology month-of-year + * @param dayOfMonth the chronology day-of-month + * @return the local date in this chronology, not null + * @throws DateTimeException if unable to create the date + */ + public ChronoLocalDate date(Era era, int yearOfEra, int month, int dayOfMonth) { + return date(prolepticYear(era, yearOfEra), month, dayOfMonth); + } + + /** + * Obtains a local date in this chronology from the proleptic-year, + * month-of-year and day-of-month fields. + * + * @param prolepticYear the chronology proleptic-year + * @param month the chronology month-of-year + * @param dayOfMonth the chronology day-of-month + * @return the local date in this chronology, not null + * @throws DateTimeException if unable to create the date + */ + public abstract ChronoLocalDate date(int prolepticYear, int month, int dayOfMonth); + + /** + * Obtains a local date in this chronology from the era, year-of-era and + * day-of-year fields. + * + * @param era the era of the correct type for the chronology, not null + * @param yearOfEra the chronology year-of-era + * @param dayOfYear the chronology day-of-year + * @return the local date in this chronology, not null + * @throws DateTimeException if unable to create the date + */ + public ChronoLocalDate dateYearDay(Era era, int yearOfEra, int dayOfYear) { + return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear); + } + + /** + * Obtains a local date in this chronology from the proleptic-year and + * day-of-year fields. + * + * @param prolepticYear the chronology proleptic-year + * @param dayOfYear the chronology day-of-year + * @return the local date in this chronology, not null + * @throws DateTimeException if unable to create the date + */ + public abstract ChronoLocalDate dateYearDay(int prolepticYear, int dayOfYear); + + /** + * Obtains a local date in this chronology from another temporal object. + *

+ * This creates a date in this chronology based on the specified {@code TemporalAccessor}. + *

+ * The standard mechanism for conversion between date types is the + * {@link ChronoField#EPOCH_DAY local epoch-day} field. + * + * @param temporal the temporal object to convert, not null + * @return the local date in this chronology, not null + * @throws DateTimeException if unable to create the date + */ + public abstract ChronoLocalDate date(TemporalAccessor temporal); + + //----------------------------------------------------------------------- + /** + * Obtains the current local date in this chronology from the system clock in the default time-zone. + *

+ * This will query the {@link Clock#systemDefaultZone() system clock} in the default + * time-zone to obtain the current date. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + *

+ * This implementation uses {@link #dateNow(Clock)}. + * + * @return the current local date using the system clock and default time-zone, not null + * @throws DateTimeException if unable to create the date + */ + public ChronoLocalDate dateNow() { + return dateNow(Clock.systemDefaultZone()); + } + + /** + * Obtains the current local date in this chronology from the system clock in the specified time-zone. + *

+ * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date. + * Specifying the time-zone avoids dependence on the default time-zone. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @param zone the zone ID to use, not null + * @return the current local date using the system clock, not null + * @throws DateTimeException if unable to create the date + */ + public ChronoLocalDate dateNow(ZoneId zone) { + return dateNow(Clock.system(zone)); + } + + /** + * Obtains the current local date in this chronology from the specified clock. + *

+ * This will query the specified clock to obtain the current date - today. + * Using this method allows the use of an alternate clock for testing. + * The alternate clock may be introduced using {@link Clock dependency injection}. + * + * @param clock the clock to use, not null + * @return the current local date, not null + * @throws DateTimeException if unable to create the date + */ + public ChronoLocalDate dateNow(Clock clock) { + Objects.requireNonNull(clock, "clock"); + return date(LocalDate.now(clock)); + } + + //----------------------------------------------------------------------- + /** + * Obtains a local date-time in this chronology from another temporal object. + *

+ * This creates a date-time in this chronology based on the specified {@code TemporalAccessor}. + *

+ * The date of the date-time should be equivalent to that obtained by calling + * {@link #date(TemporalAccessor)}. + * The standard mechanism for conversion between time types is the + * {@link ChronoField#NANO_OF_DAY nano-of-day} field. + * + * @param temporal the temporal object to convert, not null + * @return the local date-time in this chronology, not null + * @throws DateTimeException if unable to create the date-time + */ + public ChronoLocalDateTime localDateTime(TemporalAccessor temporal) { + try { + return date(temporal).atTime(LocalTime.from(temporal)); + } catch (DateTimeException ex) { + throw new DateTimeException("Unable to obtain ChronoLocalDateTime from TemporalAccessor: " + temporal.getClass(), ex); + } + } + + /** + * Obtains a zoned date-time in this chronology from another temporal object. + *

+ * This creates a date-time in this chronology based on the specified {@code TemporalAccessor}. + *

+ * This should obtain a {@code ZoneId} using {@link ZoneId#from(TemporalAccessor)}. + * The date-time should be obtained by obtaining an {@code Instant}. + * If that fails, the local date-time should be used. + * + * @param temporal the temporal object to convert, not null + * @return the zoned date-time in this chronology, not null + * @throws DateTimeException if unable to create the date-time + */ + public ChronoZonedDateTime zonedDateTime(TemporalAccessor temporal) { + try { + ZoneId zone = ZoneId.from(temporal); + try { + Instant instant = Instant.from(temporal); + return zonedDateTime(instant, zone); + + } catch (DateTimeException ex1) { + ChronoLocalDateTimeImpl cldt = ensureChronoLocalDateTime(localDateTime(temporal)); + return ChronoZonedDateTimeImpl.ofBest(cldt, zone, null); + } + } catch (DateTimeException ex) { + throw new DateTimeException("Unable to obtain ChronoZonedDateTime from TemporalAccessor: " + temporal.getClass(), ex); + } + } + + /** + * Obtains a zoned date-time in this chronology from an {@code Instant}. + *

+ * This creates a zoned date-time with the same instant as that specified. + * + * @param instant the instant to create the date-time from, not null + * @param zone the time-zone, not null + * @return the zoned date-time, not null + * @throws DateTimeException if the result exceeds the supported range + */ + public ChronoZonedDateTime zonedDateTime(Instant instant, ZoneId zone) { + return ChronoZonedDateTimeImpl.ofInstant(this, instant, zone); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified year is a leap year. + *

+ * A leap-year is a year of a longer length than normal. + * The exact meaning is determined by the chronology according to the following constraints. + *

    + *
  • a leap-year must imply a year-length longer than a non leap-year. + *
  • a chronology that does not support the concept of a year must return false. + *

+ * + * @param prolepticYear the proleptic-year to check, not validated for range + * @return true if the year is a leap year + */ + public abstract boolean isLeapYear(long prolepticYear); + + /** + * Calculates the proleptic-year given the era and year-of-era. + *

+ * This combines the era and year-of-era into the single proleptic-year field. + * + * @param era the era of the correct type for the chronology, not null + * @param yearOfEra the chronology year-of-era + * @return the proleptic-year + * @throws DateTimeException if unable to convert + */ + public abstract int prolepticYear(Era era, int yearOfEra); + + /** + * Creates the chronology era object from the numeric value. + *

+ * The era is, conceptually, the largest division of the time-line. + * Most calendar systems have a single epoch dividing the time-line into two eras. + * However, some have multiple eras, such as one for the reign of each leader. + * The exact meaning is determined by the chronology according to the following constraints. + *

+ * The era in use at 1970-01-01 must have the value 1. + * Later eras must have sequentially higher values. + * Earlier eras must have sequentially lower values. + * Each chronology must refer to an enum or similar singleton to provide the era values. + *

+ * This method returns the singleton era of the correct type for the specified era value. + * + * @param eraValue the era value + * @return the calendar system era, not null + * @throws DateTimeException if unable to create the era + */ + public abstract Era eraOf(int eraValue); + + /** + * Gets the list of eras for the chronology. + *

+ * Most calendar systems have an era, within which the year has meaning. + * If the calendar system does not support the concept of eras, an empty + * list must be returned. + * + * @return the list of eras for the chronology, may be immutable, not null + */ + public abstract List> eras(); + + //----------------------------------------------------------------------- + /** + * Gets the range of valid values for the specified field. + *

+ * All fields can be expressed as a {@code long} integer. + * This method returns an object that describes the valid range for that value. + *

+ * Note that the result only describes the minimum and maximum valid values + * and it is important not to read too much into them. For example, there + * could be values within the range that are invalid for the field. + *

+ * This method will return a result whether or not the chronology supports the field. + * + * @param field the field to get the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + public abstract ValueRange range(ChronoField field); + + //----------------------------------------------------------------------- + /** + * Gets the textual representation of this chronology. + *

+ * This returns the textual name used to identify the chronology. + * The parameters control the style of the returned text and the locale. + * + * @param style the style of the text required, not null + * @param locale the locale to use, not null + * @return the text value of the chronology, not null + */ + public String getText(TextStyle style, Locale locale) { + return new DateTimeFormatterBuilder().appendChronoText(style).toFormatter(locale).print(new TemporalAccessor() { + @Override + public boolean isSupported(TemporalField field) { + return false; + } + @Override + public long getLong(TemporalField field) { + throw new DateTimeException("Unsupported field: " + field); + } + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.chrono()) { + return (R) Chrono.this; + } + return TemporalAccessor.super.query(query); + } + }); + } + + //----------------------------------------------------------------------- + /** + * Compares this chronology to another chronology. + *

+ * The comparison order first by the chronology ID string, then by any + * additional information specific to the subclass. + * It is "consistent with equals", as defined by {@link Comparable}. + *

+ * The default implementation compares the chronology ID. + * Subclasses must compare any additional state that they store. + * + * @param other the other chronology to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + @Override + public int compareTo(Chrono other) { + return getId().compareTo(other.getId()); + } + + /** + * Checks if this chronology is equal to another chronology. + *

+ * The comparison is based on the entire state of the object. + *

+ * The default implementation checks the type and calls {@link #compareTo(Chrono)}. + * + * @param obj the object to check, null returns false + * @return true if this is equal to the other chronology + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Chrono) { + return compareTo((Chrono) obj) == 0; + } + return false; + } + + /** + * A hash code for this chronology. + *

+ * The default implementation is based on the ID and class. + * Subclasses should add any additional state that they store. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return getClass().hashCode() ^ getId().hashCode(); + } + + //----------------------------------------------------------------------- + /** + * Outputs this chronology as a {@code String}, using the ID. + * + * @return a string representation of this chronology, not null + */ + @Override + public String toString() { + return getId(); + } + + //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(7);  // identifies this as a Chrono
+     * out.writeUTF(chronoId);
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.CHRONO_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(DataOutput out) throws IOException { + out.writeUTF(getId()); + } + + static Chrono readExternal(DataInput in) throws IOException { + String id = in.readUTF(); + return Chrono.of(id); + } + +} diff --git a/src/share/classes/java/time/temporal/ChronoField.java b/src/share/classes/java/time/temporal/ChronoField.java new file mode 100644 index 0000000000000000000000000000000000000000..f163654225bf241f48f862a60d627cbc16e294ff --- /dev/null +++ b/src/share/classes/java/time/temporal/ChronoField.java @@ -0,0 +1,610 @@ +/* + * 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 java.time.temporal; + +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.ERAS; +import static java.time.temporal.ChronoUnit.FOREVER; +import static java.time.temporal.ChronoUnit.HALF_DAYS; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MICROS; +import static java.time.temporal.ChronoUnit.MILLIS; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static java.time.temporal.ChronoUnit.WEEKS; +import static java.time.temporal.ChronoUnit.YEARS; + +import java.time.DayOfWeek; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeBuilder; + +/** + * A standard set of fields. + *

+ * This set of fields provide field-based access to manipulate a date, time or date-time. + * The standard set of fields can be extended by implementing {@link TemporalField}. + *

+ * These fields are intended to be applicable in multiple calendar systems. + * For example, most non-ISO calendar systems define dates as a year, month and day, + * just with slightly different rules. + * The documentation of each field explains how it operates. + * + *

Specification for implementors

+ * This is a final, immutable and thread-safe enum. + * + * @since 1.8 + */ +public enum ChronoField implements TemporalField { + + /** + * The nano-of-second. + *

+ * This counts the nanosecond within the second, from 0 to 999,999,999. + * This field has the same meaning for all calendar systems. + *

+ * This field is used to represent the nano-of-second handling any fraction of the second. + * Implementations of {@code TemporalAccessor} should provide a value for this field if + * they can return a value for {@link #SECOND_OF_MINUTE}, {@link #SECOND_OF_DAY} or + * {@link #INSTANT_SECONDS} filling unknown precision with zero. + *

+ * When this field is used for setting a value, it should set as much precision as the + * object stores, using integer division to remove excess precision. + * For example, if the {@code TemporalAccessor} stores time to millisecond precision, + * then the nano-of-second must be divided by 1,000,000 before replacing the milli-of-second. + */ + NANO_OF_SECOND("NanoOfSecond", NANOS, SECONDS, ValueRange.of(0, 999_999_999)), + /** + * The nano-of-day. + *

+ * This counts the nanosecond within the day, from 0 to (24 * 60 * 60 * 1,000,000,000) - 1. + * This field has the same meaning for all calendar systems. + *

+ * This field is used to represent the nano-of-day handling any fraction of the second. + * Implementations of {@code TemporalAccessor} should provide a value for this field if + * they can return a value for {@link #SECOND_OF_DAY} filling unknown precision with zero. + */ + NANO_OF_DAY("NanoOfDay", NANOS, DAYS, ValueRange.of(0, 86400L * 1000_000_000L - 1)), + /** + * The micro-of-second. + *

+ * This counts the microsecond within the second, from 0 to 999,999. + * This field has the same meaning for all calendar systems. + *

+ * This field is used to represent the micro-of-second handling any fraction of the second. + * Implementations of {@code TemporalAccessor} should provide a value for this field if + * they can return a value for {@link #SECOND_OF_MINUTE}, {@link #SECOND_OF_DAY} or + * {@link #INSTANT_SECONDS} filling unknown precision with zero. + *

+ * When this field is used for setting a value, it should behave in the same way as + * setting {@link #NANO_OF_SECOND} with the value multiplied by 1,000. + */ + MICRO_OF_SECOND("MicroOfSecond", MICROS, SECONDS, ValueRange.of(0, 999_999)), + /** + * The micro-of-day. + *

+ * This counts the microsecond within the day, from 0 to (24 * 60 * 60 * 1,000,000) - 1. + * This field has the same meaning for all calendar systems. + *

+ * This field is used to represent the micro-of-day handling any fraction of the second. + * Implementations of {@code TemporalAccessor} should provide a value for this field if + * they can return a value for {@link #SECOND_OF_DAY} filling unknown precision with zero. + *

+ * When this field is used for setting a value, it should behave in the same way as + * setting {@link #NANO_OF_DAY} with the value multiplied by 1,000. + */ + MICRO_OF_DAY("MicroOfDay", MICROS, DAYS, ValueRange.of(0, 86400L * 1000_000L - 1)), + /** + * The milli-of-second. + *

+ * This counts the millisecond within the second, from 0 to 999. + * This field has the same meaning for all calendar systems. + *

+ * This field is used to represent the milli-of-second handling any fraction of the second. + * Implementations of {@code TemporalAccessor} should provide a value for this field if + * they can return a value for {@link #SECOND_OF_MINUTE}, {@link #SECOND_OF_DAY} or + * {@link #INSTANT_SECONDS} filling unknown precision with zero. + *

+ * When this field is used for setting a value, it should behave in the same way as + * setting {@link #NANO_OF_SECOND} with the value multiplied by 1,000,000. + */ + MILLI_OF_SECOND("MilliOfSecond", MILLIS, SECONDS, ValueRange.of(0, 999)), + /** + * The milli-of-day. + *

+ * This counts the millisecond within the day, from 0 to (24 * 60 * 60 * 1,000) - 1. + * This field has the same meaning for all calendar systems. + *

+ * This field is used to represent the milli-of-day handling any fraction of the second. + * Implementations of {@code TemporalAccessor} should provide a value for this field if + * they can return a value for {@link #SECOND_OF_DAY} filling unknown precision with zero. + *

+ * When this field is used for setting a value, it should behave in the same way as + * setting {@link #NANO_OF_DAY} with the value multiplied by 1,000,000. + */ + MILLI_OF_DAY("MilliOfDay", MILLIS, DAYS, ValueRange.of(0, 86400L * 1000L - 1)), + /** + * The second-of-minute. + *

+ * 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", SECONDS, MINUTES, ValueRange.of(0, 59)), + /** + * The second-of-day. + *

+ * 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", SECONDS, DAYS, ValueRange.of(0, 86400L - 1)), + /** + * The minute-of-hour. + *

+ * 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", MINUTES, HOURS, ValueRange.of(0, 59)), + /** + * The minute-of-day. + *

+ * This counts the minute within the day, from 0 to (24 * 60) - 1. + * This field has the same meaning for all calendar systems. + */ + MINUTE_OF_DAY("MinuteOfDay", MINUTES, DAYS, ValueRange.of(0, (24 * 60) - 1)), + /** + * The hour-of-am-pm. + *

+ * This counts the hour within the AM/PM, from 0 to 11. + * This is the hour that would be observed on a standard 12-hour digital clock. + * This field has the same meaning for all calendar systems. + */ + HOUR_OF_AMPM("HourOfAmPm", HOURS, HALF_DAYS, ValueRange.of(0, 11)), + /** + * The clock-hour-of-am-pm. + *

+ * This counts the hour within the AM/PM, from 1 to 12. + * This is the hour that would be observed on a standard 12-hour analog wall clock. + * This field has the same meaning for all calendar systems. + */ + CLOCK_HOUR_OF_AMPM("ClockHourOfAmPm", HOURS, HALF_DAYS, ValueRange.of(1, 12)), + /** + * The hour-of-day. + *

+ * 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", HOURS, DAYS, ValueRange.of(0, 23)), + /** + * The clock-hour-of-day. + *

+ * This counts the hour within the AM/PM, from 1 to 24. + * This is the hour that would be observed on a 24-hour analog wall clock. + * This field has the same meaning for all calendar systems. + */ + CLOCK_HOUR_OF_DAY("ClockHourOfDay", HOURS, DAYS, ValueRange.of(1, 24)), + /** + * The am-pm-of-day. + *

+ * This counts the AM/PM within the day, from 0 (AM) to 1 (PM). + * This field has the same meaning for all calendar systems. + */ + AMPM_OF_DAY("AmPmOfDay", HALF_DAYS, DAYS, ValueRange.of(0, 1)), + /** + * The day-of-week, such as Tuesday. + *

+ * This represents the standard concept of the day of the week. + * In the default ISO calendar system, this has values from Monday (1) to Sunday (7). + * The {@link DayOfWeek} class can be used to interpret the result. + *

+ * Most non-ISO calendar systems also define a seven day week that aligns with ISO. + * Those calendar systems must also use the same numbering system, from Monday (1) to + * Sunday (7), which allows {@code DayOfWeek} to be used. + *

+ * Calendar systems that do not have a standard seven day week should implement this field + * if they have a similar concept of named or numbered days within a period similar + * to a week. It is recommended that the numbering starts from 1. + */ + DAY_OF_WEEK("DayOfWeek", DAYS, WEEKS, ValueRange.of(1, 7)), + /** + * The aligned day-of-week within a month. + *

+ * This represents concept of the count of days within the period of a week + * where the weeks are aligned to the start of the month. + * This field is typically used with {@link #ALIGNED_WEEK_OF_MONTH}. + *

+ * For example, in a calendar systems with a seven day week, the first aligned-week-of-month + * starts on day-of-month 1, the second aligned-week starts on day-of-month 8, and so on. + * Within each of these aligned-weeks, the days are numbered from 1 to 7 and returned + * as the value of this field. + * As such, day-of-month 1 to 7 will have aligned-day-of-week values from 1 to 7. + * And day-of-month 8 to 14 will repeat this with aligned-day-of-week values from 1 to 7. + *

+ * Calendar systems that do not have a seven day week should typically implement this + * field in the same way, but using the alternate week length. + */ + ALIGNED_DAY_OF_WEEK_IN_MONTH("AlignedDayOfWeekInMonth", DAYS, WEEKS, ValueRange.of(1, 7)), + /** + * The aligned day-of-week within a year. + *

+ * This represents concept of the count of days within the period of a week + * where the weeks are aligned to the start of the year. + * This field is typically used with {@link #ALIGNED_WEEK_OF_YEAR}. + *

+ * For example, in a calendar systems with a seven day week, the first aligned-week-of-year + * starts on day-of-year 1, the second aligned-week starts on day-of-year 8, and so on. + * Within each of these aligned-weeks, the days are numbered from 1 to 7 and returned + * as the value of this field. + * As such, day-of-year 1 to 7 will have aligned-day-of-week values from 1 to 7. + * And day-of-year 8 to 14 will repeat this with aligned-day-of-week values from 1 to 7. + *

+ * Calendar systems that do not have a seven day week should typically implement this + * field in the same way, but using the alternate week length. + */ + ALIGNED_DAY_OF_WEEK_IN_YEAR("AlignedDayOfWeekInYear", DAYS, WEEKS, ValueRange.of(1, 7)), + /** + * The day-of-month. + *

+ * 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. + *

+ * 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", DAYS, MONTHS, ValueRange.of(1, 28, 31)), + /** + * The day-of-year. + *

+ * This represents the concept of the day within the year. + * In the default ISO calendar system, this has values from 1 to 365 in standard + * years and 1 to 366 in leap years. + *

+ * Non-ISO calendar systems should implement this field using the most recognized + * day-of-year values for users of the calendar system. + * Normally, this is a count of days from 1 to the length of the year. + */ + DAY_OF_YEAR("DayOfYear", DAYS, YEARS, ValueRange.of(1, 365, 366)), + /** + * The epoch-day, based on the Java epoch of 1970-01-01 (ISO). + *

+ * This field is the sequential count of days where 1970-01-01 (ISO) is zero. + * Note that this uses the local time-line, ignoring offset and time-zone. + *

+ * This field is strictly defined to have the same meaning in all calendar systems. + * This is necessary to ensure interoperation between calendars. + */ + EPOCH_DAY("EpochDay", DAYS, FOREVER, ValueRange.of((long) (Year.MIN_VALUE * 365.25), (long) (Year.MAX_VALUE * 365.25))), + /** + * The aligned week within a month. + *

+ * This represents concept of the count of weeks within the period of a month + * where the weeks are aligned to the start of the month. + * This field is typically used with {@link #ALIGNED_DAY_OF_WEEK_IN_MONTH}. + *

+ * For example, in a calendar systems with a seven day week, the first aligned-week-of-month + * starts on day-of-month 1, the second aligned-week starts on day-of-month 8, and so on. + * Thus, day-of-month values 1 to 7 are in aligned-week 1, while day-of-month values + * 8 to 14 are in aligned-week 2, and so on. + *

+ * Calendar systems that do not have a seven day week should typically implement this + * field in the same way, but using the alternate week length. + */ + ALIGNED_WEEK_OF_MONTH("AlignedWeekOfMonth", WEEKS, MONTHS, ValueRange.of(1, 4, 5)), + /** + * The aligned week within a year. + *

+ * This represents concept of the count of weeks within the period of a year + * where the weeks are aligned to the start of the year. + * This field is typically used with {@link #ALIGNED_DAY_OF_WEEK_IN_YEAR}. + *

+ * For example, in a calendar systems with a seven day week, the first aligned-week-of-year + * starts on day-of-year 1, the second aligned-week starts on day-of-year 8, and so on. + * Thus, day-of-year values 1 to 7 are in aligned-week 1, while day-of-year values + * 8 to 14 are in aligned-week 2, and so on. + *

+ * Calendar systems that do not have a seven day week should typically implement this + * field in the same way, but using the alternate week length. + */ + ALIGNED_WEEK_OF_YEAR("AlignedWeekOfYear", WEEKS, YEARS, ValueRange.of(1, 53)), + /** + * The month-of-year, such as March. + *

+ * 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). + *

+ * 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", MONTHS, YEARS, ValueRange.of(1, 12)), + /** + * The epoch-month based on the Java epoch of 1970-01-01. + *

+ * This field is the sequential count of months where January 1970 (ISO) is zero. + * Note that this uses the local time-line, ignoring offset and time-zone. + *

+ * Non-ISO calendar systems should also implement this field to represent a sequential + * count of months. It is recommended to define zero as the month of 1970-01-01 (ISO). + */ + EPOCH_MONTH("EpochMonth", MONTHS, FOREVER, ValueRange.of((Year.MIN_VALUE - 1970L) * 12, (Year.MAX_VALUE - 1970L) * 12L - 1L)), + /** + * The year within the era. + *

+ * This represents the concept of the year within the era. + * This field is typically used with {@link #ERA}. + *

+ * 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} is used depends on which mental model is being used. + * See {@link ChronoLocalDate} for more discussion on this topic. + *

+ * In the default ISO calendar system, there are two eras defined, 'BCE' and 'CE'. + * The era 'CE' is the one currently in use and year-of-era runs from 1 to the maximum value. + * The era 'BCE' is the previous era, and the year-of-era runs backwards. + *

+ * For example, subtracting a year each time yield the following:
+ * - year-proleptic 2 = 'CE' year-of-era 2
+ * - year-proleptic 1 = 'CE' year-of-era 1
+ * - year-proleptic 0 = 'BCE' year-of-era 1
+ * - year-proleptic -1 = 'BCE' year-of-era 2
+ *

+ * Note that the ISO-8601 standard does not actually define eras. + * Note also that the ISO eras do not align with the well-known AD/BC eras due to the + * change between the Julian and Gregorian calendar systems. + *

+ * Non-ISO calendar systems should implement this field using the most recognized + * year-of-era value for users of the calendar system. + * Since most calendar systems have only two eras, the year-of-era numbering approach + * will typically be the same as that used by the ISO calendar system. + * The year-of-era value should typically always be positive, however this is not required. + */ + YEAR_OF_ERA("YearOfEra", YEARS, FOREVER, ValueRange.of(1, Year.MAX_VALUE, Year.MAX_VALUE + 1)), + /** + * The proleptic year, such as 2012. + *

+ * 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. + *

+ * 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. + *

+ * 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", YEARS, FOREVER, ValueRange.of(Year.MIN_VALUE, Year.MAX_VALUE)), + /** + * The era. + *

+ * This represents the concept of the era, which is the largest division of the time-line. + * This field is typically used with {@link #YEAR_OF_ERA}. + *

+ * In the default ISO calendar system, there are two eras defined, 'BCE' and 'CE'. + * The era 'CE' is the one currently in use and year-of-era runs from 1 to the maximum value. + * The era 'BCE' is the previous era, and the year-of-era runs backwards. + * See {@link #YEAR_OF_ERA} for a full example. + *

+ * Non-ISO calendar systems should implement this field to define eras. + * The value of the era that was active on 1970-01-01 (ISO) must be assigned the value 1. + * Earlier eras must have sequentially smaller values. + * Later eras must have sequentially larger values, + */ + ERA("Era", ERAS, FOREVER, ValueRange.of(0, 1)), + /** + * The instant epoch-seconds. + *

+ * This represents the concept of the sequential count of seconds where + * 1970-01-01T00:00Z (ISO) is zero. + * This field may be used with {@link #NANO_OF_DAY} to represent the fraction of the day. + *

+ * An {@link Instant} represents an instantaneous point on the time-line. + * On their own they have no elements which allow a local date-time to be obtained. + * Only when paired with an offset or time-zone can the local date or time be found. + * This field allows the seconds part of the instant to be queried. + *

+ * This field is strictly defined to have the same meaning in all calendar systems. + * This is necessary to ensure interoperation between calendars. + */ + INSTANT_SECONDS("InstantSeconds", SECONDS, FOREVER, ValueRange.of(Long.MIN_VALUE, Long.MAX_VALUE)), + /** + * The offset from UTC/Greenwich. + *

+ * This represents the concept of the offset in seconds of local time from UTC/Greenwich. + *

+ * A {@link ZoneOffset} represents the period of time that local time differs from UTC/Greenwich. + * This is usually a fixed number of hours and minutes. + * It is equivalent to the {@link ZoneOffset#getTotalSeconds() total amount} of the offset in seconds. + * For example, during the winter Paris has an offset of {@code +01:00}, which is 3600 seconds. + *

+ * This field is strictly defined to have the same meaning in all calendar systems. + * This is necessary to ensure interoperation between calendars. + */ + OFFSET_SECONDS("OffsetSeconds", SECONDS, FOREVER, ValueRange.of(-18 * 3600, 18 * 3600)); + + private final String name; + private final TemporalUnit baseUnit; + private final TemporalUnit rangeUnit; + private final ValueRange range; + + private ChronoField(String name, TemporalUnit baseUnit, TemporalUnit rangeUnit, ValueRange range) { + this.name = name; + this.baseUnit = baseUnit; + this.rangeUnit = rangeUnit; + this.range = range; + } + + //----------------------------------------------------------------------- + @Override + public String getName() { + return name; + } + + @Override + public TemporalUnit getBaseUnit() { + return baseUnit; + } + + @Override + public TemporalUnit getRangeUnit() { + return rangeUnit; + } + + @Override + public ValueRange range() { + return range; + } + + //----------------------------------------------------------------------- + /** + * Checks if this field represents a component of a date. + * + * @return true if it is a component of a date + */ + public boolean isDateField() { + return ordinal() >= DAY_OF_WEEK.ordinal() && ordinal() <= ERA.ordinal(); + } + + /** + * Checks if this field represents a component of a time. + * + * @return true if it is a component of a time + */ + public boolean isTimeField() { + return ordinal() < DAY_OF_WEEK.ordinal(); + } + + //----------------------------------------------------------------------- + /** + * Checks that the specified value is valid for this field. + *

+ * This validates that the value is within the outer range of valid values + * returned by {@link #range()}. + * + * @param value the value to check + * @return the value that was passed in + */ + public long checkValidValue(long value) { + return range().checkValidValue(value, this); + } + + /** + * Checks that the specified value is valid and fits in an {@code int}. + *

+ * This validates that the value is within the outer range of valid values + * returned by {@link #range()}. + * It also checks that all valid values are within the bounds of an {@code int}. + * + * @param value the value to check + * @return the value that was passed in + */ + public int checkValidIntValue(long value) { + return range().checkValidIntValue(value, this); + } + + //----------------------------------------------------------------------- + @Override + public boolean doIsSupported(TemporalAccessor temporal) { + return temporal.isSupported(this); + } + + @Override + public ValueRange doRange(TemporalAccessor temporal) { + return temporal.range(this); + } + + @Override + public long doGet(TemporalAccessor temporal) { + return temporal.getLong(this); + } + + @SuppressWarnings("unchecked") + @Override + public R doWith(R temporal, long newValue) { + return (R) temporal.with(this, newValue); + } + + //----------------------------------------------------------------------- + @Override + public boolean resolve(DateTimeBuilder builder, long value) { + return false; // resolve implemented in builder + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + return getName(); + } + +} diff --git a/src/share/classes/java/time/temporal/ChronoLocalDate.java b/src/share/classes/java/time/temporal/ChronoLocalDate.java new file mode 100644 index 0000000000000000000000000000000000000000..4701296bd7ad4638b3a77103dcab0f5fa4634b88 --- /dev/null +++ b/src/share/classes/java/time/temporal/ChronoLocalDate.java @@ -0,0 +1,691 @@ +/* + * 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) 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 java.time.temporal; + +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoUnit.DAYS; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Comparator; +import java.util.Objects; + +/** + * A date without time-of-day or time-zone in an arbitrary chronology, intended + * for advanced globalization use cases. + *

+ * Most applications should declare method signatures, fields and variables + * as {@link LocalDate}, not this interface. + *

+ * A {@code ChronoLocalDate} is the abstract representation of a date where the + * {@code Chrono chronology}, or calendar system, is pluggable. + * The date is defined in terms of fields expressed by {@link TemporalField}, + * where most common implementations are defined in {@link ChronoField}. + * The chronology defines how the calendar system operates and the meaning of + * the standard fields. + * + *

When to use this interface

+ * The design of the API encourages the use of {@code LocalDate} rather than this + * interface, even in the case where the application needs to deal with multiple + * calendar systems. The rationale for this is explored in the following documentation. + *

+ * The primary use case where this interface should be used is where the generic + * type parameter {@code } is fully defined as a specific chronology. + * In that case, the assumptions of that chronology are known at development + * time and specified in the code. + *

+ * When the chronology is defined in the generic type parameter as ? or otherwise + * unknown at development time, the rest of the discussion below applies. + *

+ * To emphasize the point, declaring a method signature, field or variable as this + * interface type can initially seem like the sensible way to globalize an application, + * however it is usually the wrong approach. + * As such, it should be considered an application-wide architectural decision to choose + * to use this interface as opposed to {@code LocalDate}. + * + *

Architectural issues to consider

+ * These are some of the points that must be considered before using this interface + * throughout an application. + *

+ * 1) Applications using this interface, as opposed to using just {@code LocalDate}, + * face a significantly higher probability of bugs. This is because the calendar system + * in use is not known at development time. A key cause of bugs is where the developer + * applies assumptions from their day-to-day knowledge of the ISO calendar system + * to code that is intended to deal with any arbitrary calendar system. + * The section below outlines how those assumptions can cause problems + * The primary mechanism for reducing this increased risk of bugs is a strong code review process. + * This should also be considered a extra cost in maintenance for the lifetime of the code. + *

+ * 2) This interface does not enforce immutability of implementations. + * While the implementation notes indicate that all implementations must be immutable + * there is nothing in the code or type system to enforce this. Any method declared + * to accept a {@code ChronoLocalDate} could therefore be passed a poorly or + * maliciously written mutable implementation. + *

+ * 3) Applications using this interface must consider the impact of eras. + * {@code LocalDate} shields users from the concept of eras, by ensuring that {@code getYear()} + * returns the proleptic year. That decision ensures that developers can think of + * {@code LocalDate} instances as consisting of three fields - year, month-of-year and day-of-month. + * By contrast, users of this interface must think of dates as consisting of four fields - + * era, year-of-era, month-of-year and day-of-month. The extra era field is frequently + * forgotten, yet it is of vital importance to dates in an arbitrary calendar system. + * For example, in the Japanese calendar system, the era represents the reign of an Emperor. + * Whenever one reign ends and another starts, the year-of-era is reset to one. + *

+ * 4) The only agreed international standard for passing a date between two systems + * is the ISO-8601 standard which requires the ISO calendar system. Using this interface + * throughout the application will inevitably lead to the requirement to pass the date + * across a network or component boundary, requiring an application specific protocol or format. + *

+ * 5) Long term persistence, such as a database, will almost always only accept dates in the + * ISO-8601 calendar system (or the related Julian-Gregorian). Passing around dates in other + * calendar systems increases the complications of interacting with persistence. + *

+ * 6) Most of the time, passing a {@code ChronoLocalDate} throughout an application + * is unnecessary, as discussed in the last section below. + * + *

False assumptions causing bugs in multi-calendar system code

+ * As indicated above, there are many issues to consider when try to use and manipulate a + * date in an arbitrary calendar system. These are some of the key issues. + *

+ * Code that queries the day-of-month and assumes that the value will never be more than + * 31 is invalid. Some calendar systems have more than 31 days in some months. + *

+ * Code that adds 12 months to a date and assumes that a year has been added is invalid. + * Some calendar systems have a different number of months, such as 13 in the Coptic or Ethiopic. + *

+ * Code that adds one month to a date and assumes that the month-of-year value will increase + * by one or wrap to the next year is invalid. Some calendar systems have a variable number + * of months in a year, such as the Hebrew. + *

+ * Code that adds one month, then adds a second one month and assumes that the day-of-month + * will remain close to its original value is invalid. Some calendar systems have a large difference + * between the length of the longest month and the length of the shortest month. + * For example, the Coptic or Ethiopic have 12 months of 30 days and 1 month of 5 days. + *

+ * Code that adds seven days and assumes that a week has been added is invalid. + * Some calendar systems have weeks of other than seven days, such as the French Revolutionary. + *

+ * Code that assumes that because the year of {@code date1} is greater than the year of {@code date2} + * then {@code date1} is after {@code date2} is invalid. This is invalid for all calendar systems + * when referring to the year-of-era, and especially untrue of the Japanese calendar system + * where the year-of-era restarts with the reign of every new Emperor. + *

+ * Code that treats month-of-year one and day-of-month one as the start of the year is invalid. + * Not all calendar systems start the year when the month value is one. + *

+ * In general, manipulating a date, and even querying a date, is wide open to bugs when the + * calendar system is unknown at development time. This is why it is essential that code using + * this interface is subjected to additional code reviews. It is also why an architectural + * decision to avoid this interface type is usually the correct one. + * + *

Using LocalDate instead

+ * The primary alternative to using this interface throughout your application is as follows. + *

    + *
  • Declare all method signatures referring to dates in terms of {@code LocalDate}. + *
  • Either store the chronology (calendar system) in the user profile or lookup + * the chronology from the user locale + *
  • Convert the ISO {@code LocalDate} to and from the user's preferred calendar system during + * printing and parsing + *

+ * This approach treats the problem of globalized calendar systems as a localization issue + * and confines it to the UI layer. This approach is in keeping with other localization + * issues in the java platform. + *

+ * As discussed above, performing calculations on a date where the rules of the calendar system + * are pluggable requires skill and is not recommended. + * Fortunately, the need to perform calculations on a date in an arbitrary calendar system + * is extremely rare. For example, it is highly unlikely that the business rules of a library + * book rental scheme will allow rentals to be for one month, where meaning of the month + * is dependent on the user's preferred calendar system. + *

+ * A key use case for calculations on a date in an arbitrary calendar system is producing + * a month-by-month calendar for display and user interaction. Again, this is a UI issue, + * and use of this interface solely within a few methods of the UI layer may be justified. + *

+ * In any other part of the system, where a date must be manipulated in a calendar system + * other than ISO, the use case will generally specify the calendar system to use. + * For example, an application may need to calculate the next Islamic or Hebrew holiday + * which may require manipulating the date. + * This kind of use case can be handled as follows: + *

    + *
  • start from the ISO {@code LocalDate} being passed to the method + *
  • convert the date to the alternate calendar system, which for this use case is known + * rather than arbitrary + *
  • perform the calculation + *
  • convert back to {@code LocalDate} + *

+ * Developers writing low-level frameworks or libraries should also avoid this interface. + * Instead, one of the two general purpose access interfaces should be used. + * Use {@link TemporalAccessor} if read-only access is required, or use {@link Temporal} + * if read-write access is required. + * + *

Specification for implementors

+ * This interface must be implemented with care to ensure other classes operate correctly. + * All implementations that can be instantiated must be final, immutable and thread-safe. + * Subclasses should be Serializable wherever possible. + *

+ * Additional calendar systems may be added to the system. + * See {@link Chrono} for more details. + * + * @param the chronology of this date + * @since 1.8 + */ +public interface ChronoLocalDate> + extends Temporal, TemporalAdjuster, Comparable> { + + /** + * Comparator for two {@code ChronoLocalDate}s ignoring the chronology. + *

+ * This comparator differs from the comparison in {@link #compareTo} in that it + * only compares the underlying date and not the chronology. + * This allows dates in different calendar systems to be compared based + * on the time-line position. + * This is equivalent to using {@code Long.compare(date1.toEpochDay(), date2.toEpochDay())}. + * + * @see #isAfter + * @see #isBefore + * @see #isEqual + */ + public static final Comparator> DATE_COMPARATOR = + new Comparator>() { + @Override + public int compare(ChronoLocalDate date1, ChronoLocalDate date2) { + return Long.compare(date1.toEpochDay(), date2.toEpochDay()); + } + }; + + //----------------------------------------------------------------------- + /** + * Gets the chronology of this date. + *

+ * The {@code Chrono} represents the calendar system in use. + * The era and other fields in {@link ChronoField} are defined by the chronology. + * + * @return the chronology, not null + */ + C getChrono(); + + /** + * Gets the era, as defined by the chronology. + *

+ * The era is, conceptually, the largest division of the time-line. + * Most calendar systems have a single epoch dividing the time-line into two eras. + * However, some have multiple eras, such as one for the reign of each leader. + * The exact meaning is determined by the {@code Chrono}. + *

+ * All correctly implemented {@code Era} classes are singletons, thus it + * is valid code to write {@code date.getEra() == SomeChrono.ERA_NAME)}. + *

+ * This default implementation uses {@link Chrono#eraOf(int)}. + * + * @return the chronology specific era constant applicable at this date, not null + */ + public default Era getEra() { + return getChrono().eraOf(get(ERA)); + } + + /** + * Checks if the year is a leap year, as defined by the calendar system. + *

+ * A leap-year is a year of a longer length than normal. + * The exact meaning is determined by the chronology with the constraint that + * a leap-year must imply a year-length longer than a non leap-year. + *

+ * This default implementation uses {@link Chrono#isLeapYear(long)}. + * + * @return true if this date is in a leap year, false otherwise + */ + public default boolean isLeapYear() { + return getChrono().isLeapYear(getLong(YEAR)); + } + + /** + * Returns the length of the month represented by this date, as defined by the calendar system. + *

+ * This returns the length of the month in days. + * + * @return the length of the month in days + */ + int lengthOfMonth(); + + /** + * Returns the length of the year represented by this date, as defined by the calendar system. + *

+ * This returns the length of the year in days. + *

+ * The default implementation uses {@link #isLeapYear()} and returns 365 or 366. + * + * @return the length of the year in days + */ + public default int lengthOfYear() { + return (isLeapYear() ? 366 : 365); + } + + @Override + public default boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return ((ChronoField) field).isDateField(); + } + return field != null && field.doIsSupported(this); + } + + //----------------------------------------------------------------------- + // override for covariant return type + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public default ChronoLocalDate with(TemporalAdjuster adjuster) { + return getChrono().ensureChronoLocalDate(Temporal.super.with(adjuster)); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public default ChronoLocalDate with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return getChrono().ensureChronoLocalDate(field.doWith(this, newValue)); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public default ChronoLocalDate plus(TemporalAdder adder) { + return getChrono().ensureChronoLocalDate(Temporal.super.plus(adder)); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public default ChronoLocalDate plus(long amountToAdd, TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + throw new DateTimeException("Unsupported unit: " + unit.getName()); + } + return getChrono().ensureChronoLocalDate(unit.doPlus(this, amountToAdd)); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public default ChronoLocalDate minus(TemporalSubtractor subtractor) { + return getChrono().ensureChronoLocalDate(Temporal.super.minus(subtractor)); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public default ChronoLocalDate minus(long amountToSubtract, TemporalUnit unit) { + return getChrono().ensureChronoLocalDate(Temporal.super.minus(amountToSubtract, unit)); + } + + //----------------------------------------------------------------------- + /** + * Queries this date using the specified query. + *

+ * This queries this date using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @SuppressWarnings("unchecked") + @Override + public default R query(TemporalQuery query) { + if (query == Queries.chrono()) { + return (R) getChrono(); + } + if (query == Queries.precision()) { + return (R) DAYS; + } + // inline TemporalAccessor.super.query(query) as an optimization + if (query == Queries.zoneId() || query == Queries.zone() || query == Queries.offset()) { + return null; + } + return query.queryFrom(this); + } + + /** + * Adjusts the specified temporal object to have the same date as this object. + *

+ * This returns a temporal object of the same observable type as the input + * with the date changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * passing {@link ChronoField#EPOCH_DAY} as the field. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisLocalDate.adjustInto(temporal);
+     *   temporal = temporal.with(thisLocalDate);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public default Temporal adjustInto(Temporal temporal) { + return temporal.with(EPOCH_DAY, toEpochDay()); + } + + /** + * Calculates the period between this date and another date in + * terms of the specified unit. + *

+ * This calculates the period between two dates in terms of a single unit. + * The start and end points are {@code this} and the specified date. + * The result will be negative if the end is before the start. + * The {@code Temporal} passed to this method must be a + * {@code ChronoLocalDate} in the same chronology. + * The calculation returns a whole number, representing the number of + * complete units between the two dates. + * For example, the period in days between two dates can be calculated + * using {@code startDate.periodUntil(endDate, DAYS)}. + *

+ * This method operates in association with {@link TemporalUnit#between}. + * The result of this method is a {@code long} representing the amount of + * the specified unit. By contrast, the result of {@code between} is an + * object that can be used directly in addition/subtraction: + *

+     *   long period = start.periodUntil(end, MONTHS);   // this method
+     *   dateTime.plus(MONTHS.between(start, end));      // use in plus/minus
+     * 
+ *

+ * The calculation is implemented in this method for {@link ChronoUnit}. + * The units {@code DAYS}, {@code WEEKS}, {@code MONTHS}, {@code YEARS}, + * {@code DECADES}, {@code CENTURIES}, {@code MILLENNIA} and {@code ERAS} + * should be supported by all implementations. + * Other {@code ChronoUnit} values will throw an exception. + *

+ * If the unit is not a {@code ChronoUnit}, then the result of this method + * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)} + * passing {@code this} as the first argument and the input temporal as + * the second argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param endDate the end date, which must be a {@code ChronoLocalDate} + * in the same chronology, not null + * @param unit the unit to measure the period in, not null + * @return the amount of the period between this date and the end date + * @throws DateTimeException if the period cannot be calculated + * @throws ArithmeticException if numeric overflow occurs + */ + @Override // override for Javadoc + public abstract long periodUntil(Temporal endDate, TemporalUnit unit); + + //----------------------------------------------------------------------- + /** + * Returns a date-time formed from this date at the specified time. + *

+ * This merges the two objects - {@code this} and the specified time - + * to form an instance of {@code ChronoLocalDateTime}. + *

+ * This instance is immutable and unaffected by this method call. + *

+ * This default implementation creates the date-time. + * + * @param localTime the local time to use, not null + * @return the local date-time formed from this date and the specified time, not null + */ + public default ChronoLocalDateTime atTime(LocalTime localTime) { + return Chrono.dateTime(this, localTime); + } + + //----------------------------------------------------------------------- + /** + * Converts this date to the Epoch Day. + *

+ * The {@link ChronoField#EPOCH_DAY Epoch Day count} is a simple + * incrementing count of days where day 0 is 1970-01-01 (ISO). + * This definition is the same for all chronologies, enabling conversion. + *

+ * This default implementation queries the {@code EPOCH_DAY} field. + * + * @return the Epoch Day equivalent to this date + */ + public default long toEpochDay() { + return getLong(EPOCH_DAY); + } + + //----------------------------------------------------------------------- + /** + * Compares this date to another date, including the chronology. + *

+ * The comparison is based first on the underlying time-line date, then + * on the chronology. + * It is "consistent with equals", as defined by {@link Comparable}. + *

+ * For example, the following is the comparator order: + *

    + *
  1. {@code 2012-12-03 (ISO)}
  2. + *
  3. {@code 2012-12-04 (ISO)}
  4. + *
  5. {@code 2555-12-04 (ThaiBuddhist)}
  6. + *
  7. {@code 2012-12-05 (ISO)}
  8. + *
+ * Values #2 and #3 represent the same date on the time-line. + * When two values represent the same date, the chronology ID is compared to distinguish them. + * This step is needed to make the ordering "consistent with equals". + *

+ * If all the date objects being compared are in the same chronology, then the + * additional chronology stage is not required and only the local date is used. + * To compare the dates of two {@code TemporalAccessor} instances, including dates + * in two different chronologies, use {@link ChronoField#EPOCH_DAY} as a comparator. + *

+ * This default implementation performs the comparison defined above. + * + * @param other the other date to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + @Override + public default int compareTo(ChronoLocalDate other) { + int cmp = Long.compare(toEpochDay(), other.toEpochDay()); + if (cmp == 0) { + cmp = getChrono().compareTo(other.getChrono()); + } + return cmp; + } + + /** + * Checks if this date is after the specified date ignoring the chronology. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the underlying date and not the chronology. + * This allows dates in different calendar systems to be compared based + * on the time-line position. + * This is equivalent to using {@code date1.toEpochDay() > date2.toEpochDay()}. + *

+ * This default implementation performs the comparison based on the epoch-day. + * + * @param other the other date to compare to, not null + * @return true if this is after the specified date + */ + public default boolean isAfter(ChronoLocalDate other) { + return this.toEpochDay() > other.toEpochDay(); + } + + /** + * Checks if this date is before the specified date ignoring the chronology. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the underlying date and not the chronology. + * This allows dates in different calendar systems to be compared based + * on the time-line position. + * This is equivalent to using {@code date1.toEpochDay() < date2.toEpochDay()}. + *

+ * This default implementation performs the comparison based on the epoch-day. + * + * @param other the other date to compare to, not null + * @return true if this is before the specified date + */ + public default boolean isBefore(ChronoLocalDate other) { + return this.toEpochDay() < other.toEpochDay(); + } + + /** + * Checks if this date is equal to the specified date ignoring the chronology. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the underlying date and not the chronology. + * This allows dates in different calendar systems to be compared based + * on the time-line position. + * This is equivalent to using {@code date1.toEpochDay() == date2.toEpochDay()}. + *

+ * This default implementation performs the comparison based on the epoch-day. + * + * @param other the other date to compare to, not null + * @return true if the underlying date is equal to the specified date + */ + public default boolean isEqual(ChronoLocalDate other) { + return this.toEpochDay() == other.toEpochDay(); + } + + //----------------------------------------------------------------------- + /** + * Checks if this date is equal to another date, including the chronology. + *

+ * Compares this date with another ensuring that the date and chronology are the same. + *

+ * 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 + boolean equals(Object obj); + + /** + * A hash code for this date. + * + * @return a suitable hash code + */ + @Override + int hashCode(); + + //----------------------------------------------------------------------- + /** + * Outputs this date as a {@code String}. + *

+ * The output will include the full local date and the chronology ID. + * + * @return the formatted date, not null + */ + @Override + String toString(); + + /** + * Outputs this date as a {@code String} using the formatter. + *

+ * The default implementation must behave as follows: + *

+     *  return formatter.print(this);
+     * 
+ * + * @param formatter the formatter to use, not null + * @return the formatted date string, not null + * @throws DateTimeException if an error occurs during printing + */ + public default String toString(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.print(this); + } + +} diff --git a/src/share/classes/java/time/temporal/ChronoLocalDateTime.java b/src/share/classes/java/time/temporal/ChronoLocalDateTime.java new file mode 100644 index 0000000000000000000000000000000000000000..1fc12d8e07fa0c9e910e7b5cdf7148a5263f8c68 --- /dev/null +++ b/src/share/classes/java/time/temporal/ChronoLocalDateTime.java @@ -0,0 +1,499 @@ +/* + * 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 java.time.temporal; + +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.NANO_OF_DAY; +import static java.time.temporal.ChronoUnit.NANOS; + +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.zone.ZoneRules; +import java.util.Comparator; +import java.util.Objects; + +/** + * A date-time without a time-zone in an arbitrary chronology, intended + * for advanced globalization use cases. + *

+ * Most applications should declare method signatures, fields and variables + * as {@link LocalDateTime}, not this interface. + *

+ * A {@code ChronoLocalDateTime} is the abstract representation of a local date-time + * where the {@code Chrono chronology}, or calendar system, is pluggable. + * The date-time is defined in terms of fields expressed by {@link TemporalField}, + * where most common implementations are defined in {@link ChronoField}. + * The chronology defines how the calendar system operates and the meaning of + * the standard fields. + * + *

When to use this interface

+ * The design of the API encourages the use of {@code LocalDateTime} rather than this + * interface, even in the case where the application needs to deal with multiple + * calendar systems. The rationale for this is explored in detail in {@link ChronoLocalDate}. + *

+ * Ensure that the discussion in {@code ChronoLocalDate} has been read and understood + * before using this interface. + * + *

Specification for implementors

+ * This interface must be implemented with care to ensure other classes operate correctly. + * All implementations that can be instantiated must be final, immutable and thread-safe. + * Subclasses should be Serializable wherever possible. + * + * @param the chronology of this date-time + * @since 1.8 + */ +public interface ChronoLocalDateTime> + extends Temporal, TemporalAdjuster, Comparable> { + + /** + * Comparator for two {@code ChronoLocalDateTime} instances ignoring the chronology. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the underlying date and not the chronology. + * This allows dates in different calendar systems to be compared based + * on the time-line position. + * + * @see #isAfter + * @see #isBefore + * @see #isEqual + */ + Comparator> DATE_TIME_COMPARATOR = + new Comparator>() { + @Override + public int compare(ChronoLocalDateTime datetime1, ChronoLocalDateTime datetime2) { + int cmp = Long.compare(datetime1.getDate().toEpochDay(), datetime2.getDate().toEpochDay()); + if (cmp == 0) { + cmp = Long.compare(datetime1.getTime().toNanoOfDay(), datetime2.getTime().toNanoOfDay()); + } + return cmp; + } + }; + + /** + * Gets the local date part of this date-time. + *

+ * This returns a local date with the same year, month and day + * as this date-time. + * + * @return the date part of this date-time, not null + */ + ChronoLocalDate getDate() ; + + /** + * Gets the local time part of this date-time. + *

+ * This returns a local time with the same hour, minute, second and + * nanosecond as this date-time. + * + * @return the time part of this date-time, not null + */ + LocalTime getTime(); + + + //----------------------------------------------------------------------- + // override for covariant return type + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public default ChronoLocalDateTime with(TemporalAdjuster adjuster) { + return getDate().getChrono().ensureChronoLocalDateTime(Temporal.super.with(adjuster)); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + ChronoLocalDateTime with(TemporalField field, long newValue); + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public default ChronoLocalDateTime plus(TemporalAdder adder) { + return getDate().getChrono().ensureChronoLocalDateTime(Temporal.super.plus(adder)); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + ChronoLocalDateTime plus(long amountToAdd, TemporalUnit unit); + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public default ChronoLocalDateTime minus(TemporalSubtractor subtractor) { + return getDate().getChrono().ensureChronoLocalDateTime(Temporal.super.minus(subtractor)); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public default ChronoLocalDateTime minus(long amountToSubtract, TemporalUnit unit) { + return getDate().getChrono().ensureChronoLocalDateTime(Temporal.super.minus(amountToSubtract, unit)); + } + + //----------------------------------------------------------------------- + /** + * Queries this date-time using the specified query. + *

+ * This queries this date-time using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link java.time.temporal.TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @SuppressWarnings("unchecked") + @Override + public default R query(TemporalQuery query) { + if (query == Queries.chrono()) { + return (R) getDate().getChrono(); + } + if (query == Queries.precision()) { + return (R) NANOS; + } + // inline TemporalAccessor.super.query(query) as an optimization + if (query == Queries.zoneId() || query == Queries.zone() || query == Queries.offset()) { + return null; + } + return query.queryFrom(this); + } + + /** + * Adjusts the specified temporal object to have the same date and time as this object. + *

+ * This returns a temporal object of the same observable type as the input + * with the date and time changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * twice, passing {@link ChronoField#EPOCH_DAY} and + * {@link ChronoField#NANO_OF_DAY} as the fields. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisLocalDateTime.adjustInto(temporal);
+     *   temporal = temporal.with(thisLocalDateTime);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public default Temporal adjustInto(Temporal temporal) { + return temporal + .with(EPOCH_DAY, getDate().toEpochDay()) + .with(NANO_OF_DAY, getTime().toNanoOfDay()); + } + + //----------------------------------------------------------------------- + /** + * Returns a zoned date-time formed from this date-time and the specified time-zone. + *

+ * This creates a zoned date-time matching the input date-time as closely as possible. + * Time-zone rules, such as daylight savings, mean that not every local date-time + * is valid for the specified zone, thus the local date-time may be adjusted. + *

+ * The local date-time is resolved to a single instant on the time-line. + * This is achieved by finding a valid offset from UTC/Greenwich for the local + * date-time as defined by the {@link ZoneRules rules} of the zone ID. + *

+ * In most cases, there is only one valid offset for a local date-time. + * In the case of an overlap, where clocks are set back, there are two valid offsets. + * This method uses the earlier offset typically corresponding to "summer". + *

+ * In the case of a gap, where clocks jump forward, there is no valid offset. + * Instead, the local date-time is adjusted to be later by the length of the gap. + * For a typical one hour daylight savings change, the local date-time will be + * moved one hour later into the offset typically corresponding to "summer". + *

+ * To obtain the later offset during an overlap, call + * {@link ChronoZonedDateTime#withLaterOffsetAtOverlap()} on the result of this method. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param zone the time-zone to use, not null + * @return the zoned date-time formed from this date-time, not null + */ + ChronoZonedDateTime atZone(ZoneId zone); + + //----------------------------------------------------------------------- + /** + * Converts this date-time to an {@code Instant}. + *

+ * This combines this local date-time and the specified offset to form + * an {@code Instant}. + *

+ * 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 an {@code Instant} representing the same instant, not null + */ + public default Instant toInstant(ZoneOffset offset) { + return Instant.ofEpochSecond(toEpochSecond(offset), getTime().getNano()); + } + + /** + * Converts this date-time to the number of seconds from the epoch + * of 1970-01-01T00:00:00Z. + *

+ * 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. + *

+ * 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 default long toEpochSecond(ZoneOffset offset) { + Objects.requireNonNull(offset, "offset"); + long epochDay = getDate().toEpochDay(); + long secs = epochDay * 86400 + getTime().toSecondOfDay(); + secs -= offset.getTotalSeconds(); + return secs; + } + + //----------------------------------------------------------------------- + /** + * Compares this date-time to another date-time, including the chronology. + *

+ * The comparison is based first on the underlying time-line date-time, then + * on the chronology. + * It is "consistent with equals", as defined by {@link Comparable}. + *

+ * For example, the following is the comparator order: + *

    + *
  1. {@code 2012-12-03T12:00 (ISO)}
  2. + *
  3. {@code 2012-12-04T12:00 (ISO)}
  4. + *
  5. {@code 2555-12-04T12:00 (ThaiBuddhist)}
  6. + *
  7. {@code 2012-12-05T12:00 (ISO)}
  8. + *
+ * Values #2 and #3 represent the same date-time on the time-line. + * When two values represent the same date-time, the chronology ID is compared to distinguish them. + * This step is needed to make the ordering "consistent with equals". + *

+ * If all the date-time objects being compared are in the same chronology, then the + * additional chronology stage is not required and only the local date-time is used. + *

+ * This default implementation performs the comparison defined above. + * + * @param other the other date-time to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + @Override + public default int compareTo(ChronoLocalDateTime other) { + int cmp = getDate().compareTo(other.getDate()); + if (cmp == 0) { + cmp = getTime().compareTo(other.getTime()); + if (cmp == 0) { + cmp = getDate().getChrono().compareTo(other.getDate().getChrono()); + } + } + return cmp; + } + + /** + * Checks if this date-time is after the specified date-time ignoring the chronology. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the underlying date-time and not the chronology. + * This allows dates in different calendar systems to be compared based + * on the time-line position. + *

+ * This default implementation performs the comparison based on the epoch-day + * and nano-of-day. + * + * @param other the other date-time to compare to, not null + * @return true if this is after the specified date-time + */ + public default boolean isAfter(ChronoLocalDateTime other) { + long thisEpDay = this.getDate().toEpochDay(); + long otherEpDay = other.getDate().toEpochDay(); + return thisEpDay > otherEpDay || + (thisEpDay == otherEpDay && this.getTime().toNanoOfDay() > other.getTime().toNanoOfDay()); + } + + /** + * Checks if this date-time is before the specified date-time ignoring the chronology. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the underlying date-time and not the chronology. + * This allows dates in different calendar systems to be compared based + * on the time-line position. + *

+ * This default implementation performs the comparison based on the epoch-day + * and nano-of-day. + * + * @param other the other date-time to compare to, not null + * @return true if this is before the specified date-time + */ + public default boolean isBefore(ChronoLocalDateTime other) { + long thisEpDay = this.getDate().toEpochDay(); + long otherEpDay = other.getDate().toEpochDay(); + return thisEpDay < otherEpDay || + (thisEpDay == otherEpDay && this.getTime().toNanoOfDay() < other.getTime().toNanoOfDay()); + } + + /** + * Checks if this date-time is equal to the specified date-time ignoring the chronology. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the underlying date and time and not the chronology. + * This allows date-times in different calendar systems to be compared based + * on the time-line position. + *

+ * This default implementation performs the comparison based on the epoch-day + * and nano-of-day. + * + * @param other the other date-time to compare to, not null + * @return true if the underlying date-time is equal to the specified date-time on the timeline + */ + public default boolean isEqual(ChronoLocalDateTime other) { + // Do the time check first, it is cheaper than computing EPOCH day. + return this.getTime().toNanoOfDay() == other.getTime().toNanoOfDay() && + this.getDate().toEpochDay() == other.getDate().toEpochDay(); + } + + /** + * Checks if this date-time is equal to another date-time, including the chronology. + *

+ * Compares this date-time with another ensuring that the date-time and chronology are the same. + * + * @param obj the object to check, null returns false + * @return true if this is equal to the other date + */ + @Override + boolean equals(Object obj); + + /** + * A hash code for this date-time. + * + * @return a suitable hash code + */ + @Override + int hashCode(); + + //----------------------------------------------------------------------- + /** + * Outputs this date-time as a {@code String}. + *

+ * The output will include the full local date-time and the chronology ID. + * + * @return a string representation of this date-time, not null + */ + @Override + String toString(); + + /** + * Outputs this date-time as a {@code String} using the formatter. + *

+ * The default implementation must behave as follows: + *

+     *  return formatter.print(this);
+     * 
+ * + * @param formatter the formatter to use, not null + * @return the formatted date-time string, not null + * @throws DateTimeException if an error occurs during printing + */ + public default String toString(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.print(this); + } +} diff --git a/src/share/classes/java/time/temporal/ChronoLocalDateTimeImpl.java b/src/share/classes/java/time/temporal/ChronoLocalDateTimeImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..4baeec053f2624d79e9309be7e5d3482a6e033fa --- /dev/null +++ b/src/share/classes/java/time/temporal/ChronoLocalDateTimeImpl.java @@ -0,0 +1,426 @@ +/* + * 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 java.time.temporal; + +import static java.time.temporal.ChronoField.EPOCH_DAY; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.DateTimeException; +import java.time.LocalTime; +import java.time.ZoneId; +import java.util.Objects; + +/** + * A date-time without a time-zone for the calendar neutral API. + *

+ * {@code ChronoLocalDateTime} is an immutable date-time object that represents a date-time, often + * viewed as year-month-day-hour-minute-second. This object can also access other + * fields such as day-of-year, day-of-week and week-of-year. + *

+ * This class stores all date and time fields, to a precision of nanoseconds. + * It does not store or represent a time-zone. For example, the value + * "2nd October 2007 at 13:45.30.123456789" can be stored in an {@code ChronoLocalDateTime}. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @param the chronology of this date + * @since 1.8 + */ +final class ChronoLocalDateTimeImpl> + implements ChronoLocalDateTime, Temporal, TemporalAdjuster, Serializable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = 4556003607393004514L; + /** + * 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; + /** + * Nanos per second. + */ + static final long NANOS_PER_SECOND = 1000_000_000L; + /** + * Nanos per minute. + */ + static final long NANOS_PER_MINUTE = NANOS_PER_SECOND * SECONDS_PER_MINUTE; + /** + * Nanos per hour. + */ + static final long NANOS_PER_HOUR = NANOS_PER_MINUTE * MINUTES_PER_HOUR; + /** + * Nanos per day. + */ + static final long NANOS_PER_DAY = NANOS_PER_HOUR * HOURS_PER_DAY; + + /** + * The date part. + */ + private final ChronoLocalDate date; + /** + * The time part. + */ + private final LocalTime time; + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code ChronoLocalDateTime} 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 + */ + static > ChronoLocalDateTimeImpl of(ChronoLocalDate date, LocalTime time) { + return new ChronoLocalDateTimeImpl<>(date, time); + } + + /** + * Constructor. + * + * @param date the date part of the date-time, not null + * @param time the time part of the date-time, not null + */ + private ChronoLocalDateTimeImpl(ChronoLocalDate date, LocalTime time) { + Objects.requireNonNull(date, "date"); + Objects.requireNonNull(time, "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 ChronoLocalDateTimeImpl with(Temporal newDate, LocalTime newTime) { + if (date == newDate && time == newTime) { + return this; + } + // Validate that the new Temporal is a ChronoLocalDate (and not something else) + ChronoLocalDate cd = date.getChrono().ensureChronoLocalDate(newDate); + return new ChronoLocalDateTimeImpl<>(cd, newTime); + } + + //----------------------------------------------------------------------- + @Override + public ChronoLocalDate getDate() { + return date; + } + + @Override + public LocalTime getTime() { + return time; + } + + //----------------------------------------------------------------------- + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + return f.isDateField() || f.isTimeField(); + } + return field != null && field.doIsSupported(this); + } + + @Override + public ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + return (f.isTimeField() ? time.range(field) : date.range(field)); + } + return field.doRange(this); + } + + @Override + public int get(TemporalField field) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + return (f.isTimeField() ? time.get(field) : date.get(field)); + } + return range(field).checkValidIntValue(getLong(field), field); + } + + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + return (f.isTimeField() ? time.getLong(field) : date.getLong(field)); + } + return field.doGet(this); + } + + //----------------------------------------------------------------------- + @SuppressWarnings("unchecked") + @Override + public ChronoLocalDateTimeImpl with(TemporalAdjuster adjuster) { + if (adjuster instanceof ChronoLocalDate) { + // The Chrono is checked in with(date,time) + return with((ChronoLocalDate) adjuster, time); + } else if (adjuster instanceof LocalTime) { + return with(date, (LocalTime) adjuster); + } else if (adjuster instanceof ChronoLocalDateTimeImpl) { + return date.getChrono().ensureChronoLocalDateTime((ChronoLocalDateTimeImpl) adjuster); + } + return date.getChrono().ensureChronoLocalDateTime((ChronoLocalDateTimeImpl) adjuster.adjustInto(this)); + } + + @Override + public ChronoLocalDateTimeImpl with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + if (f.isTimeField()) { + return with(date, time.with(field, newValue)); + } else { + return with(date.with(field, newValue), time); + } + } + return date.getChrono().ensureChronoLocalDateTime(field.doWith(this, newValue)); + } + + //----------------------------------------------------------------------- + @Override + public ChronoLocalDateTimeImpl plus(long amountToAdd, TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + ChronoUnit f = (ChronoUnit) unit; + switch (f) { + case NANOS: return plusNanos(amountToAdd); + case MICROS: return plusDays(amountToAdd / MICROS_PER_DAY).plusNanos((amountToAdd % MICROS_PER_DAY) * 1000); + case MILLIS: return plusDays(amountToAdd / MILLIS_PER_DAY).plusNanos((amountToAdd % MILLIS_PER_DAY) * 1000000); + case SECONDS: return plusSeconds(amountToAdd); + case MINUTES: return plusMinutes(amountToAdd); + case HOURS: return plusHours(amountToAdd); + case HALF_DAYS: return plusDays(amountToAdd / 256).plusHours((amountToAdd % 256) * 12); // no overflow (256 is multiple of 2) + } + return with(date.plus(amountToAdd, unit), time); + } + return date.getChrono().ensureChronoLocalDateTime(unit.doPlus(this, amountToAdd)); + } + + private ChronoLocalDateTimeImpl plusDays(long days) { + return with(date.plus(days, ChronoUnit.DAYS), time); + } + + private ChronoLocalDateTimeImpl plusHours(long hours) { + return plusWithOverflow(date, hours, 0, 0, 0); + } + + private ChronoLocalDateTimeImpl plusMinutes(long minutes) { + return plusWithOverflow(date, 0, minutes, 0, 0); + } + + ChronoLocalDateTimeImpl plusSeconds(long seconds) { + return plusWithOverflow(date, 0, 0, seconds, 0); + } + + private ChronoLocalDateTimeImpl plusNanos(long nanos) { + return plusWithOverflow(date, 0, 0, 0, nanos); + } + + //----------------------------------------------------------------------- + private ChronoLocalDateTimeImpl plusWithOverflow(ChronoLocalDate newDate, long hours, long minutes, long seconds, long nanos) { + // 9223372036854775808 long, 2147483648 int + if ((hours | minutes | seconds | nanos) == 0) { + return with(newDate, time); + } + long totDays = nanos / NANOS_PER_DAY + // max/24*60*60*1B + seconds / SECONDS_PER_DAY + // max/24*60*60 + minutes / MINUTES_PER_DAY + // max/24*60 + hours / HOURS_PER_DAY; // max/24 + long totNanos = nanos % NANOS_PER_DAY + // max 86400000000000 + (seconds % SECONDS_PER_DAY) * NANOS_PER_SECOND + // max 86400000000000 + (minutes % MINUTES_PER_DAY) * NANOS_PER_MINUTE + // max 86400000000000 + (hours % HOURS_PER_DAY) * NANOS_PER_HOUR; // max 86400000000000 + long curNoD = time.toNanoOfDay(); // max 86400000000000 + totNanos = totNanos + curNoD; // total 432000000000000 + totDays += Math.floorDiv(totNanos, NANOS_PER_DAY); + long newNoD = Math.floorMod(totNanos, NANOS_PER_DAY); + LocalTime newTime = (newNoD == curNoD ? time : LocalTime.ofNanoOfDay(newNoD)); + return with(newDate.plus(totDays, ChronoUnit.DAYS), newTime); + } + + //----------------------------------------------------------------------- + @Override + public ChronoZonedDateTime atZone(ZoneId zone) { + return ChronoZonedDateTimeImpl.ofBest(this, zone, null); + } + + //----------------------------------------------------------------------- + @Override + public long periodUntil(Temporal endDateTime, TemporalUnit unit) { + if (endDateTime instanceof ChronoLocalDateTime == false) { + throw new DateTimeException("Unable to calculate period between objects of two different types"); + } + @SuppressWarnings("unchecked") + ChronoLocalDateTime end = (ChronoLocalDateTime) endDateTime; + if (getDate().getChrono().equals(end.getDate().getChrono()) == false) { + throw new DateTimeException("Unable to calculate period between two different chronologies"); + } + if (unit instanceof ChronoUnit) { + ChronoUnit f = (ChronoUnit) unit; + if (f.isTimeUnit()) { + long amount = end.getLong(EPOCH_DAY) - date.getLong(EPOCH_DAY); + switch (f) { + case NANOS: amount = Math.multiplyExact(amount, NANOS_PER_DAY); break; + case MICROS: amount = Math.multiplyExact(amount, MICROS_PER_DAY); break; + case MILLIS: amount = Math.multiplyExact(amount, MILLIS_PER_DAY); break; + case SECONDS: amount = Math.multiplyExact(amount, SECONDS_PER_DAY); break; + case MINUTES: amount = Math.multiplyExact(amount, MINUTES_PER_DAY); break; + case HOURS: amount = Math.multiplyExact(amount, HOURS_PER_DAY); break; + case HALF_DAYS: amount = Math.multiplyExact(amount, 2); break; + } + return Math.addExact(amount, time.periodUntil(end.getTime(), unit)); + } + ChronoLocalDate endDate = end.getDate(); + if (end.getTime().isBefore(time)) { + endDate = endDate.minus(1, ChronoUnit.DAYS); + } + return date.periodUntil(endDate, unit); + } + return unit.between(this, endDateTime).getAmount(); + } + + //----------------------------------------------------------------------- + private Object writeReplace() { + return new Ser(Ser.CHRONO_LOCAL_DATE_TIME_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(ObjectOutput out) throws IOException { + out.writeObject(date); + out.writeObject(time); + } + + static ChronoLocalDateTime readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + ChronoLocalDate date = (ChronoLocalDate) in.readObject(); + LocalTime time = (LocalTime) in.readObject(); + return date.atTime(time); + } + + //----------------------------------------------------------------------- + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof ChronoLocalDateTime) { + return compareTo((ChronoLocalDateTime) obj) == 0; + } + return false; + } + + @Override + public int hashCode() { + return getDate().hashCode() ^ getTime().hashCode(); + } + + @Override + public String toString() { + return getDate().toString() + 'T' + getTime().toString(); + } + +} diff --git a/src/share/classes/java/time/temporal/ChronoUnit.java b/src/share/classes/java/time/temporal/ChronoUnit.java new file mode 100644 index 0000000000000000000000000000000000000000..33f1318bbf6e849cb5a8196179e04e70ee0643f5 --- /dev/null +++ b/src/share/classes/java/time/temporal/ChronoUnit.java @@ -0,0 +1,288 @@ +/* + * 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 java.time.temporal; + +import java.time.Duration; + +/** + * A standard set of date periods units. + *

+ * This set of units provide unit-based access to manipulate a date, time or date-time. + * The standard set of units can be extended by implementing {@link TemporalUnit}. + *

+ * These units are intended to be applicable in multiple calendar systems. + * For example, most non-ISO calendar systems define units of years, months and days, + * just with slightly different rules. + * The documentation of each unit explains how it operates. + * + *

Specification for implementors

+ * This is a final, immutable and thread-safe enum. + * + * @since 1.8 + */ +public enum ChronoUnit implements TemporalUnit { + + /** + * Unit that represents the concept of a nanosecond, the smallest supported unit of time. + * For the ISO calendar system, it is equal to the 1,000,000,000th part of the second unit. + */ + NANOS("Nanos", Duration.ofNanos(1)), + /** + * Unit that represents the concept of a microsecond. + * For the ISO calendar system, it is equal to the 1,000,000th part of the second unit. + */ + MICROS("Micros", Duration.ofNanos(1000)), + /** + * Unit that represents the concept of a millisecond. + * For the ISO calendar system, it is equal to the 1000th part of the second unit. + */ + MILLIS("Millis", Duration.ofNanos(1000_000)), + /** + * Unit that represents the concept of a second. + * For the ISO calendar system, it is equal to the second in the SI system + * of units, except around a leap-second. + */ + SECONDS("Seconds", Duration.ofSeconds(1)), + /** + * Unit that represents the concept of a minute. + * For the ISO calendar system, it is equal to 60 seconds. + */ + MINUTES("Minutes", Duration.ofSeconds(60)), + /** + * Unit that represents the concept of an hour. + * For the ISO calendar system, it is equal to 60 minutes. + */ + HOURS("Hours", Duration.ofSeconds(3600)), + /** + * Unit that represents the concept of half a day, as used in AM/PM. + * For the ISO calendar system, it is equal to 12 hours. + */ + HALF_DAYS("HalfDays", Duration.ofSeconds(43200)), + /** + * Unit that represents the concept of a day. + * For the ISO calendar system, it is the standard day from midnight to midnight. + * The estimated duration of a day is {@code 24 Hours}. + *

+ * When used with other calendar systems it must correspond to the day defined by + * the rising and setting of the Sun on Earth. It is not required that days begin + * at midnight - when converting between calendar systems, the date should be + * equivalent at midday. + */ + DAYS("Days", Duration.ofSeconds(86400)), + /** + * Unit that represents the concept of a week. + * For the ISO calendar system, it is equal to 7 days. + *

+ * When used with other calendar systems it must correspond to an integral number of days. + */ + WEEKS("Weeks", Duration.ofSeconds(7 * 86400L)), + /** + * Unit that represents the concept of a month. + * For the ISO calendar system, the length of the month varies by month-of-year. + * The estimated duration of a month is one twelfth of {@code 365.2425 Days}. + *

+ * When used with other calendar systems it must correspond to an integral number of days. + */ + MONTHS("Months", Duration.ofSeconds(31556952L / 12)), + /** + * Unit that represents the concept of a year. + * For the ISO calendar system, it is equal to 12 months. + * The estimated duration of a year is {@code 365.2425 Days}. + *

+ * When used with other calendar systems it must correspond to an integral number of days + * or months roughly equal to a year defined by the passage of the Earth around the Sun. + */ + YEARS("Years", Duration.ofSeconds(31556952L)), + /** + * Unit that represents the concept of a decade. + * For the ISO calendar system, it is equal to 10 years. + *

+ * When used with other calendar systems it must correspond to an integral number of days + * and is normally an integral number of years. + */ + DECADES("Decades", Duration.ofSeconds(31556952L * 10L)), + /** + * Unit that represents the concept of a century. + * For the ISO calendar system, it is equal to 100 years. + *

+ * When used with other calendar systems it must correspond to an integral number of days + * and is normally an integral number of years. + */ + CENTURIES("Centuries", Duration.ofSeconds(31556952L * 100L)), + /** + * Unit that represents the concept of a millennium. + * For the ISO calendar system, it is equal to 1000 years. + *

+ * When used with other calendar systems it must correspond to an integral number of days + * and is normally an integral number of years. + */ + MILLENNIA("Millennia", Duration.ofSeconds(31556952L * 1000L)), + /** + * Unit that represents the concept of an era. + * The ISO calendar system doesn't have eras thus it is impossible to add + * an era to a date or date-time. + * The estimated duration of the era is artificially defined as {@code 1,000,00,000 Years}. + *

+ * When used with other calendar systems there are no restrictions on the unit. + */ + ERAS("Eras", Duration.ofSeconds(31556952L * 1000_000_000L)), + /** + * Artificial unit that represents the concept of forever. + * This is primarily used with {@link TemporalField} to represent unbounded fields + * such as the year or era. + * The estimated duration of the era is artificially defined as the largest duration + * supported by {@code Duration}. + */ + FOREVER("Forever", Duration.ofSeconds(Long.MAX_VALUE, 999_999_999)); + + private final String name; + private final Duration duration; + + private ChronoUnit(String name, Duration estimatedDuration) { + this.name = name; + this.duration = estimatedDuration; + } + + //----------------------------------------------------------------------- + @Override + public String getName() { + return name; + } + + //----------------------------------------------------------------------- + /** + * Gets the estimated duration of this unit in the ISO calendar system. + *

+ * All of the units in this class have an estimated duration. + * Days vary due to daylight saving time, while months have different lengths. + * + * @return the estimated duration of this unit, not null + */ + @Override + public Duration getDuration() { + return duration; + } + + /** + * Checks if the duration of the unit is an estimate. + *

+ * All time units in this class are considered to be accurate, while all date + * units in this class are considered to be estimated. + *

+ * This definition ignores leap seconds, but considers that Days vary due to + * daylight saving time and months have different lengths. + * + * @return true if the duration is estimated, false if accurate + */ + @Override + public boolean isDurationEstimated() { + return isDateUnit(); + } + + //----------------------------------------------------------------------- + /** + * Checks if this unit is a date unit. + * + * @return true if a date unit, false if a time unit + */ + public boolean isDateUnit() { + return this.compareTo(DAYS) >= 0; + } + + /** + * Checks if this unit is a time unit. + * + * @return true if a time unit, false if a date unit + */ + public boolean isTimeUnit() { + return this.compareTo(DAYS) < 0; + } + + //----------------------------------------------------------------------- + @Override + public boolean isSupported(Temporal temporal) { + if (this == FOREVER) { + return false; + } + if (temporal instanceof ChronoLocalDate) { + return isDateUnit(); + } + if (temporal instanceof ChronoLocalDateTime || temporal instanceof ChronoZonedDateTime) { + return true; + } + return TemporalUnit.super.isSupported(temporal); + } + + @SuppressWarnings("unchecked") + @Override + public R doPlus(R dateTime, long periodToAdd) { + return (R) dateTime.plus(periodToAdd, this); + } + + //----------------------------------------------------------------------- + @Override + public SimplePeriod between(R dateTime1, R dateTime2) { + return new SimplePeriod(dateTime1.periodUntil(dateTime2, this), this); + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + return getName(); + } + +} diff --git a/src/share/classes/java/time/temporal/ChronoZonedDateTime.java b/src/share/classes/java/time/temporal/ChronoZonedDateTime.java new file mode 100644 index 0000000000000000000000000000000000000000..9e172ef7fda1cb245b47be1077f82e6d11a8449f --- /dev/null +++ b/src/share/classes/java/time/temporal/ChronoZonedDateTime.java @@ -0,0 +1,564 @@ +/* + * 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 java.time.temporal; + +import static java.time.temporal.ChronoField.INSTANT_SECONDS; +import static java.time.temporal.ChronoField.OFFSET_SECONDS; +import static java.time.temporal.ChronoUnit.NANOS; + +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Comparator; +import java.util.Objects; + +/** + * A date-time with a time-zone in an arbitrary chronology, + * intended for advanced globalization use cases. + *

+ * Most applications should declare method signatures, fields and variables + * as {@link ZonedDateTime}, not this interface. + *

+ * A {@code ChronoZonedDateTime} is the abstract representation of an offset date-time + * where the {@code Chrono chronology}, or calendar system, is pluggable. + * The date-time is defined in terms of fields expressed by {@link TemporalField}, + * where most common implementations are defined in {@link ChronoField}. + * The chronology defines how the calendar system operates and the meaning of + * the standard fields. + * + *

When to use this interface

+ * The design of the API encourages the use of {@code ZonedDateTime} rather than this + * interface, even in the case where the application needs to deal with multiple + * calendar systems. The rationale for this is explored in detail in {@link ChronoLocalDate}. + *

+ * Ensure that the discussion in {@code ChronoLocalDate} has been read and understood + * before using this interface. + * + *

Specification for implementors

+ * This interface must be implemented with care to ensure other classes operate correctly. + * All implementations that can be instantiated must be final, immutable and thread-safe. + * Subclasses should be Serializable wherever possible. + * + * @param the chronology of this date-time + * @since 1.8 + */ +public interface ChronoZonedDateTime> + extends Temporal, Comparable> { + + /** + * Comparator for two {@code ChronoZonedDateTime} instances ignoring the chronology. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the underlying date and not the chronology. + * This allows dates in different calendar systems to be compared based + * on the time-line position. + * + * @see #isAfter + * @see #isBefore + * @see #isEqual + */ + Comparator> INSTANT_COMPARATOR = new Comparator>() { + @Override + public int compare(ChronoZonedDateTime datetime1, ChronoZonedDateTime datetime2) { + int cmp = Long.compare(datetime1.toEpochSecond(), datetime2.toEpochSecond()); + if (cmp == 0) { + cmp = Long.compare(datetime1.getTime().toNanoOfDay(), datetime2.getTime().toNanoOfDay()); + } + return cmp; + } + }; + + @Override + public default ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + if (field == INSTANT_SECONDS || field == OFFSET_SECONDS) { + return field.range(); + } + return getDateTime().range(field); + } + return field.doRange(this); + } + + @Override + public default int get(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case INSTANT_SECONDS: throw new DateTimeException("Field too large for an int: " + field); + case OFFSET_SECONDS: return getOffset().getTotalSeconds(); + } + return getDateTime().get(field); + } + return Temporal.super.get(field); + } + + @Override + public default long getLong(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case INSTANT_SECONDS: return toEpochSecond(); + case OFFSET_SECONDS: return getOffset().getTotalSeconds(); + } + return getDateTime().getLong(field); + } + return field.doGet(this); + } + + /** + * Gets the local date part of this date-time. + *

+ * This returns a local date with the same year, month and day + * as this date-time. + * + * @return the date part of this date-time, not null + */ + public default ChronoLocalDate getDate() { + return getDateTime().getDate(); + } + + /** + * Gets the local time part of this date-time. + *

+ * This returns a local time with the same hour, minute, second and + * nanosecond as this date-time. + * + * @return the time part of this date-time, not null + */ + public default LocalTime getTime() { + return getDateTime().getTime(); + } + + /** + * Gets the local date-time part of this date-time. + *

+ * This returns a local date with the same year, month and day + * as this date-time. + * + * @return the local date-time part of this date-time, not null + */ + ChronoLocalDateTime getDateTime(); + + /** + * Gets the zone offset, such as '+01:00'. + *

+ * This is the offset of the local date-time from UTC/Greenwich. + * + * @return the zone offset, not null + */ + ZoneOffset getOffset(); + + /** + * Gets the zone ID, such as 'Europe/Paris'. + *

+ * This returns the stored time-zone id used to determine the time-zone rules. + * + * @return the zone ID, not null + */ + ZoneId getZone(); + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date-time changing the zone offset to the + * earlier of the two valid offsets at a local time-line overlap. + *

+ * This method only has any effect when the local time-line overlaps, such as + * at an autumn daylight savings cutover. In this scenario, there are two + * valid offsets for the local date-time. Calling this method will return + * a zoned date-time with the earlier of the two selected. + *

+ * If this method is called when it is not an overlap, {@code this} + * is returned. + *

+ * This instance is immutable and unaffected by this method call. + * + * @return a {@code ZoneChronoDateTime} based on this date-time with the earlier offset, not null + * @throws DateTimeException if no rules can be found for the zone + * @throws DateTimeException if no rules are valid for this date-time + */ + ChronoZonedDateTime withEarlierOffsetAtOverlap(); + + /** + * Returns a copy of this date-time changing the zone offset to the + * later of the two valid offsets at a local time-line overlap. + *

+ * This method only has any effect when the local time-line overlaps, such as + * at an autumn daylight savings cutover. In this scenario, there are two + * valid offsets for the local date-time. Calling this method will return + * a zoned date-time with the later of the two selected. + *

+ * If this method is called when it is not an overlap, {@code this} + * is returned. + *

+ * This instance is immutable and unaffected by this method call. + * + * @return a {@code ChronoZonedDateTime} based on this date-time with the later offset, not null + * @throws DateTimeException if no rules can be found for the zone + * @throws DateTimeException if no rules are valid for this date-time + */ + ChronoZonedDateTime withLaterOffsetAtOverlap(); + + /** + * Returns a copy of this ZonedDateTime with a different time-zone, + * retaining the local date-time if possible. + *

+ * This method changes the time-zone and retains the local date-time. + * The local date-time is only changed if it is invalid for the new zone. + *

+ * To change the zone and adjust the local date-time, + * use {@link #withZoneSameInstant(ZoneId)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param zone the time-zone to change to, not null + * @return a {@code ChronoZonedDateTime} based on this date-time with the requested zone, not null + */ + ChronoZonedDateTime withZoneSameLocal(ZoneId zone); + + /** + * Returns a copy of this date-time with a different time-zone, + * retaining the instant. + *

+ * This method changes the time-zone and retains the instant. + * This normally results in a change to the local date-time. + *

+ * This method is based on retaining the same instant, thus gaps and overlaps + * in the local time-line have no effect on the result. + *

+ * To change the offset while keeping the local time, + * use {@link #withZoneSameLocal(ZoneId)}. + * + * @param zone the time-zone to change to, not null + * @return a {@code ChronoZonedDateTime} based on this date-time with the requested zone, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + ChronoZonedDateTime withZoneSameInstant(ZoneId zone); + + //----------------------------------------------------------------------- + // override for covariant return type + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public default ChronoZonedDateTime with(TemporalAdjuster adjuster) { + return getDate().getChrono().ensureChronoZonedDateTime(Temporal.super.with(adjuster)); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + ChronoZonedDateTime with(TemporalField field, long newValue); + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public default ChronoZonedDateTime plus(TemporalAdder adder) { + return getDate().getChrono().ensureChronoZonedDateTime(Temporal.super.plus(adder)); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + ChronoZonedDateTime plus(long amountToAdd, TemporalUnit unit); + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public default ChronoZonedDateTime minus(TemporalSubtractor subtractor) { + return getDate().getChrono().ensureChronoZonedDateTime(Temporal.super.minus(subtractor)); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public default ChronoZonedDateTime minus(long amountToSubtract, TemporalUnit unit) { + return getDate().getChrono().ensureChronoZonedDateTime(Temporal.super.minus(amountToSubtract, unit)); + } + + //----------------------------------------------------------------------- + /** + * Queries this date-time using the specified query. + *

+ * This queries this date-time using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link java.time.temporal.TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @SuppressWarnings("unchecked") + @Override + public default R query(TemporalQuery query) { + if (query == Queries.zone() || query == Queries.zoneId()) { + return (R) getZone(); + } else if (query == Queries.chrono()) { + return (R) getDate().getChrono(); + } else if (query == Queries.precision()) { + return (R) NANOS; + } else if (query == Queries.offset()) { + return (R) getOffset(); + } + // inline TemporalAccessor.super.query(query) as an optimization + return query.queryFrom(this); + } + + //----------------------------------------------------------------------- + /** + * Converts this date-time to an {@code Instant}. + *

+ * This combines the {@linkplain #getDateTime() local date-time} and + * {@linkplain #getOffset() offset} to form an {@code Instant}. + * + * @return an {@code Instant} representing the same instant, not null + */ + public default Instant toInstant() { + return Instant.ofEpochSecond(toEpochSecond(), getTime().getNano()); + } + + /** + * Converts this date-time to the number of seconds from the epoch + * of 1970-01-01T00:00:00Z. + *

+ * This uses the {@linkplain #getDateTime() local date-time} and + * {@linkplain #getOffset() 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. + * + * @return the number of seconds from the epoch of 1970-01-01T00:00:00Z + */ + public default long toEpochSecond() { + long epochDay = getDate().toEpochDay(); + long secs = epochDay * 86400 + getTime().toSecondOfDay(); + secs -= getOffset().getTotalSeconds(); + return secs; + } + + //----------------------------------------------------------------------- + /** + * Compares this date-time to another date-time, including the chronology. + *

+ * The comparison is based first on the instant, then on the local date-time, + * then on the zone ID, then on the chronology. + * It is "consistent with equals", as defined by {@link Comparable}. + *

+ * If all the date-time objects being compared are in the same chronology, then the + * additional chronology stage is not required. + *

+ * This default implementation performs the comparison defined above. + * + * @param other the other date-time to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + @Override + public default int compareTo(ChronoZonedDateTime other) { + int cmp = Long.compare(toEpochSecond(), other.toEpochSecond()); + if (cmp == 0) { + cmp = getTime().getNano() - other.getTime().getNano(); + if (cmp == 0) { + cmp = getDateTime().compareTo(other.getDateTime()); + if (cmp == 0) { + cmp = getZone().getId().compareTo(other.getZone().getId()); + if (cmp == 0) { + cmp = getDate().getChrono().compareTo(other.getDate().getChrono()); + } + } + } + } + return cmp; + } + + /** + * Checks if the instant of this date-time is before that of the specified date-time. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the instant of the date-time. This is equivalent to using + * {@code dateTime1.toInstant().isBefore(dateTime2.toInstant());}. + *

+ * This default implementation performs the comparison based on the epoch-second + * and nano-of-second. + * + * @param other the other date-time to compare to, not null + * @return true if this point is before the specified date-time + */ + public default boolean isBefore(ChronoZonedDateTime other) { + long thisEpochSec = toEpochSecond(); + long otherEpochSec = other.toEpochSecond(); + return thisEpochSec < otherEpochSec || + (thisEpochSec == otherEpochSec && getTime().getNano() < other.getTime().getNano()); + } + + /** + * Checks if the instant of this date-time is after that of the specified date-time. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the instant of the date-time. This is equivalent to using + * {@code dateTime1.toInstant().isAfter(dateTime2.toInstant());}. + *

+ * This default implementation performs the comparison based on the epoch-second + * and nano-of-second. + * + * @param other the other date-time to compare to, not null + * @return true if this is after the specified date-time + */ + public default boolean isAfter(ChronoZonedDateTime other) { + long thisEpochSec = toEpochSecond(); + long otherEpochSec = other.toEpochSecond(); + return thisEpochSec > otherEpochSec || + (thisEpochSec == otherEpochSec && getTime().getNano() > other.getTime().getNano()); + } + + /** + * Checks if the instant of this date-time is equal to that of the specified date-time. + *

+ * This method differs from the comparison in {@link #compareTo} and {@link #equals} + * in that it only compares the instant of the date-time. This is equivalent to using + * {@code dateTime1.toInstant().equals(dateTime2.toInstant());}. + *

+ * This default implementation performs the comparison based on the epoch-second + * and nano-of-second. + * + * @param other the other date-time to compare to, not null + * @return true if the instant equals the instant of the specified date-time + */ + public default boolean isEqual(ChronoZonedDateTime other) { + return toEpochSecond() == other.toEpochSecond() && + getTime().getNano() == other.getTime().getNano(); + } + + //----------------------------------------------------------------------- + /** + * Checks if this date-time is equal to another date-time. + *

+ * The comparison is based on the offset date-time and the zone. + * To compare for the same instant on the time-line, use {@link #compareTo}. + * Only objects of type {@code ChronoZonedDateTime} 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 + boolean equals(Object obj); + + /** + * A hash code for this date-time. + * + * @return a suitable hash code + */ + @Override + int hashCode(); + + //----------------------------------------------------------------------- + /** + * Outputs this date-time as a {@code String}. + *

+ * The output will include the full zoned date-time and the chronology ID. + * + * @return a string representation of this date-time, not null + */ + @Override + String toString(); + + /** + * Outputs this date-time as a {@code String} using the formatter. + *

+ * The default implementation must behave as follows: + *

+     *  return formatter.print(this);
+     * 
+ * + * @param formatter the formatter to use, not null + * @return the formatted date-time string, not null + * @throws DateTimeException if an error occurs during printing + */ + public default String toString(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.print(this); + } + +} diff --git a/src/share/classes/java/time/temporal/ChronoZonedDateTimeImpl.java b/src/share/classes/java/time/temporal/ChronoZonedDateTimeImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..27efde35d06442138005e8f33734fccf870ecd80 --- /dev/null +++ b/src/share/classes/java/time/temporal/ChronoZonedDateTimeImpl.java @@ -0,0 +1,353 @@ +/* + * 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 java.time.temporal; + +import static java.time.temporal.ChronoUnit.SECONDS; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.zone.ZoneOffsetTransition; +import java.time.zone.ZoneRules; +import java.util.List; +import java.util.Objects; + +/** + * A date-time with a time-zone in the calendar neutral API. + *

+ * {@code ZoneChronoDateTime} is an immutable representation of a date-time with a time-zone. + * This class stores all date and time fields, to a precision of nanoseconds, + * as well as a time-zone and zone offset. + *

+ * The purpose of storing the time-zone is to distinguish the ambiguous case where + * the local time-line overlaps, typically as a result of the end of daylight time. + * Information about the local-time can be obtained using methods on the time-zone. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @param the chronology of this date + * @since 1.8 + */ +final class ChronoZonedDateTimeImpl> + implements ChronoZonedDateTime, Serializable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = -5261813987200935591L; + + /** + * The local date-time. + */ + private final ChronoLocalDateTimeImpl dateTime; + /** + * The zone offset. + */ + private final ZoneOffset offset; + /** + * The zone ID. + */ + private final ZoneId zone; + + //----------------------------------------------------------------------- + /** + * Obtains an instance from a local date-time using the preferred offset if possible. + * + * @param localDateTime the local date-time, not null + * @param zone the zone identifier, not null + * @param preferredOffset the zone offset, null if no preference + * @return the zoned date-time, not null + */ + static > ChronoZonedDateTime ofBest( + ChronoLocalDateTimeImpl localDateTime, ZoneId zone, ZoneOffset preferredOffset) { + Objects.requireNonNull(localDateTime, "localDateTime"); + Objects.requireNonNull(zone, "zone"); + if (zone instanceof ZoneOffset) { + return new ChronoZonedDateTimeImpl(localDateTime, (ZoneOffset) zone, zone); + } + ZoneRules rules = zone.getRules(); + LocalDateTime isoLDT = LocalDateTime.from(localDateTime); + List validOffsets = rules.getValidOffsets(isoLDT); + ZoneOffset offset; + if (validOffsets.size() == 1) { + offset = validOffsets.get(0); + } else if (validOffsets.size() == 0) { + ZoneOffsetTransition trans = rules.getTransition(isoLDT); + localDateTime = localDateTime.plusSeconds(trans.getDuration().getSeconds()); + offset = trans.getOffsetAfter(); + } else { + if (preferredOffset != null && validOffsets.contains(preferredOffset)) { + offset = preferredOffset; + } else { + offset = validOffsets.get(0); + } + } + Objects.requireNonNull(offset, "offset"); // protect against bad ZoneRules + return new ChronoZonedDateTimeImpl(localDateTime, offset, zone); + } + + /** + * Obtains an instance from an instant using the specified time-zone. + * + * @param chrono the chronology, not null + * @param instant the instant, not null + * @param zone the zone identifier, not null + * @return the zoned date-time, not null + */ + static > ChronoZonedDateTimeImpl ofInstant(Chrono chrono, Instant instant, ZoneId zone) { + ZoneRules rules = zone.getRules(); + ZoneOffset offset = rules.getOffset(instant); + Objects.requireNonNull(offset, "offset"); // protect against bad ZoneRules + LocalDateTime ldt = LocalDateTime.ofEpochSecond(instant.getEpochSecond(), instant.getNano(), offset); + ChronoLocalDateTimeImpl cldt = (ChronoLocalDateTimeImpl) chrono.localDateTime(ldt); + return new ChronoZonedDateTimeImpl(cldt, offset, zone); + } + + /** + * Obtains an instance from an {@code Instant}. + * + * @param instant the instant to create the date-time from, not null + * @param zone the time-zone to use, validated not null + * @return the zoned date-time, validated not null + */ + private ChronoZonedDateTimeImpl create(Instant instant, ZoneId zone) { + return ofInstant(getDate().getChrono(), instant, zone); + } + + //----------------------------------------------------------------------- + /** + * Constructor. + * + * @param dateTime the date-time, not null + * @param offset the zone offset, not null + * @param zone the zone ID, not null + */ + private ChronoZonedDateTimeImpl(ChronoLocalDateTimeImpl dateTime, ZoneOffset offset, ZoneId zone) { + this.dateTime = Objects.requireNonNull(dateTime, "dateTime"); + this.offset = Objects.requireNonNull(offset, "offset"); + this.zone = Objects.requireNonNull(zone, "zone"); + } + + //----------------------------------------------------------------------- + public ZoneOffset getOffset() { + return offset; + } + + @Override + public ChronoZonedDateTime withEarlierOffsetAtOverlap() { + ZoneOffsetTransition trans = getZone().getRules().getTransition(LocalDateTime.from(this)); + if (trans != null && trans.isOverlap()) { + ZoneOffset earlierOffset = trans.getOffsetBefore(); + if (earlierOffset.equals(offset) == false) { + return new ChronoZonedDateTimeImpl(dateTime, earlierOffset, zone); + } + } + return this; + } + + @Override + public ChronoZonedDateTime withLaterOffsetAtOverlap() { + ZoneOffsetTransition trans = getZone().getRules().getTransition(LocalDateTime.from(this)); + if (trans != null) { + ZoneOffset offset = trans.getOffsetAfter(); + if (offset.equals(getOffset()) == false) { + return new ChronoZonedDateTimeImpl(dateTime, offset, zone); + } + } + return this; + } + + //----------------------------------------------------------------------- + @Override + public ChronoLocalDateTime getDateTime() { + return dateTime; + } + + public ZoneId getZone() { + return zone; + } + + public ChronoZonedDateTime withZoneSameLocal(ZoneId zone) { + return ofBest(dateTime, zone, offset); + } + + @Override + public ChronoZonedDateTime withZoneSameInstant(ZoneId zone) { + Objects.requireNonNull(zone, "zone"); + return this.zone.equals(zone) ? this : create(dateTime.toInstant(offset), zone); + } + + //----------------------------------------------------------------------- + @Override + public boolean isSupported(TemporalField field) { + return field instanceof ChronoField || (field != null && field.doIsSupported(this)); + } + + //----------------------------------------------------------------------- + @Override + public ChronoZonedDateTime with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + switch (f) { + case INSTANT_SECONDS: return plus(newValue - toEpochSecond(), SECONDS); + case OFFSET_SECONDS: { + ZoneOffset offset = ZoneOffset.ofTotalSeconds(f.checkValidIntValue(newValue)); + return create(dateTime.toInstant(offset), zone); + } + } + return ofBest(dateTime.with(field, newValue), zone, offset); + } + return getDate().getChrono().ensureChronoZonedDateTime(field.doWith(this, newValue)); + } + + //----------------------------------------------------------------------- + @Override + public ChronoZonedDateTime plus(long amountToAdd, TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + return with(dateTime.plus(amountToAdd, unit)); + } + return getDate().getChrono().ensureChronoZonedDateTime(unit.doPlus(this, amountToAdd)); /// TODO: Generics replacement Risk! + } + + //----------------------------------------------------------------------- + @Override + public long periodUntil(Temporal endDateTime, TemporalUnit unit) { + if (endDateTime instanceof ChronoZonedDateTime == false) { + throw new DateTimeException("Unable to calculate period between objects of two different types"); + } + @SuppressWarnings("unchecked") + ChronoZonedDateTime end = (ChronoZonedDateTime) endDateTime; + if (getDate().getChrono().equals(end.getDate().getChrono()) == false) { + throw new DateTimeException("Unable to calculate period between two different chronologies"); + } + if (unit instanceof ChronoUnit) { + end = end.withZoneSameInstant(offset); + return dateTime.periodUntil(end.getDateTime(), unit); + } + return unit.between(this, endDateTime).getAmount(); + } + + //----------------------------------------------------------------------- + private Object writeReplace() { + return new Ser(Ser.CHRONO_ZONE_DATE_TIME_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(ObjectOutput out) throws IOException { + out.writeObject(dateTime); + out.writeObject(offset); + out.writeObject(zone); + } + + static ChronoZonedDateTime readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + ChronoLocalDateTime dateTime = (ChronoLocalDateTime) in.readObject(); + ZoneOffset offset = (ZoneOffset) in.readObject(); + ZoneId zone = (ZoneId) in.readObject(); + return dateTime.atZone(offset).withZoneSameLocal(zone); + // TODO: ZDT uses ofLenient() + } + + //------------------------------------------------------------------------- + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof ChronoZonedDateTime) { + return compareTo((ChronoZonedDateTime) obj) == 0; + } + return false; + } + + @Override + public int hashCode() { + return getDateTime().hashCode() ^ getOffset().hashCode() ^ Integer.rotateLeft(getZone().hashCode(), 3); + } + + @Override + public String toString() { + String str = getDateTime().toString() + getOffset().toString(); + if (getOffset() != getZone()) { + str += '[' + getZone().toString() + ']'; + } + return str; + } + + +} diff --git a/src/share/classes/java/time/temporal/Era.java b/src/share/classes/java/time/temporal/Era.java new file mode 100644 index 0000000000000000000000000000000000000000..e32de7473eb481470705a04bfb8139e2496bc7b5 --- /dev/null +++ b/src/share/classes/java/time/temporal/Era.java @@ -0,0 +1,359 @@ +/* + * 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) 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 java.time.temporal; + +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoUnit.ERAS; + +import java.time.DateTimeException; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.TextStyle; +import java.util.Locale; + +/** + * An era of the time-line. + *

+ * Most calendar systems have a single epoch dividing the time-line into two eras. + * However, some calendar systems, have multiple eras, such as one for the reign + * of each leader. + * In all cases, the era is conceptually the largest division of the time-line. + * Each chronology defines the Era's that are known Eras and a + * {@link Chrono#eras Chrono.eras} to get the valid eras. + *

+ * For example, the Thai Buddhist calendar system divides time into two eras, + * before and after a single date. By contrast, the Japanese calendar system + * has one era for the reign of each Emperor. + *

+ * Instances of {@code Era} may be compared using the {@code ==} operator. + * + *

Specification for implementors

+ * This interface must be implemented with care to ensure other classes operate correctly. + * All implementations must be singletons - final, immutable and thread-safe. + * It is recommended to use an enum whenever possible. + * + * @param the chronology of the era + * @since 1.8 + */ +public interface Era> extends TemporalAccessor, TemporalAdjuster { + + /** + * Gets the numeric value associated with the era as defined by the chronology. + * Each chronology defines the predefined Eras and methods to list the Eras + * of the chronology. + *

+ * All fields, including eras, have an associated numeric value. + * The meaning of the numeric value for era is determined by the chronology + * according to these principles: + *

    + *
  • The era in use at the epoch 1970-01-01 (ISO) has the value 1. + *
  • Later eras have sequentially higher values. + *
  • Earlier eras have sequentially lower values, which may be negative. + *

+ * + * @return the numeric era value + */ + int getValue(); + + /** + * Gets the chronology of this era. + *

+ * The {@code Chrono} represents the calendar system in use. + * This always returns the standard form of the chronology. + * + * @return the chronology, not null + */ + C getChrono(); + + //----------------------------------------------------------------------- + /** + * Obtains a date in this era given the year-of-era, month, and day. + *

+ * This era is combined with the given date fields to form a date. + * The year specified must be the year-of-era. + * Methods to create a date from the proleptic-year are on {@code Chrono}. + * This always uses the standard form of the chronology. + *

+ * This default implementation invokes the factory method on {@link Chrono}. + * + * @param yearOfEra the calendar system year-of-era + * @param month the calendar system month-of-year + * @param day the calendar system day-of-month + * @return a local date based on this era and the specified year-of-era, month and day + */ + public default ChronoLocalDate date(int yearOfEra, int month, int day) { + return getChrono().date(this, yearOfEra, month, day); + } + + + /** + * Obtains a date in this era given year-of-era and day-of-year fields. + *

+ * This era is combined with the given date fields to form a date. + * The year specified must be the year-of-era. + * Methods to create a date from the proleptic-year are on {@code Chrono}. + * This always uses the standard form of the chronology. + *

+ * This default implementation invokes the factory method on {@link Chrono}. + * + * @param yearOfEra the calendar system year-of-era + * @param dayOfYear the calendar system day-of-year + * @return a local date based on this era and the specified year-of-era and day-of-year + */ + public default ChronoLocalDate dateYearDay(int yearOfEra, int dayOfYear) { + return getChrono().dateYearDay(this, yearOfEra, dayOfYear); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this era can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and + * {@link #get(TemporalField) get} methods will throw an exception. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@code ERA} field returns true. + * All other {@code ChronoField} instances will return false. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the field is supported is determined by the field. + * + * @param field the field to check, null returns false + * @return true if the field is supported on this era, false if not + */ + @Override + public default boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == ERA; + } + return field != null && field.doIsSupported(this); + } + + /** + * Gets the range of valid values for the specified field. + *

+ * The range object expresses the minimum and maximum valid values for a field. + * This era is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@code ERA} field returns the range. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + @Override // override for Javadoc + public default ValueRange range(TemporalField field) { + return TemporalAccessor.super.range(field); + } + + /** + * Gets the value of the specified field from this era as an {@code int}. + *

+ * This queries this era for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@code ERA} field returns the value of the era. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override // override for Javadoc and performance + public default int get(TemporalField field) { + if (field == ERA) { + return getValue(); + } + return range(field).checkValidIntValue(getLong(field), field); + } + + /** + * Gets the value of the specified field from this era as a {@code long}. + *

+ * This queries this era for the value for the specified field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@code ERA} field returns the value of the era. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public default long getLong(TemporalField field) { + if (field == ERA) { + return getValue(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doGet(this); + } + + //----------------------------------------------------------------------- + /** + * Queries this era using the specified query. + *

+ * This queries this era using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @SuppressWarnings("unchecked") + @Override + public default R query(TemporalQuery query) { + if (query == Queries.chrono()) { + return (R) getChrono(); + } else if (query == Queries.precision()) { + return (R) ERAS; + } + return TemporalAccessor.super.query(query); + } + + /** + * Adjusts the specified temporal object to have the same era as this object. + *

+ * This returns a temporal object of the same observable type as the input + * with the era changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * passing {@link ChronoField#ERA} as the field. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisEra.adjustInto(temporal);
+     *   temporal = temporal.with(thisEra);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public default Temporal adjustInto(Temporal temporal) { + return temporal.with(ERA, getValue()); + } + + //----------------------------------------------------------------------- + /** + * Gets the textual representation of this era. + *

+ * This returns the textual name used to identify the era. + * The parameters control the style of the returned text and the locale. + *

+ * If no textual mapping is found then the {@link #getValue() numeric value} is returned. + *

+ * This default implementation is suitable for all implementations. + * + * @param style the style of the text required, not null + * @param locale the locale to use, not null + * @return the text value of the era, not null + */ + public default String getText(TextStyle style, Locale locale) { + return new DateTimeFormatterBuilder().appendText(ERA, style).toFormatter(locale).print(this); + } + + // NOTE: methods to convert year-of-era/proleptic-year cannot be here as they may depend on month/day (Japanese) +} diff --git a/src/share/classes/java/time/temporal/ISOChrono.java b/src/share/classes/java/time/temporal/ISOChrono.java new file mode 100644 index 0000000000000000000000000000000000000000..c290e4e1dbb17e09b75e933c38ab8e089d1d7dc1 --- /dev/null +++ b/src/share/classes/java/time/temporal/ISOChrono.java @@ -0,0 +1,399 @@ +/* + * 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) 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 java.time.temporal; + +import java.io.Serializable; +import java.time.Clock; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +/** + * The ISO calendar system. + *

+ * This chronology defines the rules of the ISO calendar system. + * This calendar system is based on the ISO-8601 standard, which is the + * de facto world calendar. + *

+ * The fields are defined as follows: + *

    + *
  • era - There are two eras, 'Current Era' (CE) and 'Before Current Era' (BCE). + *
  • year-of-era - The year-of-era is the same as the proleptic-year for the current CE era. + * For the BCE era before the ISO epoch the year increases from 1 upwards as time goes backwards. + *
  • proleptic-year - The proleptic year is the same as the year-of-era for the + * current era. For the previous era, years have zero, then negative values. + *
  • month-of-year - There are 12 months in an ISO year, numbered from 1 to 12. + *
  • day-of-month - There are between 28 and 31 days in each of the ISO month, numbered from 1 to 31. + * Months 4, 6, 9 and 11 have 30 days, Months 1, 3, 5, 7, 8, 10 and 12 have 31 days. + * Month 2 has 28 days, or 29 in a leap year. + *
  • day-of-year - There are 365 days in a standard ISO year and 366 in a leap year. + * The days are numbered from 1 to 365 or 1 to 366. + *
  • leap-year - Leap years occur every 4 years, except where the year is divisble by 100 and not divisble by 400. + *

+ * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class ISOChrono extends Chrono implements Serializable { + + /** + * Singleton instance of the ISO chronology. + */ + public static final ISOChrono INSTANCE = new ISOChrono(); + /** + * The singleton instance for the era BCE - 'Before Current Era'. + * The 'ISO' part of the name emphasizes that this differs from the BCE + * era in the Gregorian calendar system. + * This has the numeric value of {@code 0}. + */ + public static final Era ERA_BCE = ISOEra.BCE; + /** + * The singleton instance for the era CE - 'Current Era'. + * The 'ISO' part of the name emphasizes that this differs from the CE + * era in the Gregorian calendar system. + * This has the numeric value of {@code 1}. + */ + public static final Era ERA_CE = ISOEra.CE; + + /** + * Serialization version. + */ + private static final long serialVersionUID = -1440403870442975015L; + + /** + * Restricted constructor. + */ + private ISOChrono() { + } + + /** + * Resolve singleton. + * + * @return the singleton instance, not null + */ + private Object readResolve() { + return INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the ID of the chronology - 'ISO'. + *

+ * The ID uniquely identifies the {@code Chrono}. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * + * @return the chronology ID - 'ISO' + * @see #getCalendarType() + */ + @Override + public String getId() { + return "ISO"; + } + + /** + * Gets the calendar type of the underlying calendar system - 'iso8601'. + *

+ * The calendar type is an identifier defined by the + * Unicode Locale Data Markup Language (LDML) specification. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * It can also be used as part of a locale, accessible via + * {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'. + * + * @return the calendar system type - 'iso8601' + * @see #getId() + */ + @Override + public String getCalendarType() { + return "iso8601"; + } + + //----------------------------------------------------------------------- + /** + * Obtains an ISO local date from the era, year-of-era, month-of-year + * and day-of-month fields. + * + * @param era the ISO era, not null + * @param yearOfEra the ISO year-of-era + * @param month the ISO month-of-year + * @param dayOfMonth the ISO day-of-month + * @return the ISO local date, not null + * @throws DateTimeException if unable to create the date + */ + @Override // override with covariant return type + public LocalDate date(Era era, int yearOfEra, int month, int dayOfMonth) { + return date(prolepticYear(era, yearOfEra), month, dayOfMonth); + } + + /** + * Obtains an ISO local date from the proleptic-year, month-of-year + * and day-of-month fields. + *

+ * This is equivalent to {@link LocalDate#of(int, int, int)}. + * + * @param prolepticYear the ISO proleptic-year + * @param month the ISO month-of-year + * @param dayOfMonth the ISO day-of-month + * @return the ISO local date, not null + * @throws DateTimeException if unable to create the date + */ + @Override // override with covariant return type + public LocalDate date(int prolepticYear, int month, int dayOfMonth) { + return LocalDate.of(prolepticYear, month, dayOfMonth); + } + + /** + * Obtains an ISO local date from the era, year-of-era and day-of-year fields. + * + * @param era the ISO era, not null + * @param yearOfEra the ISO year-of-era + * @param dayOfYear the ISO day-of-year + * @return the ISO local date, not null + * @throws DateTimeException if unable to create the date + */ + @Override // override with covariant return type + public LocalDate dateYearDay(Era era, int yearOfEra, int dayOfYear) { + return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear); + } + + /** + * Obtains an ISO local date from the proleptic-year and day-of-year fields. + *

+ * This is equivalent to {@link LocalDate#ofYearDay(int, int)}. + * + * @param prolepticYear the ISO proleptic-year + * @param dayOfYear the ISO day-of-year + * @return the ISO local date, not null + * @throws DateTimeException if unable to create the date + */ + @Override // override with covariant return type + public LocalDate dateYearDay(int prolepticYear, int dayOfYear) { + return LocalDate.ofYearDay(prolepticYear, dayOfYear); + } + + //----------------------------------------------------------------------- + /** + * Obtains an ISO local date from another date-time object. + *

+ * This is equivalent to {@link LocalDate#from(TemporalAccessor)}. + * + * @param temporal the date-time object to convert, not null + * @return the ISO local date, not null + * @throws DateTimeException if unable to create the date + */ + @Override // override with covariant return type + public LocalDate date(TemporalAccessor temporal) { + return LocalDate.from(temporal); + } + + /** + * Obtains an ISO local date-time from another date-time object. + *

+ * This is equivalent to {@link LocalDateTime#from(TemporalAccessor)}. + * + * @param temporal the date-time object to convert, not null + * @return the ISO local date-time, not null + * @throws DateTimeException if unable to create the date-time + */ + @Override // override with covariant return type + public LocalDateTime localDateTime(TemporalAccessor temporal) { + return LocalDateTime.from(temporal); + } + + /** + * Obtains an ISO zoned date-time from another date-time object. + *

+ * This is equivalent to {@link ZonedDateTime#from(TemporalAccessor)}. + * + * @param temporal the date-time object to convert, not null + * @return the ISO zoned date-time, not null + * @throws DateTimeException if unable to create the date-time + */ + @Override // override with covariant return type + public ZonedDateTime zonedDateTime(TemporalAccessor temporal) { + return ZonedDateTime.from(temporal); + } + + /** + * Obtains an ISO zoned date-time in this chronology from an {@code Instant}. + *

+ * This is equivalent to {@link ZonedDateTime#ofInstant(Instant, ZoneId)}. + * + * @param instant the instant to create the date-time from, not null + * @param zone the time-zone, not null + * @return the zoned date-time, not null + * @throws DateTimeException if the result exceeds the supported range + */ + public ZonedDateTime zonedDateTime(Instant instant, ZoneId zone) { + return ZonedDateTime.ofInstant(instant, zone); + } + + //----------------------------------------------------------------------- + /** + * Obtains the current ISO local date from the system clock in the default time-zone. + *

+ * This will query the {@link Clock#systemDefaultZone() system clock} in the default + * time-zone to obtain the current date. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @return the current ISO local date using the system clock and default time-zone, not null + * @throws DateTimeException if unable to create the date + */ + @Override // override with covariant return type + public LocalDate dateNow() { + return dateNow(Clock.systemDefaultZone()); + } + + /** + * Obtains the current ISO local date from the system clock in the specified time-zone. + *

+ * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date. + * Specifying the time-zone avoids dependence on the default time-zone. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @return the current ISO local date using the system clock, not null + * @throws DateTimeException if unable to create the date + */ + @Override // override with covariant return type + public LocalDate dateNow(ZoneId zone) { + return dateNow(Clock.system(zone)); + } + + /** + * Obtains the current ISO local date from the specified clock. + *

+ * This will query the specified clock to obtain the current date - today. + * Using this method allows the use of an alternate clock for testing. + * The alternate clock may be introduced using {@link Clock dependency injection}. + * + * @param clock the clock to use, not null + * @return the current ISO local date, not null + * @throws DateTimeException if unable to create the date + */ + @Override // override with covariant return type + public LocalDate dateNow(Clock clock) { + Objects.requireNonNull(clock, "clock"); + return date(LocalDate.now(clock)); + } + + //----------------------------------------------------------------------- + /** + * Checks if the year is a leap year, according to the ISO proleptic + * calendar system rules. + *

+ * This method applies the current rules for leap years across the whole time-line. + * In general, a year is a leap year if it is divisible by four without + * remainder. However, years divisible by 100, are not leap years, with + * the exception of years divisible by 400 which are. + *

+ * For example, 1904 is a leap year it is divisible by 4. + * 1900 was not a leap year as it is divisible by 100, however 2000 was a + * leap year as it is divisible by 400. + *

+ * The calculation is proleptic - applying the same rules into the far future and far past. + * This is historically inaccurate, but is correct for the ISO-8601 standard. + * + * @param prolepticYear the ISO proleptic year to check + * @return true if the year is leap, false otherwise + */ + @Override + public boolean isLeapYear(long prolepticYear) { + return ((prolepticYear & 3) == 0) && ((prolepticYear % 100) != 0 || (prolepticYear % 400) == 0); + } + + @Override + public int prolepticYear(Era era, int yearOfEra) { + if (era instanceof ISOEra == false) { + throw new DateTimeException("Era must be ISOEra"); + } + return (era == ISOEra.CE ? yearOfEra : 1 - yearOfEra); + } + + @Override + public Era eraOf(int eraValue) { + return ISOEra.of(eraValue); + } + + @Override + public List> eras() { + return Arrays.>asList(ISOEra.values()); + } + + //----------------------------------------------------------------------- + @Override + public ValueRange range(ChronoField field) { + return field.range(); + } + +} diff --git a/src/share/classes/java/time/temporal/ISOEra.java b/src/share/classes/java/time/temporal/ISOEra.java new file mode 100644 index 0000000000000000000000000000000000000000..21009f6b183b5b0f59ee8d917dea88d8db36503e --- /dev/null +++ b/src/share/classes/java/time/temporal/ISOEra.java @@ -0,0 +1,204 @@ +/* + * 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) 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 java.time.temporal; + +import static java.time.temporal.ChronoField.ERA; + +import java.time.DateTimeException; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.TextStyle; +import java.util.Locale; + +/** + * An era in the ISO calendar system. + *

+ * The ISO-8601 standard does not define eras. + * A definition has therefore been created with two eras - 'Current era' (CE) for + * years from 0001-01-01 (ISO) and 'Before current era' (BCE) for years before that. + *

+ * Do not use {@code ordinal()} to obtain the numeric representation of {@code ISOEra}. + * Use {@code getValue()} instead. + * + *

Specification for implementors

+ * This is an immutable and thread-safe enum. + * + * @since 1.8 + */ +enum ISOEra implements Era { + + /** + * The singleton instance for the era BCE, 'Before Current Era'. + * The 'ISO' part of the name emphasizes that this differs from the BCE + * era in the Gregorian calendar system. + * This has the numeric value of {@code 0}. + */ + BCE, + /** + * The singleton instance for the era CE, 'Current Era'. + * The 'ISO' part of the name emphasizes that this differs from the CE + * era in the Gregorian calendar system. + * This has the numeric value of {@code 1}. + */ + CE; + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code ISOEra} from an {@code int} value. + *

+ * {@code ISOEra} is an enum representing the ISO eras of BCE/CE. + * This factory allows the enum to be obtained from the {@code int} value. + * + * @param era the BCE/CE value to represent, from 0 (BCE) to 1 (CE) + * @return the era singleton, not null + * @throws DateTimeException if the value is invalid + */ + public static ISOEra of(int era) { + switch (era) { + case 0: + return BCE; + case 1: + return CE; + default: + throw new DateTimeException("Invalid era: " + era); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the numeric era {@code int} value. + *

+ * The era BCE has the value 0, while the era CE has the value 1. + * + * @return the era value, from 0 (BCE) to 1 (CE) + */ + @Override + public int getValue() { + return ordinal(); + } + + @Override + public ISOChrono getChrono() { + return ISOChrono.INSTANCE; + } + + // JDK8 default methods: + //----------------------------------------------------------------------- + @Override + public ChronoLocalDate date(int year, int month, int day) { + return getChrono().date(this, year, month, day); + } + + @Override + public ChronoLocalDate dateYearDay(int year, int dayOfYear) { + return getChrono().dateYearDay(this, year, dayOfYear); + } + + //----------------------------------------------------------------------- + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == ERA; + } + return field != null && field.doIsSupported(this); + } + + @Override + public ValueRange range(TemporalField field) { + if (field == ERA) { + return field.range(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doRange(this); + } + + @Override + public int get(TemporalField field) { + if (field == ERA) { + return getValue(); + } + return range(field).checkValidIntValue(getLong(field), field); + } + + @Override + public long getLong(TemporalField field) { + if (field == ERA) { + return getValue(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doGet(this); + } + + //------------------------------------------------------------------------- + @Override + public Temporal adjustInto(Temporal temporal) { + return temporal.with(ERA, getValue()); + } + + //----------------------------------------------------------------------- + @Override + public String getText(TextStyle style, Locale locale) { + return new DateTimeFormatterBuilder().appendText(ERA, style).toFormatter(locale).print(this); + } + +} diff --git a/src/share/classes/java/time/temporal/ISOFields.java b/src/share/classes/java/time/temporal/ISOFields.java new file mode 100644 index 0000000000000000000000000000000000000000..548d1d5e31b9ba99ea0b8b389b7c5431b337678d --- /dev/null +++ b/src/share/classes/java/time/temporal/ISOFields.java @@ -0,0 +1,564 @@ +/* + * 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) 2011-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 java.time.temporal; + +import static java.time.DayOfWeek.THURSDAY; +import static java.time.DayOfWeek.WEDNESDAY; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.FOREVER; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.WEEKS; +import static java.time.temporal.ChronoUnit.YEARS; + +import java.time.DateTimeException; +import java.time.Duration; +import java.time.LocalDate; +import java.time.format.DateTimeBuilder; + +/** + * Fields and units specific to the ISO-8601 calendar system, + * including quarter-of-year and week-based-year. + *

+ * This class defines fields and units that are specific to the ISO calendar system. + * + *

Quarter of year

+ * The ISO-8601 standard is based on the standard civic 12 month year. + * This is commonly divided into four quarters, often abbreviated as Q1, Q2, Q3 and Q4. + *

+ * January, February and March are in Q1. + * April, May and June are in Q2. + * July, August and September are in Q3. + * October, November and December are in Q4. + *

+ * The complete date is expressed using three fields: + *

    + *
  • {@link #DAY_OF_QUARTER DAY_OF_QUARTER} - the day within the quarter, from 1 to 90, 91 or 92 + *
  • {@link #QUARTER_OF_YEAR QUARTER_OF_YEAR} - the week within the week-based-year + *
  • {@link ChronoField#YEAR YEAR} - the standard ISO year + *

+ * + *

Week based years

+ * The ISO-8601 standard was originally intended as a data interchange format, + * defining a string format for dates and times. However, it also defines an + * alternate way of expressing the date, based on the concept of week-based-year. + *

+ * The date is expressed using three fields: + *

    + *
  • {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} - the standard field defining the + * day-of-week from Monday (1) to Sunday (7) + *
  • {@link #WEEK_OF_WEEK_BASED_YEAR} - the week within the week-based-year + *
  • {@link #WEEK_BASED_YEAR WEEK_BASED_YEAR} - the week-based-year + *

+ * The week-based-year itself is defined relative to the standard ISO proleptic year. + * It differs from the standard year in that it always starts on a Monday. + *

+ * The first week of a week-based-year is the first Monday-based week of the standard + * ISO year that has at least 4 days in the new year. + *

    + *
  • If January 1st is Monday then week 1 starts on January 1st + *
  • If January 1st is Tuesday then week 1 starts on December 31st of the previous standard year + *
  • If January 1st is Wednesday then week 1 starts on December 30th of the previous standard year + *
  • If January 1st is Thursday then week 1 starts on December 29th of the previous standard year + *
  • If January 1st is Friday then week 1 starts on January 4th + *
  • If January 1st is Saturday then week 1 starts on January 3rd + *
  • If January 1st is Sunday then week 1 starts on January 2nd + *

+ * There are 52 weeks in most week-based years, however on occasion there are 53 weeks. + *

+ * For example: + *

+ * + * + * + * + * + * + * + * + * + *
Examples of Week based Years
DateDay-of-weekField values
2008-12-28SundayWeek 52 of week-based-year 2008
2008-12-29MondayWeek 1 of week-based-year 2009
2008-12-31WednesdayWeek 1 of week-based-year 2009
2009-01-01ThursdayWeek 1 of week-based-year 2009
2009-01-04SundayWeek 1 of week-based-year 2009
2009-01-05MondayWeek 2 of week-based-year 2009
+ * + *

Specification for implementors

+ *

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class ISOFields { + + /** + * The field that represents the day-of-quarter. + *

+ * This field allows the day-of-quarter value to be queried and set. + * The day-of-quarter has values from 1 to 90 in Q1 of a standard year, from 1 to 91 + * in Q1 of a leap year, from 1 to 91 in Q2 and from 1 to 92 in Q3 and Q4. + *

+ * The day-of-quarter can only be calculated if the day-of-year, month-of-year and year + * are available. + *

+ * When setting this field, the value is allowed to be partially lenient, taking any + * value from 1 to 92. If the quarter has less than 92 days, then day 92, and + * potentially day 91, is in the following quarter. + *

+ * This unit is an immutable and thread-safe singleton. + */ + public static final TemporalField DAY_OF_QUARTER = Field.DAY_OF_QUARTER; + /** + * The field that represents the quarter-of-year. + *

+ * This field allows the quarter-of-year value to be queried and set. + * The quarter-of-year has values from 1 to 4. + *

+ * The day-of-quarter can only be calculated if the month-of-year is available. + *

+ * This unit is an immutable and thread-safe singleton. + */ + public static final TemporalField QUARTER_OF_YEAR = Field.QUARTER_OF_YEAR; + /** + * The field that represents the week-of-week-based-year. + *

+ * This field allows the week of the week-based-year value to be queried and set. + *

+ * This unit is an immutable and thread-safe singleton. + */ + public static final TemporalField WEEK_OF_WEEK_BASED_YEAR = Field.WEEK_OF_WEEK_BASED_YEAR; + /** + * The field that represents the week-based-year. + *

+ * This field allows the week-based-year value to be queried and set. + *

+ * This unit is an immutable and thread-safe singleton. + */ + public static final TemporalField WEEK_BASED_YEAR = Field.WEEK_BASED_YEAR; + /** + * The unit that represents week-based-years for the purpose of addition and subtraction. + *

+ * This allows a number of week-based-years to be added to, or subtracted from, a date. + * The unit is equal to either 52 or 53 weeks. + * The estimated duration of a week-based-year is the same as that of a standard ISO + * year at {@code 365.2425 Days}. + *

+ * The rules for addition add the number of week-based-years to the existing value + * for the week-based-year field. If the resulting week-based-year only has 52 weeks, + * then the date will be in week 1 of the following week-based-year. + *

+ * This unit is an immutable and thread-safe singleton. + */ + public static final TemporalUnit WEEK_BASED_YEARS = Unit.WEEK_BASED_YEARS; + /** + * Unit that represents the concept of a quarter-year. + * For the ISO calendar system, it is equal to 3 months. + * The estimated duration of a quarter-year is one quarter of {@code 365.2425 Days}. + *

+ * This unit is an immutable and thread-safe singleton. + */ + public static final TemporalUnit QUARTER_YEARS = Unit.QUARTER_YEARS; + + /** + * Restricted constructor. + */ + private ISOFields() { + throw new AssertionError("Not instantiable"); + } + + //----------------------------------------------------------------------- + /** + * Implementation of the field. + */ + private static enum Field implements TemporalField { + DAY_OF_QUARTER { + @Override + public String getName() { + return "DayOfQuarter"; + } + @Override + public TemporalUnit getBaseUnit() { + return DAYS; + } + @Override + public TemporalUnit getRangeUnit() { + return QUARTER_YEARS; + } + @Override + public ValueRange range() { + return ValueRange.of(1, 90, 92); + } + @Override + public boolean doIsSupported(TemporalAccessor temporal) { + return temporal.isSupported(DAY_OF_YEAR) && temporal.isSupported(MONTH_OF_YEAR) && + temporal.isSupported(YEAR) && Chrono.from(temporal).equals(ISOChrono.INSTANCE); + } + @Override + public ValueRange doRange(TemporalAccessor temporal) { + if (doIsSupported(temporal) == false) { + throw new DateTimeException("Unsupported field: DayOfQuarter"); + } + long qoy = temporal.getLong(QUARTER_OF_YEAR); + if (qoy == 1) { + long year = temporal.getLong(YEAR); + return (ISOChrono.INSTANCE.isLeapYear(year) ? ValueRange.of(1, 91) : ValueRange.of(1, 90)); + } else if (qoy == 2) { + return ValueRange.of(1, 91); + } else if (qoy == 3 || qoy == 4) { + return ValueRange.of(1, 92); + } // else value not from 1 to 4, so drop through + return range(); + } + @Override + public long doGet(TemporalAccessor temporal) { + if (doIsSupported(temporal) == false) { + throw new DateTimeException("Unsupported field: DayOfQuarter"); + } + int doy = temporal.get(DAY_OF_YEAR); + int moy = temporal.get(MONTH_OF_YEAR); + long year = temporal.getLong(YEAR); + return doy - QUARTER_DAYS[((moy - 1) / 3) + (ISOChrono.INSTANCE.isLeapYear(year) ? 4 : 0)]; + } + @Override + public R doWith(R temporal, long newValue) { + long curValue = doGet(temporal); + range().checkValidValue(newValue, this); + return (R) temporal.with(DAY_OF_YEAR, temporal.getLong(DAY_OF_YEAR) + (newValue - curValue)); + } + }, + QUARTER_OF_YEAR { + @Override + public String getName() { + return "QuarterOfYear"; + } + @Override + public TemporalUnit getBaseUnit() { + return QUARTER_YEARS; + } + @Override + public TemporalUnit getRangeUnit() { + return YEARS; + } + @Override + public ValueRange range() { + return ValueRange.of(1, 4); + } + @Override + public boolean doIsSupported(TemporalAccessor temporal) { + return temporal.isSupported(MONTH_OF_YEAR) && Chrono.from(temporal).equals(ISOChrono.INSTANCE); + } + @Override + public long doGet(TemporalAccessor temporal) { + if (doIsSupported(temporal) == false) { + throw new DateTimeException("Unsupported field: DayOfQuarter"); + } + long moy = temporal.getLong(MONTH_OF_YEAR); + return ((moy + 2) / 3); + } + @Override + public R doWith(R temporal, long newValue) { + long curValue = doGet(temporal); + range().checkValidValue(newValue, this); + return (R) temporal.with(MONTH_OF_YEAR, temporal.getLong(MONTH_OF_YEAR) + (newValue - curValue) * 3); + } + @Override + public boolean resolve(DateTimeBuilder builder, long value) { + Long[] values = builder.queryFieldValues(YEAR, QUARTER_OF_YEAR, DAY_OF_QUARTER); + if (values[0] != null && values[1] != null && values[2] != null) { + int y = YEAR.range().checkValidIntValue(values[0], YEAR); + int qoy = QUARTER_OF_YEAR.range().checkValidIntValue(values[1], QUARTER_OF_YEAR); + int doq = DAY_OF_QUARTER.range().checkValidIntValue(values[2], DAY_OF_QUARTER); + LocalDate date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1).plusDays(doq - 1); + builder.addFieldValue(EPOCH_DAY, date.toEpochDay()); + builder.removeFieldValues(QUARTER_OF_YEAR, DAY_OF_QUARTER); + } + return false; + } + }, + WEEK_OF_WEEK_BASED_YEAR { + @Override + public String getName() { + return "WeekOfWeekBasedYear"; + } + @Override + public TemporalUnit getBaseUnit() { + return WEEKS; + } + @Override + public TemporalUnit getRangeUnit() { + return WEEK_BASED_YEARS; + } + @Override + public ValueRange range() { + return ValueRange.of(1, 52, 53); + } + @Override + public boolean doIsSupported(TemporalAccessor temporal) { + return temporal.isSupported(EPOCH_DAY); + } + @Override + public ValueRange doRange(TemporalAccessor temporal) { + return getWeekRange(LocalDate.from(temporal)); + } + @Override + public long doGet(TemporalAccessor temporal) { + return getWeek(LocalDate.from(temporal)); + } + @Override + public R doWith(R temporal, long newValue) { + ValueRange.of(1, 53).checkValidValue(newValue, this); + return (R) temporal.plus(Math.subtractExact(newValue, doGet(temporal)), WEEKS); + } + }, + WEEK_BASED_YEAR { + @Override + public String getName() { + return "WeekBasedYear"; + } + @Override + public TemporalUnit getBaseUnit() { + return WEEK_BASED_YEARS; + } + @Override + public TemporalUnit getRangeUnit() { + return FOREVER; + } + @Override + public ValueRange range() { + return YEAR.range(); + } + @Override + public boolean doIsSupported(TemporalAccessor temporal) { + return temporal.isSupported(EPOCH_DAY); + } + @Override + public long doGet(TemporalAccessor temporal) { + return getWeekBasedYear(LocalDate.from(temporal)); + } + @Override + public R doWith(R temporal, long newValue) { + int newVal = range().checkValidIntValue(newValue, WEEK_BASED_YEAR); + LocalDate date = LocalDate.from(temporal); + int week = getWeek(date); + date = date.withDayOfYear(180).withYear(newVal).with(WEEK_OF_WEEK_BASED_YEAR, week); + return (R) date.with(date); + } + @Override + public boolean resolve(DateTimeBuilder builder, long value) { + Long[] values = builder.queryFieldValues(WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, DAY_OF_WEEK); + if (values[0] != null && values[1] != null && values[2] != null) { + int wby = WEEK_BASED_YEAR.range().checkValidIntValue(values[0], WEEK_BASED_YEAR); + int week = WEEK_OF_WEEK_BASED_YEAR.range().checkValidIntValue(values[1], WEEK_OF_WEEK_BASED_YEAR); + int dow = DAY_OF_WEEK.range().checkValidIntValue(values[2], DAY_OF_WEEK); + LocalDate date = LocalDate.of(wby, 2, 1).with(WEEK_OF_WEEK_BASED_YEAR, week).with(DAY_OF_WEEK, dow); + builder.addFieldValue(EPOCH_DAY, date.toEpochDay()); + builder.removeFieldValues(WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, DAY_OF_WEEK); + } + return false; + } + }; + + @Override + public ValueRange doRange(TemporalAccessor temporal) { + return range(); + } + + @Override + public boolean resolve(DateTimeBuilder builder, long value) { + return false; + } + + @Override + public String toString() { + return getName(); + } + + //------------------------------------------------------------------------- + private static final int[] QUARTER_DAYS = {0, 90, 181, 273, 0, 91, 182, 274}; + + private static ValueRange getWeekRange(LocalDate date) { + int wby = getWeekBasedYear(date); + date = date.withDayOfYear(1).withYear(wby); + // 53 weeks if standard year starts on Thursday, or Wed in a leap year + if (date.getDayOfWeek() == THURSDAY || (date.getDayOfWeek() == WEDNESDAY && date.isLeapYear())) { + return ValueRange.of(1, 53); + } + return ValueRange.of(1, 52); + } + + private static int getWeek(LocalDate date) { + int dow0 = date.getDayOfWeek().ordinal(); + int doy0 = date.getDayOfYear() - 1; + int doyThu0 = doy0 + (3 - dow0); // adjust to mid-week Thursday (which is 3 indexed from zero) + int alignedWeek = doyThu0 / 7; + int firstThuDoy0 = doyThu0 - (alignedWeek * 7); + int firstMonDoy0 = firstThuDoy0 - 3; + if (firstMonDoy0 < -3) { + firstMonDoy0 += 7; + } + if (doy0 < firstMonDoy0) { + return (int) getWeekRange(date.withDayOfYear(180).minusYears(1)).getMaximum(); + } + int week = ((doy0 - firstMonDoy0) / 7) + 1; + if (week == 53) { + if ((firstMonDoy0 == -3 || (firstMonDoy0 == -2 && date.isLeapYear())) == false) { + week = 1; + } + } + return week; + } + + private static int getWeekBasedYear(LocalDate date) { + int year = date.getYear(); + int doy = date.getDayOfYear(); + if (doy <= 3) { + int dow = date.getDayOfWeek().ordinal(); + if (doy - dow < -2) { + year--; + } + } else if (doy >= 363) { + int dow = date.getDayOfWeek().ordinal(); + doy = doy - 363 - (date.isLeapYear() ? 1 : 0); + if (doy - dow >= 0) { + year++; + } + } + return year; + } + } + + //----------------------------------------------------------------------- + /** + * Implementation of the period unit. + */ + private static enum Unit implements TemporalUnit { + + /** + * Unit that represents the concept of a week-based-year. + */ + WEEK_BASED_YEARS("WeekBasedYears", Duration.ofSeconds(31556952L)), + /** + * Unit that represents the concept of a quarter-year. + */ + QUARTER_YEARS("QuarterYears", Duration.ofSeconds(31556952L / 4)); + + private final String name; + private final Duration duration; + + private Unit(String name, Duration estimatedDuration) { + this.name = name; + this.duration = estimatedDuration; + } + + @Override + public String getName() { + return name; + } + + @Override + public Duration getDuration() { + return duration; + } + + @Override + public boolean isDurationEstimated() { + return true; + } + + @Override + public boolean isSupported(Temporal temporal) { + return temporal.isSupported(EPOCH_DAY); + } + + @Override + public R doPlus(R dateTime, long periodToAdd) { + switch(this) { + case WEEK_BASED_YEARS: + return (R) dateTime.with(WEEK_BASED_YEAR, + Math.addExact(dateTime.get(WEEK_BASED_YEAR), periodToAdd)); + case QUARTER_YEARS: + // no overflow (256 is multiple of 4) + return (R) dateTime.plus(periodToAdd / 256, YEARS) + .plus((periodToAdd % 256) * 3, MONTHS); + default: + throw new IllegalStateException("Unreachable"); + } + } + + @Override + public SimplePeriod between(R dateTime1, R dateTime2) { + switch(this) { + case WEEK_BASED_YEARS: + long period = Math.subtractExact(dateTime2.getLong(WEEK_BASED_YEAR), + dateTime1.getLong(WEEK_BASED_YEAR)); + return new SimplePeriod(period, WEEK_BASED_YEARS); + case QUARTER_YEARS: + long period2 = Math.subtractExact(dateTime2.getLong(QUARTER_OF_YEAR), + dateTime1.getLong(QUARTER_OF_YEAR)); + return new SimplePeriod(period2, QUARTER_YEARS); + default: + throw new IllegalStateException("Unreachable"); + } + } + + @Override + public String toString() { + return getName(); + + } + } +} diff --git a/src/share/classes/java/time/temporal/JulianFields.java b/src/share/classes/java/time/temporal/JulianFields.java new file mode 100644 index 0000000000000000000000000000000000000000..79a9e96bac7807687b93249394a532cc9bc8410c --- /dev/null +++ b/src/share/classes/java/time/temporal/JulianFields.java @@ -0,0 +1,305 @@ +/* + * 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) 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 java.time.temporal; + +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.FOREVER; + +import java.io.InvalidObjectException; +import java.io.Serializable; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.format.DateTimeBuilder; + +/** + * A set of date fields that provide access to Julian Days. + *

+ * The Julian Day is a standard way of expressing date and time commonly used in the scientific community. + * It is expressed as a decimal number of whole days where days start at midday. + * This class represents variations on Julian Days that count whole days from midnight. + * + *

Specification for implementors

+ * This is an immutable and thread-safe class. + * + * @since 1.8 + */ +public final class JulianFields { + + /** + * The offset from Julian to EPOCH DAY. + */ + private static final long JULIAN_DAY_OFFSET = 2440588L; + + /** + * Julian Day field. + *

+ * This is an integer-based version of the Julian Day Number. + * Julian Day is a well-known system that represents the count of whole days since day 0, + * which is defined to be January 1, 4713 BCE in the Julian calendar, and -4713-11-24 Gregorian. + * The field has "JulianDay" as 'name', and 'DAYS' as 'baseUnit'. + * The field always refers to the local date-time, ignoring the offset or zone. + *

+ * For date-times, 'JULIAN_DAY.doGet()' assumes the same value from + * midnight until just before the next midnight. + * When 'JULIAN_DAY.doWith()' is applied to a date-time, the time of day portion remains unaltered. + * 'JULIAN_DAY.doWith()' and 'JULIAN_DAY.doGet()' only apply to {@code Temporal} objects that + * can be converted into {@link ChronoField#EPOCH_DAY}. + * A {@link DateTimeException} is thrown for any other type of object. + *

+ *

Astronomical and Scientific Notes

+ * The standard astronomical definition uses a fraction to indicate the time-of-day, + * thus 3.25 would represent the time 18:00, since days start at midday. + * This implementation uses an integer and days starting at midnight. + * The integer value for the Julian Day Number is the astronomical Julian Day value at midday + * of the date in question. + * This amounts to the astronomical Julian Day, rounded to an integer {@code JDN = floor(JD + 0.5)}. + *

+ *

+     *  | ISO date          |  Julian Day Number | Astronomical Julian Day |
+     *  | 1970-01-01T00:00  |         2,440,588  |         2,440,587.5     |
+     *  | 1970-01-01T06:00  |         2,440,588  |         2,440,587.75    |
+     *  | 1970-01-01T12:00  |         2,440,588  |         2,440,588.0     |
+     *  | 1970-01-01T18:00  |         2,440,588  |         2,440,588.25    |
+     *  | 1970-01-02T00:00  |         2,440,589  |         2,440,588.5     |
+     *  | 1970-01-02T06:00  |         2,440,589  |         2,440,588.75    |
+     *  | 1970-01-02T12:00  |         2,440,589  |         2,440,589.0     |
+     * 
+ *

+ * Julian Days are sometimes taken to imply Universal Time or UTC, but this + * implementation always uses the Julian Day number for the local date, + * regardless of the offset or time-zone. + */ + public static final TemporalField JULIAN_DAY = new Field("JulianDay", DAYS, FOREVER, JULIAN_DAY_OFFSET); + + /** + * Modified Julian Day field. + *

+ * This is an integer-based version of the Modified Julian Day Number. + * Modified Julian Day (MJD) is a well-known system that counts days continuously. + * It is defined relative to astronomical Julian Day as {@code MJD = JD - 2400000.5}. + * Each Modified Julian Day runs from midnight to midnight. + * The field always refers to the local date-time, ignoring the offset or zone. + *

+ * For date-times, 'MODIFIED_JULIAN_DAY.doGet()' assumes the same value from + * midnight until just before the next midnight. + * When 'MODIFIED_JULIAN_DAY.doWith()' is applied to a date-time, the time of day portion remains unaltered. + * 'MODIFIED_JULIAN_DAY.doWith()' and 'MODIFIED_JULIAN_DAY.doGet()' only apply to {@code Temporal} objects + * that can be converted into {@link ChronoField#EPOCH_DAY}. + * A {@link DateTimeException} is thrown for any other type of object. + *

+ * This implementation is an integer version of MJD with the decimal part rounded to floor. + *

+ *

Astronomical and Scientific Notes

+ *
+     *  | ISO date          | Modified Julian Day |      Decimal MJD |
+     *  | 1970-01-01T00:00  |             40,587  |       40,587.0   |
+     *  | 1970-01-01T06:00  |             40,587  |       40,587.25  |
+     *  | 1970-01-01T12:00  |             40,587  |       40,587.5   |
+     *  | 1970-01-01T18:00  |             40,587  |       40,587.75  |
+     *  | 1970-01-02T00:00  |             40,588  |       40,588.0   |
+     *  | 1970-01-02T06:00  |             40,588  |       40,588.25  |
+     *  | 1970-01-02T12:00  |             40,588  |       40,588.5   |
+     * 
+ *

+ * Modified Julian Days are sometimes taken to imply Universal Time or UTC, but this + * implementation always uses the Modified Julian Day for the local date, + * regardless of the offset or time-zone. + */ + public static final TemporalField MODIFIED_JULIAN_DAY = new Field("ModifiedJulianDay", DAYS, FOREVER, 40587L); + + /** + * Rata Die field. + *

+ * Rata Die counts whole days continuously starting day 1 at midnight at the beginning of 0001-01-01 (ISO). + * The field always refers to the local date-time, ignoring the offset or zone. + *

+ * For date-times, 'RATA_DIE.doGet()' assumes the same value from + * midnight until just before the next midnight. + * When 'RATA_DIE.doWith()' is applied to a date-time, the time of day portion remains unaltered. + * 'MODIFIED_JULIAN_DAY.doWith()' and 'RATA_DIE.doGet()' only apply to {@code Temporal} objects + * that can be converted into {@link ChronoField#EPOCH_DAY}. + * A {@link DateTimeException} is thrown for any other type of object. + */ + public static final TemporalField RATA_DIE = new Field("RataDie", DAYS, FOREVER, 719163L); + + /** + * Restricted constructor. + */ + private JulianFields() { + throw new AssertionError("Not instantiable"); + } + + /** + * implementation of JulianFields. Each instance is a singleton. + */ + private static class Field implements TemporalField, Serializable { + + private static final long serialVersionUID = -7501623920830201812L; + + private final String name; + private final transient TemporalUnit baseUnit; + private final transient TemporalUnit rangeUnit; + private final transient ValueRange range; + private final transient long offset; + + private Field(String name, TemporalUnit baseUnit, TemporalUnit rangeUnit, long offset) { + this.name = name; + this.baseUnit = baseUnit; + this.rangeUnit = rangeUnit; + this.range = ValueRange.of(-365243219162L + offset, 365241780471L + offset); + this.offset = offset; + } + + + /** + * Resolve the object from the stream to the appropriate singleton. + * @return one of the singleton objects {@link #JULIAN_DAY}, + * {@link #MODIFIED_JULIAN_DAY}, or {@link #RATA_DIE}. + * @throws InvalidObjectException if the object in the stream is not one of the singletons. + */ + private Object readResolve() throws InvalidObjectException { + if (JULIAN_DAY.getName().equals(name)) { + return JULIAN_DAY; + } else if (MODIFIED_JULIAN_DAY.getName().equals(name)) { + return MODIFIED_JULIAN_DAY; + } else if (RATA_DIE.getName().equals(name)) { + return RATA_DIE; + } else { + throw new InvalidObjectException("Not one of the singletons"); + } + } + + //----------------------------------------------------------------------- + @Override + public String getName() { + return name; + } + + @Override + public TemporalUnit getBaseUnit() { + return baseUnit; + } + + @Override + public TemporalUnit getRangeUnit() { + return rangeUnit; + } + + @Override + public ValueRange range() { + return range; + } + + //----------------------------------------------------------------------- + @Override + public boolean doIsSupported(TemporalAccessor temporal) { + return temporal.isSupported(EPOCH_DAY); + } + + @Override + public ValueRange doRange(TemporalAccessor temporal) { + if (doIsSupported(temporal) == false) { + throw new DateTimeException("Unsupported field: " + this); + } + return range(); + } + + @Override + public long doGet(TemporalAccessor temporal) { + return temporal.getLong(EPOCH_DAY) + offset; + } + + @Override + public R doWith(R temporal, long newValue) { + if (range().isValidValue(newValue) == false) { + throw new DateTimeException("Invalid value: " + name + " " + newValue); + } + return (R) temporal.with(EPOCH_DAY, Math.subtractExact(newValue, offset)); + } + + //----------------------------------------------------------------------- + @Override + public boolean resolve(DateTimeBuilder builder, long value) { + boolean changed = false; + changed = resolve0(JULIAN_DAY, builder, changed); + changed = resolve0(MODIFIED_JULIAN_DAY, builder, changed); + changed = resolve0(RATA_DIE, builder, changed); + return changed; + } + + private boolean resolve0(TemporalField field, DateTimeBuilder builder, boolean changed) { + if (builder.containsFieldValue(field)) { + builder.addCalendrical(LocalDate.ofEpochDay(Math.subtractExact(builder.getFieldValue(JULIAN_DAY), JULIAN_DAY_OFFSET))); + builder.removeFieldValue(JULIAN_DAY); + changed = true; + } + return changed; + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + return getName(); + } + } +} diff --git a/src/share/classes/java/time/temporal/MonthDay.java b/src/share/classes/java/time/temporal/MonthDay.java new file mode 100644 index 0000000000000000000000000000000000000000..7707cd099a5ffd71fdc060bfda719c589795b759 --- /dev/null +++ b/src/share/classes/java/time/temporal/MonthDay.java @@ -0,0 +1,755 @@ +/* + * 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 java.time.temporal; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.Clock; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.Month; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.util.Objects; + +/** + * A month-day in the ISO-8601 calendar system, such as {@code --12-03}. + *

+ * {@code MonthDay} is an immutable date-time object that represents the combination + * of a year and month. Any field that can be derived from a month and day, such as + * quarter-of-year, can be obtained. + *

+ * This class does not store or represent a year, time or time-zone. + * For example, the value "December 3rd" can be stored in a {@code MonthDay}. + *

+ * Since a {@code MonthDay} does not possess a year, the leap day of + * February 29th is considered valid. + *

+ * This class implements {@link TemporalAccessor} rather than {@link Temporal}. + * This is because it is not possible to define whether February 29th is valid or not + * without external information, preventing the implementation of plus/minus. + * Related to this, {@code MonthDay} only provides access to query and set the fields + * {@code MONTH_OF_YEAR} and {@code DAY_OF_MONTH}. + *

+ * The ISO-8601 calendar system is the modern civil calendar system used today + * in most of the world. It is equivalent to the proleptic Gregorian calendar + * system, in which today's rules for leap years are applied for all time. + * For most applications written today, the ISO-8601 rules are entirely suitable. + * However, any application that makes use of historical dates, and requires them + * to be accurate will find the ISO-8601 approach unsuitable. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class MonthDay + implements TemporalAccessor, TemporalAdjuster, Comparable, Serializable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = -939150713474957432L; + /** + * Parser. + */ + private static final DateTimeFormatter PARSER = new DateTimeFormatterBuilder() + .appendLiteral("--") + .appendValue(MONTH_OF_YEAR, 2) + .appendLiteral('-') + .appendValue(DAY_OF_MONTH, 2) + .toFormatter(); + + /** + * The month-of-year, not null. + */ + private final int month; + /** + * The day-of-month. + */ + private final int day; + + //----------------------------------------------------------------------- + /** + * Obtains the current month-day from the system clock in the default time-zone. + *

+ * This will query the {@link java.time.Clock#systemDefaultZone() system clock} in the default + * time-zone to obtain the current month-day. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @return the current month-day using the system clock and default time-zone, not null + */ + public static MonthDay now() { + return now(Clock.systemDefaultZone()); + } + + /** + * Obtains the current month-day from the system clock in the specified time-zone. + *

+ * This will query the {@link Clock#system(java.time.ZoneId) system clock} to obtain the current month-day. + * Specifying the time-zone avoids dependence on the default time-zone. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @param zone the zone ID to use, not null + * @return the current month-day using the system clock, not null + */ + public static MonthDay now(ZoneId zone) { + return now(Clock.system(zone)); + } + + /** + * Obtains the current month-day from the specified clock. + *

+ * This will query the specified clock to obtain the current month-day. + * Using this method allows the use of an alternate clock for testing. + * The alternate clock may be introduced using {@link Clock dependency injection}. + * + * @param clock the clock to use, not null + * @return the current month-day, not null + */ + public static MonthDay now(Clock clock) { + final LocalDate now = LocalDate.now(clock); // called once + return MonthDay.of(now.getMonth(), now.getDayOfMonth()); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code MonthDay}. + *

+ * The day-of-month must be valid for the month within a leap year. + * Hence, for February, day 29 is valid. + *

+ * For example, passing in April and day 31 will throw an exception, as + * there can never be April 31st in any year. By contrast, passing in + * February 29th is permitted, as that month-day can sometimes be valid. + * + * @param month the month-of-year to represent, not null + * @param dayOfMonth the day-of-month to represent, from 1 to 31 + * @return the month-day, 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 + */ + public static MonthDay of(Month month, int dayOfMonth) { + Objects.requireNonNull(month, "month"); + DAY_OF_MONTH.checkValidValue(dayOfMonth); + if (dayOfMonth > month.maxLength()) { + throw new DateTimeException("Illegal value for DayOfMonth field, value " + dayOfMonth + + " is not valid for month " + month.name()); + } + return new MonthDay(month.getValue(), dayOfMonth); + } + + /** + * Obtains an instance of {@code MonthDay}. + *

+ * The day-of-month must be valid for the month within a leap year. + * Hence, for month 2 (February), day 29 is valid. + *

+ * For example, passing in month 4 (April) and day 31 will throw an exception, as + * there can never be April 31st in any year. By contrast, passing in + * February 29th is permitted, as that month-day can sometimes be valid. + * + * @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 month-day, 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 + */ + public static MonthDay of(int month, int dayOfMonth) { + return of(Month.of(month), dayOfMonth); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code MonthDay} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code MonthDay}. + *

+ * The conversion extracts the {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} and + * {@link ChronoField#DAY_OF_MONTH DAY_OF_MONTH} fields. + * The extraction is only permitted if the date-time has an ISO chronology. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used in queries via method reference, {@code MonthDay::from}. + * + * @param temporal the temporal object to convert, not null + * @return the month-day, not null + * @throws DateTimeException if unable to convert to a {@code MonthDay} + */ + public static MonthDay from(TemporalAccessor temporal) { + if (temporal instanceof MonthDay) { + return (MonthDay) temporal; + } + try { + if (ISOChrono.INSTANCE.equals(Chrono.from(temporal)) == false) { + temporal = LocalDate.from(temporal); + } + return of(temporal.get(MONTH_OF_YEAR), temporal.get(DAY_OF_MONTH)); + } catch (DateTimeException ex) { + throw new DateTimeException("Unable to obtain MonthDay from TemporalAccessor: " + temporal.getClass(), ex); + } + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code MonthDay} from a text string such as {@code --12-03}. + *

+ * The string must represent a valid month-day. + * The format is {@code --MM-dd}. + * + * @param text the text to parse such as "--12-03", not null + * @return the parsed month-day, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static MonthDay parse(CharSequence text) { + return parse(text, PARSER); + } + + /** + * Obtains an instance of {@code MonthDay} from a text string using a specific formatter. + *

+ * The text is parsed using the formatter, returning a month-day. + * + * @param text the text to parse, not null + * @param formatter the formatter to use, not null + * @return the parsed month-day, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static MonthDay parse(CharSequence text, DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.parse(text, MonthDay::from); + } + + //----------------------------------------------------------------------- + /** + * Constructor, previously validated. + * + * @param month the month-of-year to represent, validated from 1 to 12 + * @param dayOfMonth the day-of-month to represent, validated from 1 to 29-31 + */ + private MonthDay(int month, int dayOfMonth) { + this.month = month; + this.day = dayOfMonth; + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this month-day can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and + * {@link #get(TemporalField) get} methods will throw an exception. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date-time. + * The supported fields are: + *

    + *
  • {@code MONTH_OF_YEAR} + *
  • {@code YEAR} + *
+ * All other {@code ChronoField} instances will return false. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the field is supported is determined by the field. + * + * @param field the field to check, null returns false + * @return true if the field is supported on this month-day, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == MONTH_OF_YEAR || field == DAY_OF_MONTH; + } + return field != null && field.doIsSupported(this); + } + + /** + * Gets the range of valid values for the specified field. + *

+ * The range object expresses the minimum and maximum valid values for a field. + * This month-day is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return + * appropriate range instances. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + @Override + public ValueRange range(TemporalField field) { + if (field == MONTH_OF_YEAR) { + return field.range(); + } else if (field == DAY_OF_MONTH) { + return ValueRange.of(1, getMonth().minLength(), getMonth().maxLength()); + } + return TemporalAccessor.super.range(field); + } + + /** + * Gets the value of the specified field from this month-day as an {@code int}. + *

+ * This queries this month-day for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this month-day. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override // override for Javadoc + public int get(TemporalField field) { + return range(field).checkValidIntValue(getLong(field), field); + } + + /** + * Gets the value of the specified field from this month-day as a {@code long}. + *

+ * This queries this month-day for the value for the specified field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this month-day. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + // alignedDOW and alignedWOM not supported because they cannot be set in with() + case DAY_OF_MONTH: return day; + case MONTH_OF_YEAR: return month; + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doGet(this); + } + + //----------------------------------------------------------------------- + /** + * Gets the month-of-year field using the {@code Month} enum. + *

+ * This method returns the enum {@link Month} for the month. + * This avoids confusion as to what {@code int} values mean. + * If you need access to the primitive {@code int} value then the enum + * provides the {@link Month#getValue() int value}. + * + * @return the month-of-year, not null + */ + public Month getMonth() { + return Month.of(month); + } + + /** + * Gets the day-of-month field. + *

+ * 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; + } + + //----------------------------------------------------------------------- + /** + * Checks if the year is valid for this month-day. + *

+ * This method checks whether this month and day and the input year form + * a valid date. This can only return false for February 29th. + * + * @param year the year to validate, an out of range value returns false + * @return true if the year is valid for this month-day + * @see Year#isValidMonthDay(MonthDay) + */ + public boolean isValidYear(int year) { + return (day == 29 && month == 2 && Year.isLeap(year) == false) == false; + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code MonthDay} with the month-of-year altered. + *

+ * This returns a month-day with the specified month. + * If the day-of-month is invalid for the specified month, the day will + * be adjusted to the last valid day-of-month. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param month the month-of-year to set in the returned month-day, from 1 (January) to 12 (December) + * @return a {@code MonthDay} based on this month-day with the requested month, not null + * @throws DateTimeException if the month-of-year value is invalid + */ + public MonthDay withMonth(int month) { + return with(Month.of(month)); + } + + /** + * Returns a copy of this {@code MonthDay} with the month-of-year altered. + *

+ * This returns a month-day with the specified month. + * If the day-of-month is invalid for the specified month, the day will + * be adjusted to the last valid day-of-month. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param month the month-of-year to set in the returned month-day, not null + * @return a {@code MonthDay} based on this month-day with the requested month, not null + */ + public MonthDay with(Month month) { + Objects.requireNonNull(month, "month"); + if (month.getValue() == this.month) { + return this; + } + int day = Math.min(this.day, month.maxLength()); + return new MonthDay(month.getValue(), day); + } + + /** + * Returns a copy of this {@code MonthDay} with the day-of-month altered. + *

+ * This returns a month-day with the specified day-of-month. + * If the day-of-month is invalid for the month, an exception is thrown. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param dayOfMonth the day-of-month to set in the return month-day, from 1 to 31 + * @return a {@code MonthDay} based on this month-day with the requested day, not null + * @throws DateTimeException if the day-of-month value is invalid + * @throws DateTimeException if the day-of-month is invalid for the month + */ + public MonthDay withDayOfMonth(int dayOfMonth) { + if (dayOfMonth == this.day) { + return this; + } + return of(month, dayOfMonth); + } + + //----------------------------------------------------------------------- + /** + * Queries this month-day using the specified query. + *

+ * This queries this month-day using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.chrono()) { + return (R) ISOChrono.INSTANCE; + } + return TemporalAccessor.super.query(query); + } + + /** + * Adjusts the specified temporal object to have this month-day. + *

+ * This returns a temporal object of the same observable type as the input + * with the month and day-of-month changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * twice, passing {@link ChronoField#MONTH_OF_YEAR} and + * {@link ChronoField#DAY_OF_MONTH} as the fields. + * If the specified temporal object does not use the ISO calendar system then + * a {@code DateTimeException} is thrown. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisMonthDay.adjustInto(temporal);
+     *   temporal = temporal.with(thisMonthDay);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal adjustInto(Temporal temporal) { + if (Chrono.from(temporal).equals(ISOChrono.INSTANCE) == false) { + throw new DateTimeException("Adjustment only supported on ISO date-time"); + } + temporal = temporal.with(MONTH_OF_YEAR, month); + return temporal.with(DAY_OF_MONTH, Math.min(temporal.range(DAY_OF_MONTH).getMaximum(), day)); + } + + //----------------------------------------------------------------------- + /** + * Returns a date formed from this month-day at the specified year. + *

+ * This combines this month-day and the specified year to form a {@code LocalDate}. + * A month-day of February 29th will be adjusted to February 28th in the resulting + * date if the year is not a leap year. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param year the year to use, from MIN_YEAR to MAX_YEAR + * @return the local date formed from this month-day and the specified year, not null + * @see Year#atMonthDay(MonthDay) + */ + public LocalDate atYear(int year) { + return LocalDate.of(year, month, isValidYear(year) ? day : 28); + } + + //----------------------------------------------------------------------- + /** + * Compares this month-day to another month-day. + *

+ * The comparison is based first on value of the month, then on the value of the day. + * It is "consistent with equals", as defined by {@link Comparable}. + * + * @param other the other month-day to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + public int compareTo(MonthDay other) { + int cmp = (month - other.month); + if (cmp == 0) { + cmp = (day - other.day); + } + return cmp; + } + + /** + * Is this month-day after the specified month-day. + * + * @param other the other month-day to compare to, not null + * @return true if this is after the specified month-day + */ + public boolean isAfter(MonthDay other) { + return compareTo(other) > 0; + } + + /** + * Is this month-day before the specified month-day. + * + * @param other the other month-day to compare to, not null + * @return true if this point is before the specified month-day + */ + public boolean isBefore(MonthDay other) { + return compareTo(other) < 0; + } + + //----------------------------------------------------------------------- + /** + * Checks if this month-day is equal to another month-day. + *

+ * The comparison is based on the time-line position of the month-day within a year. + * + * @param obj the object to check, null returns false + * @return true if this is equal to the other month-day + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof MonthDay) { + MonthDay other = (MonthDay) obj; + return month == other.month && day == other.day; + } + return false; + } + + /** + * A hash code for this month-day. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return (month << 6) + day; + } + + //----------------------------------------------------------------------- + /** + * Outputs this month-day as a {@code String}, such as {@code --12-03}. + *

+ * The output will be in the format {@code --MM-dd}: + * + * @return a string representation of this month-day, not null + */ + @Override + public String toString() { + return new StringBuilder(10).append("--") + .append(month < 10 ? "0" : "").append(month) + .append(day < 10 ? "-0" : "-").append(day) + .toString(); + } + + /** + * Outputs this month-day as a {@code String} using the formatter. + *

+ * This month-day will be passed to the formatter + * {@link DateTimeFormatter#print(TemporalAccessor) print method}. + * + * @param formatter the formatter to use, not null + * @return the formatted month-day string, not null + * @throws DateTimeException if an error occurs during printing + */ + public String toString(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.print(this); + } + + //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(6);  // identifies this as a Year
+     *  out.writeByte(month);
+     *  out.writeByte(day);
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.MONTH_DAY_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(DataOutput out) throws IOException { + out.writeByte(month); + out.writeByte(day); + } + + static MonthDay readExternal(DataInput in) throws IOException { + byte month = in.readByte(); + byte day = in.readByte(); + return MonthDay.of(month, day); + } + +} diff --git a/src/share/classes/java/time/temporal/OffsetDate.java b/src/share/classes/java/time/temporal/OffsetDate.java new file mode 100644 index 0000000000000000000000000000000000000000..2503de927bd4f24001b2016d93837a7e36580bf2 --- /dev/null +++ b/src/share/classes/java/time/temporal/OffsetDate.java @@ -0,0 +1,1351 @@ +/* + * 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 java.time.temporal; + +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.OFFSET_SECONDS; +import static java.time.temporal.ChronoLocalDateTimeImpl.SECONDS_PER_DAY; +import static java.time.temporal.ChronoUnit.DAYS; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.Clock; +import java.time.DateTimeException; +import java.time.DayOfWeek; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.zone.ZoneRules; +import java.util.Objects; + +/** + * A date with an offset from UTC/Greenwich in the ISO-8601 calendar system, + * such as {@code 2007-12-03+01:00}. + *

+ * {@code OffsetDate} is an immutable date-time object that represents a date, often viewed + * as year-month-day-offset. This object can also access other date fields such as + * day-of-year, day-of-week and week-of-year. + *

+ * This class does not store or represent a time. + * For example, the value "2nd October 2007 +02:00" can be stored + * in an {@code OffsetDate}. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class OffsetDate + implements Temporal, TemporalAdjuster, Comparable, Serializable { + + /** + * The minimum supported {@code OffsetDate}, '-999999999-01-01+18:00'. + * This is the minimum local date in the maximum offset + * (larger offsets are earlier on the time-line). + * This combines {@link LocalDate#MIN} and {@link ZoneOffset#MAX}. + * This could be used by an application as a "far past" date. + */ + public static final OffsetDate MIN = LocalDate.MIN.atOffset(ZoneOffset.MAX); + /** + * The maximum supported {@code OffsetDate}, '+999999999-12-31-18:00'. + * This is the maximum local date in the minimum offset + * (larger negative offsets are later on the time-line). + * This combines {@link LocalDate#MAX} and {@link ZoneOffset#MIN}. + * This could be used by an application as a "far future" date. + */ + public static final OffsetDate MAX = LocalDate.MAX.atOffset(ZoneOffset.MIN); + + /** + * Serialization version. + */ + private static final long serialVersionUID = -4382054179074397774L; + + /** + * The local date. + */ + private final LocalDate date; + /** + * The offset from UTC/Greenwich. + */ + private final ZoneOffset offset; + + //----------------------------------------------------------------------- + /** + * Obtains the current date from the system clock in the default time-zone. + *

+ * This will query the {@link java.time.Clock#systemDefaultZone() system clock} in the default + * time-zone to obtain the current date. + * The offset will be calculated from the time-zone in the clock. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @return the current date using the system clock, not null + */ + public static OffsetDate now() { + return now(Clock.systemDefaultZone()); + } + + /** + * Obtains the current date from the system clock in the specified time-zone. + *

+ * This will query the {@link Clock#system(java.time.ZoneId) system clock} to obtain the current date. + * Specifying the time-zone avoids dependence on the default time-zone. + * The offset will be calculated from the specified time-zone. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @param zone the zone ID to use, not null + * @return the current date using the system clock, not null + */ + public static OffsetDate now(ZoneId zone) { + return now(Clock.system(zone)); + } + + /** + * Obtains the current date from the specified clock. + *

+ * This will query the specified clock to obtain the current date - today. + * The offset will be calculated from the time-zone in the clock. + *

+ * Using this method allows the use of an alternate clock for testing. + * The alternate clock may be introduced using {@link Clock dependency injection}. + * + * @param clock the clock to use, not null + * @return the current date, not null + */ + public static OffsetDate now(Clock clock) { + Objects.requireNonNull(clock, "clock"); + final Instant now = clock.instant(); // called once + return ofInstant(now, clock.getZone().getRules().getOffset(now)); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code OffsetDate} from a local date and an offset. + * + * @param date the local date, not null + * @param offset the zone offset, not null + * @return the offset date, not null + */ + public static OffsetDate of(LocalDate date, ZoneOffset offset) { + return new OffsetDate(date, offset); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code OffsetDate} from an {@code Instant} and zone ID. + *

+ * This creates an offset date with the same instant as midnight at the + * start of day of the instant specified. + * Finding the offset from UTC/Greenwich is simple as there is only one valid + * offset for each instant. + * + * @param instant the instant to create the time from, not null + * @param zone the time-zone, which may be an offset, not null + * @return the offset time, not null + */ + public static OffsetDate ofInstant(Instant instant, ZoneId zone) { + Objects.requireNonNull(instant, "instant"); + Objects.requireNonNull(zone, "zone"); + ZoneRules rules = zone.getRules(); + ZoneOffset offset = rules.getOffset(instant); + long epochSec = instant.getEpochSecond() + offset.getTotalSeconds(); // overflow caught later + long epochDay = Math.floorDiv(epochSec, SECONDS_PER_DAY); + LocalDate date = LocalDate.ofEpochDay(epochDay); + return new OffsetDate(date, offset); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code OffsetDate} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code OffsetDate}. + *

+ * The conversion extracts and combines {@code LocalDate} and {@code ZoneOffset}. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used in queries via method reference, {@code OffsetDate::from}. + * + * @param temporal the temporal object to convert, not null + * @return the offset date, not null + * @throws DateTimeException if unable to convert to an {@code OffsetDate} + */ + public static OffsetDate from(TemporalAccessor temporal) { + if (temporal instanceof OffsetDate) { + return (OffsetDate) temporal; + } + try { + LocalDate date = LocalDate.from(temporal); + ZoneOffset offset = ZoneOffset.from(temporal); + return new OffsetDate(date, offset); + } catch (DateTimeException ex) { + throw new DateTimeException("Unable to obtain OffsetDate from TemporalAccessor: " + temporal.getClass(), ex); + } + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code OffsetDate} from a text string such as {@code 2007-12-03+01:00}. + *

+ * The string must represent a valid date and is parsed using + * {@link java.time.format.DateTimeFormatters#isoOffsetDate()}. + * + * @param text the text to parse such as "2007-12-03+01:00", not null + * @return the parsed offset date, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static OffsetDate parse(CharSequence text) { + return parse(text, DateTimeFormatters.isoOffsetDate()); + } + + /** + * Obtains an instance of {@code OffsetDate} from a text string using a specific formatter. + *

+ * The text is parsed using the formatter, returning a date. + * + * @param text the text to parse, not null + * @param formatter the formatter to use, not null + * @return the parsed offset date, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static OffsetDate parse(CharSequence text, DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.parse(text, OffsetDate::from); + } + + //----------------------------------------------------------------------- + /** + * Constructor. + * + * @param date the local date, not null + * @param offset the zone offset, not null + */ + private OffsetDate(LocalDate date, ZoneOffset offset) { + this.date = Objects.requireNonNull(date, "date"); + this.offset = Objects.requireNonNull(offset, "offset"); + } + + /** + * Returns a new date based on this one, returning {@code this} where possible. + * + * @param date the date to create with, not null + * @param offset the zone offset to create with, not null + */ + private OffsetDate with(LocalDate date, ZoneOffset offset) { + if (this.date == date && this.offset.equals(offset)) { + return this; + } + return new OffsetDate(date, offset); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this date can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and + * {@link #get(TemporalField) get} methods will throw an exception. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date-time. + * The supported fields are: + *

    + *
  • {@code DAY_OF_WEEK} + *
  • {@code ALIGNED_DAY_OF_WEEK_IN_MONTH} + *
  • {@code ALIGNED_DAY_OF_WEEK_IN_YEAR} + *
  • {@code DAY_OF_MONTH} + *
  • {@code DAY_OF_YEAR} + *
  • {@code EPOCH_DAY} + *
  • {@code ALIGNED_WEEK_OF_MONTH} + *
  • {@code ALIGNED_WEEK_OF_YEAR} + *
  • {@code MONTH_OF_YEAR} + *
  • {@code EPOCH_MONTH} + *
  • {@code YEAR_OF_ERA} + *
  • {@code YEAR} + *
  • {@code ERA} + *
  • {@code OFFSET_SECONDS} + *
+ * All other {@code ChronoField} instances will return false. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the field is supported is determined by the field. + * + * @param field the field to check, null returns false + * @return true if the field is supported on this date, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return ((ChronoField) field).isDateField() || field == OFFSET_SECONDS; + } + return field != null && field.doIsSupported(this); + } + + /** + * Gets the range of valid values for the specified field. + *

+ * The range object expresses the minimum and maximum valid values for a field. + * This date is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return + * appropriate range instances. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + @Override + public ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + if (field == OFFSET_SECONDS) { + return field.range(); + } + return date.range(field); + } + return field.doRange(this); + } + + /** + * Gets the value of the specified field from this date as an {@code int}. + *

+ * This queries this date for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date, except {@code EPOCH_DAY} and {@code EPOCH_MONTH} + * which are too large to fit in an {@code int} and throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override // override for Javadoc + public int get(TemporalField field) { + return Temporal.super.get(field); + } + + /** + * Gets the value of the specified field from this date as a {@code long}. + *

+ * This queries this date for the value for the specified field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + if (field == OFFSET_SECONDS) { + return getOffset().getTotalSeconds(); + } + return date.getLong(field); + } + return field.doGet(this); + } + + //----------------------------------------------------------------------- + /** + * Gets the zone offset, such as '+01:00'. + *

+ * This is the offset of the local date from UTC/Greenwich. + * + * @return the zone offset, not null + */ + public ZoneOffset getOffset() { + return offset; + } + + /** + * Returns a copy of this {@code OffsetDate} with the specified offset. + *

+ * This method returns an object with the same {@code LocalDate} and the specified {@code ZoneOffset}. + * No calculation is needed or performed. + * For example, if this time represents {@code 2007-12-03+02:00} and the offset specified is + * {@code +03:00}, then this method will return {@code 2007-12-03+03:00}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param offset the zone offset to change to, not null + * @return an {@code OffsetDate} based on this date with the requested offset, not null + */ + public OffsetDate withOffset(ZoneOffset offset) { + Objects.requireNonNull(offset, "offset"); + return with(date, offset); + } + + //----------------------------------------------------------------------- + /** + * Gets the {@code LocalDate} part of this date-time. + *

+ * 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. + *

+ * This method returns the primitive {@code int} value for the year. + *

+ * 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 from 1 to 12. + *

+ * This method returns the month as an {@code int} from 1 to 12. + * Application code is frequently clearer if the enum {@link Month} + * is used by calling {@link #getMonth()}. + * + * @return the month-of-year, from 1 to 12 + * @see #getMonth() + */ + public int getMonthValue() { + return date.getMonthValue(); + } + + /** + * Gets the month-of-year field using the {@code Month} enum. + *

+ * This method returns the enum {@link Month} for the month. + * This avoids confusion as to what {@code int} values mean. + * If you need access to the primitive {@code int} value then the enum + * provides the {@link Month#getValue() int value}. + * + * @return the month-of-year, not null + * @see #getMonthValue() + */ + public Month getMonth() { + return date.getMonth(); + } + + /** + * Gets the day-of-month field. + *

+ * 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-year field. + *

+ * This method returns the primitive {@code int} value for the day-of-year. + * + * @return the day-of-year, from 1 to 365, or 366 in a leap year + */ + public int getDayOfYear() { + return date.getDayOfYear(); + } + + /** + * Gets the day-of-week field, which is an enum {@code DayOfWeek}. + *

+ * This method returns the enum {@link java.time.DayOfWeek} for the day-of-week. + * This avoids confusion as to what {@code int} values mean. + * If you need access to the primitive {@code int} value then the enum + * provides the {@link java.time.DayOfWeek#getValue() int value}. + *

+ * Additional information can be obtained from the {@code DayOfWeek}. + * This includes textual names of the values. + * + * @return the day-of-week, not null + */ + public DayOfWeek getDayOfWeek() { + return date.getDayOfWeek(); + } + + //----------------------------------------------------------------------- + /** + * Returns an adjusted copy of this date. + *

+ * This returns a new {@code OffsetDate}, based on this one, with the date adjusted. + * The adjustment takes place using the specified adjuster strategy object. + * Read the documentation of the adjuster to understand what adjustment will be made. + *

+ * A simple adjuster might simply set the one of the fields, such as the year field. + * A more complex adjuster might set the date to the last day of the month. + * A selection of common adjustments is provided in {@link java.time.temporal.Adjusters}. + * These include finding the "last day of the month" and "next Wednesday". + * Key date-time classes also implement the {@code TemporalAdjuster} interface, + * such as {@link Month} and {@link java.time.temporal.MonthDay MonthDay}. + * The adjuster is responsible for handling special cases, such as the varying + * lengths of month and leap years. + *

+ * For example this code returns a date on the last day of July: + *

+     *  import static java.time.Month.*;
+     *  import static java.time.temporal.Adjusters.*;
+     *
+     *  result = offsetDate.with(JULY).with(lastDayOfMonth());
+     * 
+ *

+ * The classes {@link LocalDate} and {@link ZoneOffset} implement {@code TemporalAdjuster}, + * thus this method can be used to change the date or offset: + *

+     *  result = offsetDate.with(date);
+     *  result = offsetDate.with(offset);
+     * 
+ *

+ * The result of this method is obtained by invoking the + * {@link TemporalAdjuster#adjustInto(Temporal)} method on the + * specified adjuster passing {@code this} as the argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adjuster the adjuster to use, not null + * @return an {@code OffsetDate} based on {@code this} with the adjustment made, not null + * @throws DateTimeException if the adjustment cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public OffsetDate with(TemporalAdjuster adjuster) { + // optimizations + if (adjuster instanceof LocalDate) { + return with((LocalDate) adjuster, offset); + } else if (adjuster instanceof ZoneOffset) { + return with(date, (ZoneOffset) adjuster); + } else if (adjuster instanceof OffsetDate) { + return (OffsetDate) adjuster; + } + return (OffsetDate) adjuster.adjustInto(this); + } + + /** + * Returns a copy of this date with the specified field set to a new value. + *

+ * This returns a new {@code OffsetDate}, based on this one, with the value + * for the specified field changed. + * This can be used to change any supported field, such as the year, month or day-of-month. + * If it is not possible to set the value, because the field is not supported or for + * some other reason, an exception is thrown. + *

+ * In some cases, changing the specified field can cause the resulting date to become invalid, + * such as changing the month from 31st January to February would make the day-of-month invalid. + * In cases like this, the field is responsible for resolving the date. Typically it will choose + * the previous valid date, which would be the last valid day of February in this example. + *

+ * If the field is a {@link ChronoField} then the adjustment is implemented here. + *

+ * The {@code OFFSET_SECONDS} field will return a date with the specified offset. + * The local date is unaltered. If the new offset value is outside the valid range + * then a {@code DateTimeException} will be thrown. + *

+ * The other {@link #isSupported(TemporalField) supported fields} will behave as per + * the matching method on {@link LocalDate#with(TemporalField, long)} LocalDate}. + * In this case, the offset is not part of the calculation and will be unchanged. + *

+ * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doWith(Temporal, long)} + * passing {@code this} as the argument. In this case, the field determines + * whether and how to adjust the instant. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param field the field to set in the result, not null + * @param newValue the new value of the field in the result + * @return an {@code OffsetDate} based on {@code this} with the specified field set, not null + * @throws DateTimeException if the field cannot be set + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public OffsetDate with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + if (field == OFFSET_SECONDS) { + ChronoField f = (ChronoField) field; + return with(date, ZoneOffset.ofTotalSeconds(f.checkValidIntValue(newValue))); + } + return with(date.with(field, newValue), offset); + } + return field.doWith(this, newValue); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code OffsetDate} with the year altered. + * The offset does not affect the calculation and will be the same in the result. + * If the day-of-month is invalid for the year, it will be changed to the last valid day of the month. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param year the year to set in the result, from MIN_YEAR to MAX_YEAR + * @return an {@code OffsetDate} based on this date with the requested year, not null + * @throws DateTimeException if the year value is invalid + */ + public OffsetDate withYear(int year) { + return with(date.withYear(year), offset); + } + + /** + * Returns a copy of this {@code OffsetDate} with the month-of-year altered. + * The offset does not affect the calculation and will be the same in the result. + * If the day-of-month is invalid for the year, it will be changed to the last valid day of the month. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param month the month-of-year to set in the result, from 1 (January) to 12 (December) + * @return an {@code OffsetDate} based on this date with the requested month, not null + * @throws DateTimeException if the month-of-year value is invalid + */ + public OffsetDate withMonth(int month) { + return with(date.withMonth(month), offset); + } + + /** + * Returns a copy of this {@code OffsetDate} with the day-of-month altered. + * If the resulting date is invalid, an exception is thrown. + * The offset does not affect the calculation and will be the same in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param dayOfMonth the day-of-month to set in the result, from 1 to 28-31 + * @return an {@code OffsetDate} based on this date with the requested day, not null + * @throws DateTimeException if the day-of-month value is invalid + * @throws DateTimeException if the day-of-month is invalid for the month-year + */ + public OffsetDate withDayOfMonth(int dayOfMonth) { + return with(date.withDayOfMonth(dayOfMonth), offset); + } + + /** + * Returns a copy of this {@code OffsetDate} with the day-of-year altered. + * If the resulting date is invalid, an exception is thrown. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param dayOfYear the day-of-year to set in the result, from 1 to 365-366 + * @return an {@code OffsetDate} based on this date with the requested day, not null + * @throws DateTimeException if the day-of-year value is invalid + * @throws DateTimeException if the day-of-year is invalid for the year + */ + public OffsetDate withDayOfYear(int dayOfYear) { + return with(date.withDayOfYear(dayOfYear), offset); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date with the specified period added. + *

+ * This method returns a new date based on this date with the specified period added. + * The adder is typically {@link java.time.Period} but may be any other type implementing + * the {@link TemporalAdder} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #plus(long, TemporalUnit)}. + * The offset is not part of the calculation and will be unchanged in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adder the adder to use, not null + * @return an {@code OffsetDate} based on this date with the addition made, not null + * @throws DateTimeException if the addition cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public OffsetDate plus(TemporalAdder adder) { + return (OffsetDate) adder.addTo(this); + } + + /** + * Returns a copy of this date with the specified period added. + *

+ * This method returns a new date based on this date with the specified period added. + * This can be used to add any period that is defined by a unit, for example to add years, months or days. + * The unit is responsible for the details of the calculation, including the resolution + * of any edge cases in the calculation. + * The offset is not part of the calculation and will be unchanged in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToAdd the amount of the unit to add to the result, may be negative + * @param unit the unit of the period to add, not null + * @return an {@code OffsetDate} based on this date with the specified period added, not null + * @throws DateTimeException if the unit cannot be added to this type + */ + @Override + public OffsetDate plus(long amountToAdd, TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + return with(date.plus(amountToAdd, unit), offset); + } + return unit.doPlus(this, amountToAdd); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code OffsetDate} with the specified period in years added. + *

+ * This method adds the specified amount to the years field in three steps: + *

    + *
  1. Add the input years to the year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2008-02-29 (leap year) plus one year would result in the + * invalid date 2009-02-29 (standard year). Instead of returning an invalid + * result, the last valid day of the month, 2009-02-28, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param years the years to add, may be negative + * @return an {@code OffsetDate} based on this date with the years added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDate plusYears(long years) { + return with(date.plusYears(years), offset); + } + + /** + * Returns a copy of this {@code OffsetDate} with the specified period in months added. + *

+ * This method adds the specified amount to the months field in three steps: + *

    + *
  1. Add the input months to the month-of-year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2007-03-31 plus one month would result in the invalid date + * 2007-04-31. Instead of returning an invalid result, the last valid day + * of the month, 2007-04-30, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param months the months to add, may be negative + * @return an {@code OffsetDate} based on this date with the months added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDate plusMonths(long months) { + return with(date.plusMonths(months), offset); + } + + /** + * Returns a copy of this {@code OffsetDate} with the specified period in weeks added. + *

+ * This method adds the specified amount in weeks 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. + *

+ * For example, 2008-12-31 plus one week would result in 2009-01-07. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param weeks the weeks to add, may be negative + * @return an {@code OffsetDate} based on this date with the weeks added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDate plusWeeks(long weeks) { + return with(date.plusWeeks(weeks), offset); + } + + /** + * Returns a copy of this {@code OffsetDate} with the specified period in days added. + *

+ * 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. + *

+ * For example, 2008-12-31 plus one day would result in 2009-01-01. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param days the days to add, may be negative + * @return an {@code OffsetDate} based on this date with the days added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDate plusDays(long days) { + return with(date.plusDays(days), offset); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date with the specified period subtracted. + *

+ * This method returns a new date based on this date with the specified period subtracted. + * The subtractor is typically {@link java.time.Period} but may be any other type implementing + * the {@link TemporalSubtractor} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #minus(long, TemporalUnit)}. + * The offset is not part of the calculation and will be unchanged in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param subtractor the subtractor to use, not null + * @return an {@code OffsetDate} based on this date with the subtraction made, not null + * @throws DateTimeException if the subtraction cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public OffsetDate minus(TemporalSubtractor subtractor) { + return (OffsetDate) subtractor.subtractFrom(this); + } + + /** + * Returns a copy of this date with the specified period subtracted. + *

+ * This method returns a new date based on this date with the specified period subtracted. + * This can be used to subtract any period that is defined by a unit, for example to subtract years, months or days. + * The unit is responsible for the details of the calculation, including the resolution + * of any edge cases in the calculation. + * The offset is not part of the calculation and will be unchanged in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToSubtract the amount of the unit to subtract from the result, may be negative + * @param unit the unit of the period to subtract, not null + * @return an {@code OffsetDate} based on this date with the specified period subtracted, not null + * @throws DateTimeException if the unit cannot be added to this type + */ + @Override + public OffsetDate minus(long amountToSubtract, TemporalUnit unit) { + return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code OffsetDate} with the specified period in years subtracted. + *

+ * This method subtracts the specified amount from the years field in three steps: + *

    + *
  1. Subtract the input years to the year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2008-02-29 (leap year) minus one year would result in the + * invalid date 2007-02-29 (standard year). Instead of returning an invalid + * result, the last valid day of the month, 2007-02-28, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param years the years to subtract, may be negative + * @return an {@code OffsetDate} based on this date with the years subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDate minusYears(long years) { + return with(date.minusYears(years), offset); + } + + /** + * Returns a copy of this {@code OffsetDate} with the specified period in months subtracted. + *

+ * This method subtracts the specified amount from the months field in three steps: + *

    + *
  1. Subtract the input months to the month-of-year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2007-03-31 minus one month would result in the invalid date + * 2007-02-31. Instead of returning an invalid result, the last valid day + * of the month, 2007-02-28, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param months the months to subtract, may be negative + * @return an {@code OffsetDate} based on this date with the months subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDate minusMonths(long months) { + return with(date.minusMonths(months), offset); + } + + /** + * Returns a copy of this {@code OffsetDate} with the specified period in weeks subtracted. + *

+ * This method subtracts the specified amount in weeks 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. + *

+ * For example, 2009-01-07 minus one week would result in 2008-12-31. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param weeks the weeks to subtract, may be negative + * @return an {@code OffsetDate} based on this date with the weeks subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDate minusWeeks(long weeks) { + return with(date.minusWeeks(weeks), offset); + } + + /** + * Returns a copy of this {@code OffsetDate} with the specified number of days subtracted. + *

+ * 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. + *

+ * For example, 2009-01-01 minus one day would result in 2008-12-31. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param days the days to subtract, may be negative + * @return an {@code OffsetDate} based on this date with the days subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDate minusDays(long days) { + return with(date.minusDays(days), offset); + } + + //----------------------------------------------------------------------- + /** + * Queries this date using the specified query. + *

+ * This queries this date using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.chrono()) { + return (R) ISOChrono.INSTANCE; + } else if (query == Queries.precision()) { + return (R) DAYS; + } else if (query == Queries.offset() || query == Queries.zone()) { + return (R) getOffset(); + } + return Temporal.super.query(query); + } + + /** + * Adjusts the specified temporal object to have the same offset and date + * as this object. + *

+ * This returns a temporal object of the same observable type as the input + * with the offset and date changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * twice, passing {@link ChronoField#OFFSET_SECONDS} and + * {@link ChronoField#EPOCH_DAY} as the fields. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisOffsetDate.adjustInto(temporal);
+     *   temporal = temporal.with(thisOffsetDate);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal adjustInto(Temporal temporal) { + return temporal + .with(OFFSET_SECONDS, getOffset().getTotalSeconds()) + .with(EPOCH_DAY, getDate().toEpochDay()); + } + + /** + * Calculates the period between this date and another date in + * terms of the specified unit. + *

+ * This calculates the period between two dates in terms of a single unit. + * The start and end points are {@code this} and the specified date. + * The result will be negative if the end is before the start. + * For example, the period in days between two dates can be calculated + * using {@code startDate.periodUntil(endDate, DAYS)}. + *

+ * The {@code Temporal} passed to this method must be an {@code OffsetDate}. + * If the offset differs between the two times, then the specified + * end time is normalized to have the same offset as this time. + *

+ * The calculation returns a whole number, representing the number of + * complete units between the two dates. + * For example, the period in months between 2012-06-15Z and 2012-08-14Z + * will only be one month as it is one day short of two months. + *

+ * This method operates in association with {@link TemporalUnit#between}. + * The result of this method is a {@code long} representing the amount of + * the specified unit. By contrast, the result of {@code between} is an + * object that can be used directly in addition/subtraction: + *

+     *   long period = start.periodUntil(end, MONTHS);   // this method
+     *   dateTime.plus(MONTHS.between(start, end));      // use in plus/minus
+     * 
+ *

+ * The calculation is implemented in this method for {@link ChronoUnit}. + * The units {@code DAYS}, {@code WEEKS}, {@code MONTHS}, {@code YEARS}, + * {@code DECADES}, {@code CENTURIES}, {@code MILLENNIA} and {@code ERAS} + * are supported. Other {@code ChronoUnit} values will throw an exception. + *

+ * If the unit is not a {@code ChronoUnit}, then the result of this method + * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)} + * passing {@code this} as the first argument and the input temporal as + * the second argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param endDate the end date, which must be an {@code OffsetDate}, not null + * @param unit the unit to measure the period in, not null + * @return the amount of the period between this date and the end date + * @throws DateTimeException if the period cannot be calculated + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long periodUntil(Temporal endDate, TemporalUnit unit) { + if (endDate instanceof OffsetDate == false) { + Objects.requireNonNull(endDate, "endDate"); + throw new DateTimeException("Unable to calculate period between objects of two different types"); + } + if (unit instanceof ChronoUnit) { + OffsetDate end = (OffsetDate) endDate; + long offsetDiff = end.offset.getTotalSeconds() - offset.getTotalSeconds(); + LocalDate endLocal = end.date.plusDays(Math.floorDiv(-offsetDiff, SECONDS_PER_DAY)); + return date.periodUntil(endLocal, unit); + } + return unit.between(this, endDate).getAmount(); + } + + //----------------------------------------------------------------------- + /** + * Returns an offset date-time formed from this date at the specified time. + *

+ * This combines this date with the specified time to form an {@code OffsetDateTime}. + * All possible combinations of date and time are valid. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param time the time to combine with, not null + * @return the offset date-time formed from this date and the specified time, not null + */ + public OffsetDateTime atTime(LocalTime time) { + return OffsetDateTime.of(date, time, offset); + } + + //----------------------------------------------------------------------- + /** + * Converts this date to midnight at the start of day in epoch seconds. + * + * @return the epoch seconds value + */ + private long toEpochSecond() { + long epochDay = date.toEpochDay(); + long secs = epochDay * SECONDS_PER_DAY; + return secs - offset.getTotalSeconds(); + } + + //----------------------------------------------------------------------- + /** + * Compares this {@code OffsetDate} to another date. + *

+ * The comparison is based first on the UTC equivalent instant, then on the local date. + * It is "consistent with equals", as defined by {@link Comparable}. + *

+ * For example, the following is the comparator order: + *

    + *
  1. 2008-06-29-11:00
  2. + *
  3. 2008-06-29-12:00
  4. + *
  5. 2008-06-30+12:00
  6. + *
  7. 2008-06-29-13:00
  8. + *
+ * Values #2 and #3 represent the same instant on the time-line. + * When two values represent the same instant, the local date is compared + * to distinguish them. This step is needed to make the ordering + * consistent with {@code equals()}. + *

+ * To compare the underlying local date of two {@code TemporalAccessor} instances, + * use {@link ChronoField#EPOCH_DAY} as a comparator. + * + * @param other the other date to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + @Override + public int compareTo(OffsetDate other) { + if (offset.equals(other.offset)) { + return date.compareTo(other.date); + } + int compare = Long.compare(toEpochSecond(), other.toEpochSecond()); + if (compare == 0) { + compare = date.compareTo(other.date); + } + return compare; + } + + //----------------------------------------------------------------------- + /** + * Checks if the instant of midnight at the start of this {@code OffsetDate} + * is after midnight at the start of the specified date. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the instant of the date. This is equivalent to using + * {@code date1.toEpochSecond().isAfter(date2.toEpochSecond())}. + * + * @param other the other date to compare to, not null + * @return true if this is after the instant of the specified date + */ + public boolean isAfter(OffsetDate other) { + return toEpochSecond() > other.toEpochSecond(); + } + + /** + * Checks if the instant of midnight at the start of this {@code OffsetDate} + * is before midnight at the start of the specified date. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the instant of the date. This is equivalent to using + * {@code date1.toEpochSecond().isBefore(date2.toEpochSecond())}. + * + * @param other the other date to compare to, not null + * @return true if this is before the instant of the specified date + */ + public boolean isBefore(OffsetDate other) { + return toEpochSecond() < other.toEpochSecond(); + } + + /** + * Checks if the instant of midnight at the start of this {@code OffsetDate} + * equals midnight at the start of the specified date. + *

+ * This method differs from the comparison in {@link #compareTo} and {@link #equals} + * in that it only compares the instant of the date. This is equivalent to using + * {@code date1.toEpochSecond().equals(date2.toEpochSecond())}. + * + * @param other the other date to compare to, not null + * @return true if the instant equals the instant of the specified date + */ + public boolean isEqual(OffsetDate other) { + return toEpochSecond() == other.toEpochSecond(); + } + + //----------------------------------------------------------------------- + /** + * Checks if this date is equal to another date. + *

+ * The comparison is based on the local-date and the offset. + * To compare for the same instant on the time-line, use {@link #isEqual(OffsetDate)}. + *

+ * Only objects of type {@code OffsetDate} are compared, other types return false. + * To compare the underlying local date of two {@code TemporalAccessor} instances, + * 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 OffsetDate) { + OffsetDate other = (OffsetDate) obj; + return date.equals(other.date) && offset.equals(other.offset); + } + return false; + } + + /** + * A hash code for this date. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return date.hashCode() ^ offset.hashCode(); + } + + //----------------------------------------------------------------------- + /** + * Outputs this date as a {@code String}, such as {@code 2007-12-03+01:00}. + *

+ * The output will be in the ISO-8601 format {@code yyyy-MM-ddXXXXX}. + * + * @return a string representation of this date, not null + */ + @Override + public String toString() { + return date.toString() + offset.toString(); + } + + /** + * Outputs this date as a {@code String} using the formatter. + *

+ * This date will be passed to the formatter + * {@link DateTimeFormatter#print(TemporalAccessor) print method}. + * + * @param formatter the formatter to use, not null + * @return the formatted date string, not null + * @throws DateTimeException if an error occurs during printing + */ + public String toString(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.print(this); + } + + //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(1);  // identifies this as a OffsetDateTime
+     *  out.writeObject(date);
+     *  out.writeObject(offset);
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.OFFSET_DATE_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(ObjectOutput out) throws IOException { + out.writeObject(date); + out.writeObject(offset); + } + + static OffsetDate readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + LocalDate date = (LocalDate) in.readObject(); + ZoneOffset offset = (ZoneOffset) in.readObject(); + return OffsetDate.of(date, offset); + } + +} diff --git a/src/share/classes/java/time/temporal/OffsetDateTime.java b/src/share/classes/java/time/temporal/OffsetDateTime.java new file mode 100644 index 0000000000000000000000000000000000000000..c7c2295a7488cb316cead12bca75455d2b8964dd --- /dev/null +++ b/src/share/classes/java/time/temporal/OffsetDateTime.java @@ -0,0 +1,1824 @@ +/* + * 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 java.time.temporal; + +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.INSTANT_SECONDS; +import static java.time.temporal.ChronoField.NANO_OF_DAY; +import static java.time.temporal.ChronoField.OFFSET_SECONDS; +import static java.time.temporal.ChronoUnit.NANOS; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.Clock; +import java.time.DateTimeException; +import java.time.DayOfWeek; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.zone.ZoneRules; +import java.util.Comparator; +import java.util.Objects; + +/** + * A date-time with an offset from UTC/Greenwich in the ISO-8601 calendar system, + * such as {@code 2007-12-03T10:15:30+01:00}. + *

+ * {@code OffsetDateTime} is an immutable representation of a date-time with an offset. + * This class stores all date and time fields, to a precision of nanoseconds, + * as well as the offset from UTC/Greenwich. For example, the value + * "2nd October 2007 at 13:45.30.123456789 +02:00" can be stored in an {@code OffsetDateTime}. + *

+ * {@code OffsetDateTime}, {@link java.time.ZonedDateTime} and {@link java.time.Instant} all store an instant + * on the time-line to nanosecond precision. + * {@code Instant} is the simplest, simply representing the instant. + * {@code OffsetDateTime} adds to the instant the offset from UTC/Greenwich, which allows + * the local date-time to be obtained. + * {@code ZonedDateTime} adds full time-zone rules. + *

+ * It is intended that {@code ZonedDateTime} or {@code Instant} is used to model data + * in simpler applications. This class may be used when modeling date-time concepts in + * more detail, or when communicating to a database or in a network protocol. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class OffsetDateTime + implements Temporal, TemporalAdjuster, Comparable, Serializable { + + /** + * The minimum supported {@code OffsetDateTime}, '-999999999-01-01T00:00:00+18:00'. + * This is the local date-time of midnight at the start of the minimum date + * in the maximum offset (larger offsets are earlier on the time-line). + * This combines {@link LocalDateTime#MIN} and {@link ZoneOffset#MAX}. + * This could be used by an application as a "far past" date-time. + */ + public static final OffsetDateTime MIN = LocalDateTime.MIN.atOffset(ZoneOffset.MAX); + /** + * The maximum supported {@code OffsetDateTime}, '+999999999-12-31T23:59:59.999999999-18:00'. + * This is the local date-time just before midnight at the end of the maximum date + * in the minimum offset (larger negative offsets are later on the time-line). + * This combines {@link LocalDateTime#MAX} and {@link ZoneOffset#MIN}. + * This could be used by an application as a "far future" date-time. + */ + public static final OffsetDateTime MAX = LocalDateTime.MAX.atOffset(ZoneOffset.MIN); + + /** + * Comparator for two {@code OffsetDateTime} instances based solely on the instant. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the underlying instant. + * + * @see #isAfter + * @see #isBefore + * @see #isEqual + */ + public static final Comparator INSTANT_COMPARATOR = new Comparator() { + @Override + public int compare(OffsetDateTime datetime1, OffsetDateTime datetime2) { + int cmp = Long.compare(datetime1.toEpochSecond(), datetime2.toEpochSecond()); + if (cmp == 0) { + cmp = Long.compare(datetime1.getTime().toNanoOfDay(), datetime2.getTime().toNanoOfDay()); + } + return cmp; + } + }; + + /** + * Serialization version. + */ + private static final long serialVersionUID = 2287754244819255394L; + + /** + * The local date-time. + */ + private final LocalDateTime dateTime; + /** + * The offset from UTC/Greenwich. + */ + private final ZoneOffset offset; + + //----------------------------------------------------------------------- + /** + * Obtains the current date-time from the system clock in the default time-zone. + *

+ * This will query the {@link java.time.Clock#systemDefaultZone() system clock} in the default + * time-zone to obtain the current date-time. + * The offset will be calculated from the time-zone in the clock. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @return the current date-time using the system clock, not null + */ + public static OffsetDateTime now() { + return now(Clock.systemDefaultZone()); + } + + /** + * Obtains the current date-time from the system clock in the specified time-zone. + *

+ * This will query the {@link Clock#system(java.time.ZoneId) system clock} to obtain the current date-time. + * Specifying the time-zone avoids dependence on the default time-zone. + * The offset will be calculated from the specified time-zone. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @param zone the zone ID to use, not null + * @return the current date-time using the system clock, not null + */ + public static OffsetDateTime now(ZoneId zone) { + return now(Clock.system(zone)); + } + + /** + * Obtains the current date-time from the specified clock. + *

+ * This will query the specified clock to obtain the current date-time. + * The offset will be calculated from the time-zone in the clock. + *

+ * Using this method allows the use of an alternate clock for testing. + * The alternate clock may be introduced using {@link Clock dependency injection}. + * + * @param clock the clock to use, not null + * @return the current date-time, not null + */ + public static OffsetDateTime now(Clock clock) { + Objects.requireNonNull(clock, "clock"); + final Instant now = clock.instant(); // called once + return ofInstant(now, clock.getZone().getRules().getOffset(now)); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code OffsetDateTime} from a date, time and offset. + *

+ * This creates an offset date-time with the specified local date, time and offset. + * + * @param date the local date, not null + * @param time the local time, not null + * @param offset the zone offset, not null + * @return the offset date-time, not null + */ + public static OffsetDateTime of(LocalDate date, LocalTime time, ZoneOffset offset) { + LocalDateTime dt = LocalDateTime.of(date, time); + return new OffsetDateTime(dt, offset); + } + + /** + * Obtains an instance of {@code OffsetDateTime} from a date-time and offset. + *

+ * This creates an offset date-time with the specified local date-time and offset. + * + * @param dateTime the local date-time, not null + * @param offset the zone offset, not null + * @return the offset date-time, not null + */ + public static OffsetDateTime of(LocalDateTime dateTime, ZoneOffset offset) { + return new OffsetDateTime(dateTime, offset); + } + + /** + * Obtains an instance of {@code OffsetDateTime} from a {@code ZonedDateTime}. + *

+ * This creates an offset date-time with the same local date-time and offset as + * the zoned date-time. The result will have the same instant as the input. + * + * @param zonedDateTime the zoned date-time to convert from, not null + * @return the offset date-time, not null + * @throws DateTimeException if the result exceeds the supported range + */ + public static OffsetDateTime of(ZonedDateTime zonedDateTime) { + Objects.requireNonNull(zonedDateTime, "zonedDateTime"); + return new OffsetDateTime(zonedDateTime.getDateTime(), zonedDateTime.getOffset()); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code OffsetDateTime} from an {@code Instant} and zone ID. + *

+ * This creates an offset date-time with the same instant as that specified. + * Finding the offset from UTC/Greenwich is simple as there is only one valid + * offset for each instant. + * + * @param instant the instant to create the date-time from, not null + * @param zone the time-zone, which may be an offset, not null + * @return the offset date-time, not null + * @throws DateTimeException if the result exceeds the supported range + */ + public static OffsetDateTime ofInstant(Instant instant, ZoneId zone) { + Objects.requireNonNull(instant, "instant"); + Objects.requireNonNull(zone, "zone"); + ZoneRules rules = zone.getRules(); + ZoneOffset offset = rules.getOffset(instant); + LocalDateTime ldt = LocalDateTime.ofEpochSecond(instant.getEpochSecond(), instant.getNano(), offset); + return new OffsetDateTime(ldt, offset); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code OffsetDateTime} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code OffsetDateTime}. + *

+ * The conversion extracts and combines {@code LocalDateTime} and {@code ZoneOffset}. + * If that fails it will try to extract and combine {@code Instant} and {@code ZoneOffset}. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used in queries via method reference, {@code OffsetDateTime::from}. + * + * @param temporal the temporal object to convert, not null + * @return the offset date-time, not null + * @throws DateTimeException if unable to convert to an {@code OffsetDateTime} + */ + public static OffsetDateTime from(TemporalAccessor temporal) { + if (temporal instanceof OffsetDateTime) { + return (OffsetDateTime) temporal; + } + ZoneOffset offset = ZoneOffset.from(temporal); + try { + try { + LocalDateTime ldt = LocalDateTime.from(temporal); + return OffsetDateTime.of(ldt, offset); + } catch (DateTimeException ignore) { + Instant instant = Instant.from(temporal); + return OffsetDateTime.ofInstant(instant, offset); + } + } catch (DateTimeException ex) { + throw new DateTimeException("Unable to obtain OffsetDateTime from TemporalAccessor: " + temporal.getClass(), ex); + } + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code OffsetDateTime} from a text string + * such as {@code 2007-12-03T10:15:30+01:00}. + *

+ * The string must represent a valid date-time and is parsed using + * {@link java.time.format.DateTimeFormatters#isoOffsetDateTime()}. + * + * @param text the text to parse such as "2007-12-03T10:15:30+01:00", not null + * @return the parsed offset date-time, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static OffsetDateTime parse(CharSequence text) { + return parse(text, DateTimeFormatters.isoOffsetDateTime()); + } + + /** + * Obtains an instance of {@code OffsetDateTime} from a text string using a specific formatter. + *

+ * The text is parsed using the formatter, returning a date-time. + * + * @param text the text to parse, not null + * @param formatter the formatter to use, not null + * @return the parsed offset date-time, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static OffsetDateTime parse(CharSequence text, DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.parse(text, OffsetDateTime::from); + } + + //----------------------------------------------------------------------- + /** + * Constructor. + * + * @param dateTime the local date-time, not null + * @param offset the zone offset, not null + */ + private OffsetDateTime(LocalDateTime dateTime, ZoneOffset offset) { + this.dateTime = Objects.requireNonNull(dateTime, "dateTime"); + this.offset = Objects.requireNonNull(offset, "offset"); + } + + /** + * Returns a new date-time based on this one, returning {@code this} where possible. + * + * @param dateTime the date-time to create with, not null + * @param offset the zone offset to create with, not null + */ + private OffsetDateTime with(LocalDateTime dateTime, ZoneOffset offset) { + if (this.dateTime == dateTime && this.offset.equals(offset)) { + return this; + } + return new OffsetDateTime(dateTime, offset); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this date-time can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and + * {@link #get(TemporalField) get} methods will throw an exception. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The supported fields are: + *

    + *
  • {@code NANO_OF_SECOND} + *
  • {@code NANO_OF_DAY} + *
  • {@code MICRO_OF_SECOND} + *
  • {@code MICRO_OF_DAY} + *
  • {@code MILLI_OF_SECOND} + *
  • {@code MILLI_OF_DAY} + *
  • {@code SECOND_OF_MINUTE} + *
  • {@code SECOND_OF_DAY} + *
  • {@code MINUTE_OF_HOUR} + *
  • {@code MINUTE_OF_DAY} + *
  • {@code HOUR_OF_AMPM} + *
  • {@code CLOCK_HOUR_OF_AMPM} + *
  • {@code HOUR_OF_DAY} + *
  • {@code CLOCK_HOUR_OF_DAY} + *
  • {@code AMPM_OF_DAY} + *
  • {@code DAY_OF_WEEK} + *
  • {@code ALIGNED_DAY_OF_WEEK_IN_MONTH} + *
  • {@code ALIGNED_DAY_OF_WEEK_IN_YEAR} + *
  • {@code DAY_OF_MONTH} + *
  • {@code DAY_OF_YEAR} + *
  • {@code EPOCH_DAY} + *
  • {@code ALIGNED_WEEK_OF_MONTH} + *
  • {@code ALIGNED_WEEK_OF_YEAR} + *
  • {@code MONTH_OF_YEAR} + *
  • {@code EPOCH_MONTH} + *
  • {@code YEAR_OF_ERA} + *
  • {@code YEAR} + *
  • {@code ERA} + *
  • {@code INSTANT_SECONDS} + *
  • {@code OFFSET_SECONDS} + *
+ * All other {@code ChronoField} instances will return false. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the field is supported is determined by the field. + * + * @param field the field to check, null returns false + * @return true if the field is supported on this date-time, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + return field instanceof ChronoField || (field != null && field.doIsSupported(this)); + } + + /** + * Gets the range of valid values for the specified field. + *

+ * The range object expresses the minimum and maximum valid values for a field. + * This date-time is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return + * appropriate range instances. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + @Override + public ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + if (field == INSTANT_SECONDS || field == OFFSET_SECONDS) { + return field.range(); + } + return dateTime.range(field); + } + return field.doRange(this); + } + + /** + * Gets the value of the specified field from this date-time as an {@code int}. + *

+ * This queries this date-time for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date-time, except {@code NANO_OF_DAY}, {@code MICRO_OF_DAY}, + * {@code EPOCH_DAY}, {@code EPOCH_MONTH} and {@code INSTANT_SECONDS} which are too + * large to fit in an {@code int} and throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public int get(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case INSTANT_SECONDS: throw new DateTimeException("Field too large for an int: " + field); + case OFFSET_SECONDS: return getOffset().getTotalSeconds(); + } + return dateTime.get(field); + } + return Temporal.super.get(field); + } + + /** + * Gets the value of the specified field from this date-time as a {@code long}. + *

+ * This queries this date-time for the value for the specified field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date-time. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case INSTANT_SECONDS: return toEpochSecond(); + case OFFSET_SECONDS: return getOffset().getTotalSeconds(); + } + return dateTime.getLong(field); + } + return field.doGet(this); + } + + //----------------------------------------------------------------------- + /** + * Gets the zone offset, such as '+01:00'. + *

+ * This is the offset of the local date-time from UTC/Greenwich. + * + * @return the zone offset, not null + */ + public ZoneOffset getOffset() { + return offset; + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the specified offset ensuring + * that the result has the same local date-time. + *

+ * This method returns an object with the same {@code LocalDateTime} and the specified {@code ZoneOffset}. + * No calculation is needed or performed. + * For example, if this time represents {@code 2007-12-03T10:30+02:00} and the offset specified is + * {@code +03:00}, then this method will return {@code 2007-12-03T10:30+03:00}. + *

+ * To take into account the difference between the offsets, and adjust the time fields, + * use {@link #withOffsetSameInstant}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param offset the zone offset to change to, not null + * @return an {@code OffsetDateTime} based on this date-time with the requested offset, not null + */ + public OffsetDateTime withOffsetSameLocal(ZoneOffset offset) { + return with(dateTime, offset); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the specified offset ensuring + * that the result is at the same instant. + *

+ * This method returns an object with the specified {@code ZoneOffset} and a {@code LocalDateTime} + * adjusted by the difference between the two offsets. + * This will result in the old and new objects representing the same instant. + * This is useful for finding the local time in a different offset. + * For example, if this time represents {@code 2007-12-03T10:30+02:00} and the offset specified is + * {@code +03:00}, then this method will return {@code 2007-12-03T11:30+03:00}. + *

+ * To change the offset without adjusting the local time use {@link #withOffsetSameLocal}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param offset the zone offset to change to, not null + * @return an {@code OffsetDateTime} based on this date-time with the requested offset, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime withOffsetSameInstant(ZoneOffset offset) { + if (offset.equals(this.offset)) { + return this; + } + int difference = offset.getTotalSeconds() - this.offset.getTotalSeconds(); + LocalDateTime adjusted = dateTime.plusSeconds(difference); + return new OffsetDateTime(adjusted, offset); + } + + //----------------------------------------------------------------------- + /** + * Gets the {@code LocalDateTime} part of this offset date-time. + *

+ * This returns a {@code LocalDateTime} with the same year, month, day and time + * as this date-time. + * + * @return the local date-time part of this date-time, not null + */ + public LocalDateTime getDateTime() { + return dateTime; + } + + //----------------------------------------------------------------------- + /** + * Gets the {@code LocalDate} part of this date-time. + *

+ * 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 dateTime.getDate(); + } + + /** + * Gets the year field. + *

+ * This method returns the primitive {@code int} value for the year. + *

+ * 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 dateTime.getYear(); + } + + /** + * Gets the month-of-year field from 1 to 12. + *

+ * This method returns the month as an {@code int} from 1 to 12. + * Application code is frequently clearer if the enum {@link Month} + * is used by calling {@link #getMonth()}. + * + * @return the month-of-year, from 1 to 12 + * @see #getMonth() + */ + public int getMonthValue() { + return dateTime.getMonthValue(); + } + + /** + * Gets the month-of-year field using the {@code Month} enum. + *

+ * This method returns the enum {@link Month} for the month. + * This avoids confusion as to what {@code int} values mean. + * If you need access to the primitive {@code int} value then the enum + * provides the {@link Month#getValue() int value}. + * + * @return the month-of-year, not null + * @see #getMonthValue() + */ + public Month getMonth() { + return dateTime.getMonth(); + } + + /** + * Gets the day-of-month field. + *

+ * 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 dateTime.getDayOfMonth(); + } + + /** + * Gets the day-of-year field. + *

+ * This method returns the primitive {@code int} value for the day-of-year. + * + * @return the day-of-year, from 1 to 365, or 366 in a leap year + */ + public int getDayOfYear() { + return dateTime.getDayOfYear(); + } + + /** + * Gets the day-of-week field, which is an enum {@code DayOfWeek}. + *

+ * This method returns the enum {@link java.time.DayOfWeek} for the day-of-week. + * This avoids confusion as to what {@code int} values mean. + * If you need access to the primitive {@code int} value then the enum + * provides the {@link java.time.DayOfWeek#getValue() int value}. + *

+ * Additional information can be obtained from the {@code DayOfWeek}. + * This includes textual names of the values. + * + * @return the day-of-week, not null + */ + public DayOfWeek getDayOfWeek() { + return dateTime.getDayOfWeek(); + } + + //----------------------------------------------------------------------- + /** + * Gets the {@code LocalTime} part of this date-time. + *

+ * 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 dateTime.getTime(); + } + + /** + * Gets the hour-of-day field. + * + * @return the hour-of-day, from 0 to 23 + */ + public int getHour() { + return dateTime.getHour(); + } + + /** + * Gets the minute-of-hour field. + * + * @return the minute-of-hour, from 0 to 59 + */ + public int getMinute() { + return dateTime.getMinute(); + } + + /** + * Gets the second-of-minute field. + * + * @return the second-of-minute, from 0 to 59 + */ + public int getSecond() { + return dateTime.getSecond(); + } + + /** + * Gets the nano-of-second field. + * + * @return the nano-of-second, from 0 to 999,999,999 + */ + public int getNano() { + return dateTime.getNano(); + } + + //----------------------------------------------------------------------- + /** + * Returns an adjusted copy of this date-time. + *

+ * This returns a new {@code OffsetDateTime}, based on this one, with the date-time adjusted. + * The adjustment takes place using the specified adjuster strategy object. + * Read the documentation of the adjuster to understand what adjustment will be made. + *

+ * A simple adjuster might simply set the one of the fields, such as the year field. + * A more complex adjuster might set the date to the last day of the month. + * A selection of common adjustments is provided in {@link java.time.temporal.Adjusters}. + * These include finding the "last day of the month" and "next Wednesday". + * Key date-time classes also implement the {@code TemporalAdjuster} interface, + * such as {@link Month} and {@link java.time.temporal.MonthDay MonthDay}. + * The adjuster is responsible for handling special cases, such as the varying + * lengths of month and leap years. + *

+ * For example this code returns a date on the last day of July: + *

+     *  import static java.time.Month.*;
+     *  import static java.time.temporal.Adjusters.*;
+     *
+     *  result = offsetDateTime.with(JULY).with(lastDayOfMonth());
+     * 
+ *

+ * The classes {@link LocalDate}, {@link LocalTime} and {@link ZoneOffset} implement + * {@code TemporalAdjuster}, thus this method can be used to change the date, time or offset: + *

+     *  result = offsetDateTime.with(date);
+     *  result = offsetDateTime.with(time);
+     *  result = offsetDateTime.with(offset);
+     * 
+ *

+ * The result of this method is obtained by invoking the + * {@link TemporalAdjuster#adjustInto(Temporal)} method on the + * specified adjuster passing {@code this} as the argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adjuster the adjuster to use, not null + * @return an {@code OffsetDateTime} based on {@code this} with the adjustment made, not null + * @throws DateTimeException if the adjustment cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public OffsetDateTime with(TemporalAdjuster adjuster) { + // optimizations + if (adjuster instanceof LocalDate || adjuster instanceof LocalTime || adjuster instanceof LocalDateTime) { + return with(dateTime.with(adjuster), offset); + } else if (adjuster instanceof Instant) { + return ofInstant((Instant) adjuster, offset); + } else if (adjuster instanceof ZoneOffset) { + return with(dateTime, (ZoneOffset) adjuster); + } else if (adjuster instanceof OffsetDateTime) { + return (OffsetDateTime) adjuster; + } + return (OffsetDateTime) adjuster.adjustInto(this); + } + + /** + * Returns a copy of this date-time with the specified field set to a new value. + *

+ * This returns a new {@code OffsetDateTime}, based on this one, with the value + * for the specified field changed. + * This can be used to change any supported field, such as the year, month or day-of-month. + * If it is not possible to set the value, because the field is not supported or for + * some other reason, an exception is thrown. + *

+ * In some cases, changing the specified field can cause the resulting date-time to become invalid, + * such as changing the month from 31st January to February would make the day-of-month invalid. + * In cases like this, the field is responsible for resolving the date. Typically it will choose + * the previous valid date, which would be the last valid day of February in this example. + *

+ * If the field is a {@link ChronoField} then the adjustment is implemented here. + *

+ * The {@code INSTANT_SECONDS} field will return a date-time with the specified instant. + * The offset and nano-of-second are unchanged. + * If the new instant value is outside the valid range then a {@code DateTimeException} will be thrown. + *

+ * The {@code OFFSET_SECONDS} field will return a date-time with the specified offset. + * The local date-time is unaltered. If the new offset value is outside the valid range + * then a {@code DateTimeException} will be thrown. + *

+ * The other {@link #isSupported(TemporalField) supported fields} will behave as per + * the matching method on {@link LocalDateTime#with(TemporalField, long) LocalDateTime}. + * In this case, the offset is not part of the calculation and will be unchanged. + *

+ * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doWith(Temporal, long)} + * passing {@code this} as the argument. In this case, the field determines + * whether and how to adjust the instant. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param field the field to set in the result, not null + * @param newValue the new value of the field in the result + * @return an {@code OffsetDateTime} based on {@code this} with the specified field set, not null + * @throws DateTimeException if the field cannot be set + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public OffsetDateTime with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + switch (f) { + case INSTANT_SECONDS: return ofInstant(Instant.ofEpochSecond(newValue, getNano()), offset); + case OFFSET_SECONDS: { + return with(dateTime, ZoneOffset.ofTotalSeconds(f.checkValidIntValue(newValue))); + } + } + return with(dateTime.with(field, newValue), offset); + } + return field.doWith(this, newValue); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code OffsetDateTime} with the year altered. + * The offset does not affect the calculation and will be the same in the result. + * If the day-of-month is invalid for the year, it will be changed to the last valid day of the month. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param year the year to set in the result, from MIN_YEAR to MAX_YEAR + * @return an {@code OffsetDateTime} based on this date-time with the requested year, not null + * @throws DateTimeException if the year value is invalid + */ + public OffsetDateTime withYear(int year) { + return with(dateTime.withYear(year), offset); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the month-of-year altered. + * The offset does not affect the calculation and will be the same in the result. + * If the day-of-month is invalid for the year, it will be changed to the last valid day of the month. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param month the month-of-year to set in the result, from 1 (January) to 12 (December) + * @return an {@code OffsetDateTime} based on this date-time with the requested month, not null + * @throws DateTimeException if the month-of-year value is invalid + */ + public OffsetDateTime withMonth(int month) { + return with(dateTime.withMonth(month), offset); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the day-of-month altered. + * If the resulting {@code OffsetDateTime} is invalid, an exception is thrown. + * The offset does not affect the calculation and will be the same in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param dayOfMonth the day-of-month to set in the result, from 1 to 28-31 + * @return an {@code OffsetDateTime} based on this date-time with the requested day, not null + * @throws DateTimeException if the day-of-month value is invalid + * @throws DateTimeException if the day-of-month is invalid for the month-year + */ + public OffsetDateTime withDayOfMonth(int dayOfMonth) { + return with(dateTime.withDayOfMonth(dayOfMonth), offset); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the day-of-year altered. + * If the resulting {@code OffsetDateTime} is invalid, an exception is thrown. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param dayOfYear the day-of-year to set in the result, from 1 to 365-366 + * @return an {@code OffsetDateTime} based on this date with the requested day, not null + * @throws DateTimeException if the day-of-year value is invalid + * @throws DateTimeException if the day-of-year is invalid for the year + */ + public OffsetDateTime withDayOfYear(int dayOfYear) { + return with(dateTime.withDayOfYear(dayOfYear), offset); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code OffsetDateTime} with the hour-of-day value altered. + *

+ * The offset does not affect the calculation and will be the same in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hour the hour-of-day to set in the result, from 0 to 23 + * @return an {@code OffsetDateTime} based on this date-time with the requested hour, not null + * @throws DateTimeException if the hour value is invalid + */ + public OffsetDateTime withHour(int hour) { + return with(dateTime.withHour(hour), offset); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the minute-of-hour value altered. + *

+ * The offset does not affect the calculation and will be the same in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param minute the minute-of-hour to set in the result, from 0 to 59 + * @return an {@code OffsetDateTime} based on this date-time with the requested minute, not null + * @throws DateTimeException if the minute value is invalid + */ + public OffsetDateTime withMinute(int minute) { + return with(dateTime.withMinute(minute), offset); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the second-of-minute value altered. + *

+ * The offset does not affect the calculation and will be the same in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param second the second-of-minute to set in the result, from 0 to 59 + * @return an {@code OffsetDateTime} based on this date-time with the requested second, not null + * @throws DateTimeException if the second value is invalid + */ + public OffsetDateTime withSecond(int second) { + return with(dateTime.withSecond(second), offset); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the nano-of-second value altered. + *

+ * The offset does not affect the calculation and will be the same in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanoOfSecond the nano-of-second to set in the result, from 0 to 999,999,999 + * @return an {@code OffsetDateTime} based on this date-time with the requested nanosecond, not null + * @throws DateTimeException if the nanos value is invalid + */ + public OffsetDateTime withNano(int nanoOfSecond) { + return with(dateTime.withNano(nanoOfSecond), offset); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code OffsetDateTime} with the time truncated. + *

+ * Truncation returns a copy of the original date-time with fields + * smaller than the specified unit set to zero. + * For example, truncating with the {@link ChronoUnit#MINUTES minutes} unit + * will set the second-of-minute and nano-of-second field to zero. + *

+ * Not all units are accepted. The {@link ChronoUnit#DAYS days} unit and time + * units with an exact duration can be used, other units throw an exception. + *

+ * The offset does not affect the calculation and will be the same in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param unit the unit to truncate to, not null + * @return an {@code OffsetDateTime} based on this date-time with the time truncated, not null + * @throws DateTimeException if unable to truncate + */ + public OffsetDateTime truncatedTo(TemporalUnit unit) { + return with(dateTime.truncatedTo(unit), offset); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date-time with the specified period added. + *

+ * This method returns a new date-time based on this time with the specified period added. + * The adder is typically {@link java.time.Period} but may be any other type implementing + * the {@link TemporalAdder} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #plus(long, TemporalUnit)}. + * The offset is not part of the calculation and will be unchanged in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adder the adder to use, not null + * @return an {@code OffsetDateTime} based on this date-time with the addition made, not null + * @throws DateTimeException if the addition cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public OffsetDateTime plus(TemporalAdder adder) { + return (OffsetDateTime) adder.addTo(this); + } + + /** + * Returns a copy of this date-time with the specified period added. + *

+ * This method returns a new date-time based on this date-time with the specified period added. + * This can be used to add any period that is defined by a unit, for example to add years, months or days. + * The unit is responsible for the details of the calculation, including the resolution + * of any edge cases in the calculation. + * The offset is not part of the calculation and will be unchanged in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToAdd the amount of the unit to add to the result, may be negative + * @param unit the unit of the period to add, not null + * @return an {@code OffsetDateTime} based on this date-time with the specified period added, not null + * @throws DateTimeException if the unit cannot be added to this type + */ + @Override + public OffsetDateTime plus(long amountToAdd, TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + return with(dateTime.plus(amountToAdd, unit), offset); + } + return unit.doPlus(this, amountToAdd); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code OffsetDateTime} with the specified period in years added. + *

+ * This method adds the specified amount to the years field in three steps: + *

    + *
  1. Add the input years to the year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2008-02-29 (leap year) plus one year would result in the + * invalid date 2009-02-29 (standard year). Instead of returning an invalid + * result, the last valid day of the month, 2009-02-28, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param years the years to add, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the years added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime plusYears(long years) { + return with(dateTime.plusYears(years), offset); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the specified period in months added. + *

+ * This method adds the specified amount to the months field in three steps: + *

    + *
  1. Add the input months to the month-of-year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2007-03-31 plus one month would result in the invalid date + * 2007-04-31. Instead of returning an invalid result, the last valid day + * of the month, 2007-04-30, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param months the months to add, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the months added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime plusMonths(long months) { + return with(dateTime.plusMonths(months), offset); + } + + /** + * Returns a copy of this OffsetDateTime with the specified period in weeks added. + *

+ * This method adds the specified amount in weeks 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. + *

+ * For example, 2008-12-31 plus one week would result in the 2009-01-07. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param weeks the weeks to add, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the weeks added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime plusWeeks(long weeks) { + return with(dateTime.plusWeeks(weeks), offset); + } + + /** + * Returns a copy of this OffsetDateTime with the specified period in days added. + *

+ * 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. + *

+ * For example, 2008-12-31 plus one day would result in the 2009-01-01. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param days the days to add, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the days added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime plusDays(long days) { + return with(dateTime.plusDays(days), offset); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the specified period in hours added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hours the hours to add, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the hours added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime plusHours(long hours) { + return with(dateTime.plusHours(hours), offset); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the specified period in minutes added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param minutes the minutes to add, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the minutes added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime plusMinutes(long minutes) { + return with(dateTime.plusMinutes(minutes), offset); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the specified period in seconds added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param seconds the seconds to add, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the seconds added, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime plusSeconds(long seconds) { + return with(dateTime.plusSeconds(seconds), offset); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the specified period in nanoseconds added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanos the nanos to add, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the nanoseconds added, not null + * @throws DateTimeException if the unit cannot be added to this type + */ + public OffsetDateTime plusNanos(long nanos) { + return with(dateTime.plusNanos(nanos), offset); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date-time with the specified period subtracted. + *

+ * This method returns a new date-time based on this time with the specified period subtracted. + * The subtractor is typically {@link java.time.Period} but may be any other type implementing + * the {@link TemporalSubtractor} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #minus(long, TemporalUnit)}. + * The offset is not part of the calculation and will be unchanged in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param subtractor the subtractor to use, not null + * @return an {@code OffsetDateTime} based on this date-time with the subtraction made, not null + * @throws DateTimeException if the subtraction cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public OffsetDateTime minus(TemporalSubtractor subtractor) { + return (OffsetDateTime) subtractor.subtractFrom(this); + } + + /** + * Returns a copy of this date-time with the specified period subtracted. + *

+ * This method returns a new date-time based on this date-time with the specified period subtracted. + * This can be used to subtract any period that is defined by a unit, for example to subtract years, months or days. + * The unit is responsible for the details of the calculation, including the resolution + * of any edge cases in the calculation. + * The offset is not part of the calculation and will be unchanged in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToSubtract the amount of the unit to subtract from the result, may be negative + * @param unit the unit of the period to subtract, not null + * @return an {@code OffsetDateTime} based on this date-time with the specified period subtracted, not null + */ + @Override + public OffsetDateTime minus(long amountToSubtract, TemporalUnit unit) { + return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code OffsetDateTime} with the specified period in years subtracted. + *

+ * This method subtracts the specified amount from the years field in three steps: + *

    + *
  1. Subtract the input years to the year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2008-02-29 (leap year) minus one year would result in the + * invalid date 2009-02-29 (standard year). Instead of returning an invalid + * result, the last valid day of the month, 2009-02-28, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param years the years to subtract, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the years subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime minusYears(long years) { + return (years == Long.MIN_VALUE ? plusYears(Long.MAX_VALUE).plusYears(1) : plusYears(-years)); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the specified period in months subtracted. + *

+ * This method subtracts the specified amount from the months field in three steps: + *

    + *
  1. Subtract the input months to the month-of-year field
  2. + *
  3. Check if the resulting date would be invalid
  4. + *
  5. Adjust the day-of-month to the last valid day if necessary
  6. + *
+ *

+ * For example, 2007-03-31 minus one month would result in the invalid date + * 2007-04-31. Instead of returning an invalid result, the last valid day + * of the month, 2007-04-30, is selected instead. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param months the months to subtract, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the months subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime minusMonths(long months) { + return (months == Long.MIN_VALUE ? plusMonths(Long.MAX_VALUE).plusMonths(1) : plusMonths(-months)); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the specified period in weeks subtracted. + *

+ * This method subtracts the specified amount in weeks 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. + *

+ * For example, 2008-12-31 minus one week would result in the 2009-01-07. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param weeks the weeks to subtract, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the weeks subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime minusWeeks(long weeks) { + return (weeks == Long.MIN_VALUE ? plusWeeks(Long.MAX_VALUE).plusWeeks(1) : plusWeeks(-weeks)); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the specified period in days subtracted. + *

+ * This method subtracts the specified amount from 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. + *

+ * For example, 2008-12-31 minus one day would result in the 2009-01-01. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param days the days to subtract, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the days subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime minusDays(long days) { + return (days == Long.MIN_VALUE ? plusDays(Long.MAX_VALUE).plusDays(1) : plusDays(-days)); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the specified period in hours subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hours the hours to subtract, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the hours subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime minusHours(long hours) { + return (hours == Long.MIN_VALUE ? plusHours(Long.MAX_VALUE).plusHours(1) : plusHours(-hours)); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the specified period in minutes subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param minutes the minutes to subtract, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the minutes subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime minusMinutes(long minutes) { + return (minutes == Long.MIN_VALUE ? plusMinutes(Long.MAX_VALUE).plusMinutes(1) : plusMinutes(-minutes)); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the specified period in seconds subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param seconds the seconds to subtract, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the seconds subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime minusSeconds(long seconds) { + return (seconds == Long.MIN_VALUE ? plusSeconds(Long.MAX_VALUE).plusSeconds(1) : plusSeconds(-seconds)); + } + + /** + * Returns a copy of this {@code OffsetDateTime} with the specified period in nanoseconds subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanos the nanos to subtract, may be negative + * @return an {@code OffsetDateTime} based on this date-time with the nanoseconds subtracted, not null + * @throws DateTimeException if the result exceeds the supported date range + */ + public OffsetDateTime minusNanos(long nanos) { + return (nanos == Long.MIN_VALUE ? plusNanos(Long.MAX_VALUE).plusNanos(1) : plusNanos(-nanos)); + } + + //----------------------------------------------------------------------- + /** + * Queries this date-time using the specified query. + *

+ * This queries this date-time using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.chrono()) { + return (R) getDate().getChrono(); + } else if (query == Queries.precision()) { + return (R) NANOS; + } else if (query == Queries.offset() || query == Queries.zone()) { + return (R) getOffset(); + } + return Temporal.super.query(query); + } + + /** + * Adjusts the specified temporal object to have the same offset, date + * and time as this object. + *

+ * This returns a temporal object of the same observable type as the input + * with the offset, date and time changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * three times, passing {@link ChronoField#OFFSET_SECONDS}, + * {@link ChronoField#EPOCH_DAY} and {@link ChronoField#NANO_OF_DAY} as the fields. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisOffsetDateTime.adjustInto(temporal);
+     *   temporal = temporal.with(thisOffsetDateTime);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal adjustInto(Temporal temporal) { + return temporal + .with(OFFSET_SECONDS, getOffset().getTotalSeconds()) + .with(EPOCH_DAY, getDate().toEpochDay()) + .with(NANO_OF_DAY, getTime().toNanoOfDay()); + } + + /** + * Calculates the period between this date-time and another date-time in + * terms of the specified unit. + *

+ * This calculates the period between two date-times in terms of a single unit. + * The start and end points are {@code this} and the specified date-time. + * The result will be negative if the end is before the start. + * For example, the period in days between two date-times can be calculated + * using {@code startDateTime.periodUntil(endDateTime, DAYS)}. + *

+ * The {@code Temporal} passed to this method must be an {@code OffsetDateTime}. + * If the offset differs between the two date-times, the specified + * end date-time is normalized to have the same offset as this date-time. + *

+ * The calculation returns a whole number, representing the number of + * complete units between the two date-times. + * For example, the period in months between 2012-06-15T00:00Z and 2012-08-14T23:59Z + * will only be one month as it is one minute short of two months. + *

+ * This method operates in association with {@link TemporalUnit#between}. + * The result of this method is a {@code long} representing the amount of + * the specified unit. By contrast, the result of {@code between} is an + * object that can be used directly in addition/subtraction: + *

+     *   long period = start.periodUntil(end, MONTHS);   // this method
+     *   dateTime.plus(MONTHS.between(start, end));      // use in plus/minus
+     * 
+ *

+ * The calculation is implemented in this method for {@link ChronoUnit}. + * The units {@code NANOS}, {@code MICROS}, {@code MILLIS}, {@code SECONDS}, + * {@code MINUTES}, {@code HOURS} and {@code HALF_DAYS}, {@code DAYS}, + * {@code WEEKS}, {@code MONTHS}, {@code YEARS}, {@code DECADES}, + * {@code CENTURIES}, {@code MILLENNIA} and {@code ERAS} are supported. + * Other {@code ChronoUnit} values will throw an exception. + *

+ * If the unit is not a {@code ChronoUnit}, then the result of this method + * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)} + * passing {@code this} as the first argument and the input temporal as + * the second argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param endDateTime the end date-time, which must be an {@code OffsetDateTime}, not null + * @param unit the unit to measure the period in, not null + * @return the amount of the period between this date-time and the end date-time + * @throws DateTimeException if the period cannot be calculated + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long periodUntil(Temporal endDateTime, TemporalUnit unit) { + if (endDateTime instanceof OffsetDateTime == false) { + Objects.requireNonNull(endDateTime, "endDateTime"); + throw new DateTimeException("Unable to calculate period between objects of two different types"); + } + if (unit instanceof ChronoUnit) { + OffsetDateTime end = (OffsetDateTime) endDateTime; + end = end.withOffsetSameInstant(offset); + return dateTime.periodUntil(end.dateTime, unit); + } + return unit.between(this, endDateTime).getAmount(); + } + + //----------------------------------------------------------------------- + /** + * Returns a zoned date-time formed from the instant represented by this + * date-time and the specified zone ID. + *

+ * This conversion will ignore the visible local date-time and use the underlying instant instead. + * This avoids any problems with local time-line gaps or overlaps. + * The result might have different values for fields such as hour, minute an even day. + *

+ * To attempt to retain the values of the fields, use {@link #atZoneSimilarLocal(ZoneId)}. + * To use the offset as the zone ID, use {@link #toZonedDateTime()}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param zone the time-zone to use, not null + * @return the zoned date-time formed from this date-time, not null + */ + public ZonedDateTime atZoneSameInstant(ZoneId zone) { + return ZonedDateTime.ofInstant(dateTime, offset, zone); + } + + /** + * Returns a zoned date-time formed from this date-time and the specified zone ID. + *

+ * Time-zone rules, such as daylight savings, mean that not every time on the + * local time-line exists. If the local date-time is in a gap or overlap according to + * the rules then a resolver is used to determine the resultant local time and offset. + * This method uses {@link ZonedDateTime#ofLocal(LocalDateTime, ZoneId, ZoneOffset)} + * to retain the offset from this instance if possible. + *

+ * Finer control over gaps and overlaps is available in two ways. + * If you simply want to use the later offset at overlaps then call + * {@link ZonedDateTime#withLaterOffsetAtOverlap()} immediately after this method. + *

+ * To create a zoned date-time at the same instant irrespective of the local time-line, + * use {@link #atZoneSameInstant(ZoneId)}. + * To use the offset as the zone ID, use {@link #toZonedDateTime()}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param zone the time-zone to use, not null + * @return the zoned date-time formed from this date and the earliest valid time for the zone, not null + */ + public ZonedDateTime atZoneSimilarLocal(ZoneId zone) { + return ZonedDateTime.ofLocal(dateTime, zone, offset); + } + + //----------------------------------------------------------------------- + /** + * Converts this date-time to an {@code OffsetDate}. + *

+ * This returns an offset date with the same local date and offset. + * + * @return an OffsetDate representing the date and offset, not null + */ + public OffsetDate toOffsetDate() { + return OffsetDate.of(dateTime.getDate(), offset); + } + + /** + * Converts this date-time to an {@code OffsetTime}. + *

+ * This returns an offset time with the same local time and offset. + * + * @return an OffsetTime representing the time and offset, not null + */ + public OffsetTime toOffsetTime() { + return OffsetTime.of(dateTime.getTime(), offset); + } + + /** + * Converts this date-time to a {@code ZonedDateTime} using the offset as the zone ID. + *

+ * This creates the simplest possible {@code ZonedDateTime} using the offset + * as the zone ID. + *

+ * To control the time-zone used, see {@link #atZoneSameInstant(ZoneId)} and + * {@link #atZoneSimilarLocal(ZoneId)}. + * + * @return a zoned date-time representing the same local date-time and offset, not null + */ + public ZonedDateTime toZonedDateTime() { + return ZonedDateTime.of(dateTime, offset); + } + + /** + * Converts this date-time to an {@code Instant}. + * + * @return an {@code Instant} representing the same instant, not null + */ + public Instant toInstant() { + return dateTime.toInstant(offset); + } + + /** + * Converts this date-time to the number of seconds from the epoch of 1970-01-01T00:00:00Z. + *

+ * This allows this date-time to be converted to a value of the + * {@link ChronoField#INSTANT_SECONDS epoch-seconds} field. This is primarily + * intended for low-level conversions rather than general application usage. + * + * @return the number of seconds from the epoch of 1970-01-01T00:00:00Z + */ + public long toEpochSecond() { + return dateTime.toEpochSecond(offset); + } + + //----------------------------------------------------------------------- + /** + * Compares this {@code OffsetDateTime} to another date-time. + *

+ * The comparison is based on the instant then on the local date-time. + * It is "consistent with equals", as defined by {@link Comparable}. + *

+ * For example, the following is the comparator order: + *

    + *
  1. {@code 2008-12-03T10:30+01:00}
  2. + *
  3. {@code 2008-12-03T11:00+01:00}
  4. + *
  5. {@code 2008-12-03T12:00+02:00}
  6. + *
  7. {@code 2008-12-03T11:30+01:00}
  8. + *
  9. {@code 2008-12-03T12:00+01:00}
  10. + *
  11. {@code 2008-12-03T12:30+01:00}
  12. + *
+ * Values #2 and #3 represent the same instant on the time-line. + * When two values represent the same instant, the local date-time is compared + * to distinguish them. This step is needed to make the ordering + * consistent with {@code equals()}. + * + * @param other the other date-time to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + @Override + public int compareTo(OffsetDateTime other) { + if (getOffset().equals(other.getOffset())) { + return getDateTime().compareTo(other.getDateTime()); + } + int cmp = Long.compare(toEpochSecond(), other.toEpochSecond()); + if (cmp == 0) { + cmp = getTime().getNano() - other.getTime().getNano(); + if (cmp == 0) { + cmp = getDateTime().compareTo(other.getDateTime()); + } + } + return cmp; + } + + //----------------------------------------------------------------------- + /** + * Checks if the instant of this date-time is after that of the specified date-time. + *

+ * This method differs from the comparison in {@link #compareTo} and {@link #equals} in that it + * only compares the instant of the date-time. This is equivalent to using + * {@code dateTime1.toInstant().isAfter(dateTime2.toInstant());}. + * + * @param other the other date-time to compare to, not null + * @return true if this is after the instant of the specified date-time + */ + public boolean isAfter(OffsetDateTime other) { + long thisEpochSec = toEpochSecond(); + long otherEpochSec = other.toEpochSecond(); + return thisEpochSec > otherEpochSec || + (thisEpochSec == otherEpochSec && getTime().getNano() > other.getTime().getNano()); + } + + /** + * Checks if the instant of this date-time is before that of the specified date-time. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the instant of the date-time. This is equivalent to using + * {@code dateTime1.toInstant().isBefore(dateTime2.toInstant());}. + * + * @param other the other date-time to compare to, not null + * @return true if this is before the instant of the specified date-time + */ + public boolean isBefore(OffsetDateTime other) { + long thisEpochSec = toEpochSecond(); + long otherEpochSec = other.toEpochSecond(); + return thisEpochSec < otherEpochSec || + (thisEpochSec == otherEpochSec && getTime().getNano() < other.getTime().getNano()); + } + + /** + * Checks if the instant of this date-time is equal to that of the specified date-time. + *

+ * This method differs from the comparison in {@link #compareTo} and {@link #equals} + * in that it only compares the instant of the date-time. This is equivalent to using + * {@code dateTime1.toInstant().equals(dateTime2.toInstant());}. + * + * @param other the other date-time to compare to, not null + * @return true if the instant equals the instant of the specified date-time + */ + public boolean isEqual(OffsetDateTime other) { + return toEpochSecond() == other.toEpochSecond() && + getTime().getNano() == other.getTime().getNano(); + } + + //----------------------------------------------------------------------- + /** + * Checks if this date-time is equal to another date-time. + *

+ * The comparison is based on the local date-time and the offset. + * To compare for the same instant on the time-line, use {@link #isEqual}. + * Only objects of type {@code OffsetDateTime} 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 OffsetDateTime) { + OffsetDateTime other = (OffsetDateTime) obj; + return dateTime.equals(other.dateTime) && offset.equals(other.offset); + } + return false; + } + + /** + * A hash code for this date-time. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return dateTime.hashCode() ^ offset.hashCode(); + } + + //----------------------------------------------------------------------- + /** + * Outputs this date-time as a {@code String}, such as {@code 2007-12-03T10:15:30+01:00}. + *

+ * The output will be one of the following ISO-8601 formats: + *

    + *
  • {@code yyyy-MM-dd'T'HH:mmXXXXX}
  • + *
  • {@code yyyy-MM-dd'T'HH:mm:ssXXXXX}
  • + *
  • {@code yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX}
  • + *
  • {@code yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXXXX}
  • + *
  • {@code yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXXXXX}
  • + *

+ * The format used will be the shortest that outputs the full value of + * the time where the omitted parts are implied to be zero. + * + * @return a string representation of this date-time, not null + */ + @Override + public String toString() { + return dateTime.toString() + offset.toString(); + } + + /** + * Outputs this date-time as a {@code String} using the formatter. + *

+ * This date-time will be passed to the formatter + * {@link DateTimeFormatter#print(TemporalAccessor) print method}. + * + * @param formatter the formatter to use, not null + * @return the formatted date-time string, not null + * @throws DateTimeException if an error occurs during printing + */ + public String toString(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.print(this); + } + + //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(3);  // identifies this as a OffsetDateTime
+     *  out.writeObject(dateTime);
+     *  out.writeObject(offset);
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.OFFSET_DATE_TIME_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(ObjectOutput out) throws IOException { + out.writeObject(dateTime); + out.writeObject(offset); + } + + static OffsetDateTime readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + LocalDateTime dateTime = (LocalDateTime) in.readObject(); + ZoneOffset offset = (ZoneOffset) in.readObject(); + return OffsetDateTime.of(dateTime, offset); + } + +} diff --git a/src/share/classes/java/time/temporal/OffsetTime.java b/src/share/classes/java/time/temporal/OffsetTime.java new file mode 100644 index 0000000000000000000000000000000000000000..390b1b199d3b16785e94082d0be92468c9537eb2 --- /dev/null +++ b/src/share/classes/java/time/temporal/OffsetTime.java @@ -0,0 +1,1311 @@ +/* + * 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 java.time.temporal; + +import static java.time.temporal.ChronoField.NANO_OF_DAY; +import static java.time.temporal.ChronoField.OFFSET_SECONDS; +import static java.time.temporal.ChronoLocalDateTimeImpl.NANOS_PER_HOUR; +import static java.time.temporal.ChronoLocalDateTimeImpl.NANOS_PER_MINUTE; +import static java.time.temporal.ChronoLocalDateTimeImpl.NANOS_PER_SECOND; +import static java.time.temporal.ChronoLocalDateTimeImpl.SECONDS_PER_DAY; +import static java.time.temporal.ChronoUnit.NANOS; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.Clock; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.zone.ZoneRules; +import java.util.Objects; + +/** + * A time with an offset from UTC/Greenwich in the ISO-8601 calendar system, + * such as {@code 10:15:30+01:00}. + *

+ * {@code OffsetTime} is an immutable date-time object that represents a time, often + * viewed as hour-minute-second-offset. + * This class stores all time fields, to a precision of nanoseconds, + * as well as a zone offset. + * For example, the value "13:45.30.123456789+02:00" can be stored + * in an {@code OffsetTime}. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class OffsetTime + implements Temporal, TemporalAdjuster, Comparable, Serializable { + + /** + * The minimum supported {@code OffsetTime}, '00:00:00+18:00'. + * This is the time of midnight at the start of the day in the maximum offset + * (larger offsets are earlier on the time-line). + * This combines {@link LocalTime#MIN} and {@link ZoneOffset#MAX}. + * This could be used by an application as a "far past" date. + */ + public static final OffsetTime MIN = LocalTime.MIN.atOffset(ZoneOffset.MAX); + /** + * The maximum supported {@code OffsetTime}, '23:59:59.999999999-18:00'. + * This is the time just before midnight at the end of the day in the minimum offset + * (larger negative offsets are later on the time-line). + * This combines {@link LocalTime#MAX} and {@link ZoneOffset#MIN}. + * This could be used by an application as a "far future" date. + */ + public static final OffsetTime MAX = LocalTime.MAX.atOffset(ZoneOffset.MIN); + + /** + * Serialization version. + */ + private static final long serialVersionUID = 7264499704384272492L; + + /** + * The local date-time. + */ + private final LocalTime time; + /** + * The offset from UTC/Greenwich. + */ + private final ZoneOffset offset; + + //----------------------------------------------------------------------- + /** + * Obtains the current time from the system clock in the default time-zone. + *

+ * This will query the {@link java.time.Clock#systemDefaultZone() system clock} in the default + * time-zone to obtain the current time. + * The offset will be calculated from the time-zone in the clock. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @return the current time using the system clock, not null + */ + public static OffsetTime now() { + return now(Clock.systemDefaultZone()); + } + + /** + * Obtains the current time from the system clock in the specified time-zone. + *

+ * This will query the {@link Clock#system(java.time.ZoneId) system clock} to obtain the current time. + * Specifying the time-zone avoids dependence on the default time-zone. + * The offset will be calculated from the specified time-zone. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @param zone the zone ID to use, not null + * @return the current time using the system clock, not null + */ + public static OffsetTime now(ZoneId zone) { + return now(Clock.system(zone)); + } + + /** + * Obtains the current time from the specified clock. + *

+ * This will query the specified clock to obtain the current time. + * The offset will be calculated from the time-zone in the clock. + *

+ * Using this method allows the use of an alternate clock for testing. + * The alternate clock may be introduced using {@link Clock dependency injection}. + * + * @param clock the clock to use, not null + * @return the current time, not null + */ + public static OffsetTime now(Clock clock) { + Objects.requireNonNull(clock, "clock"); + final Instant now = clock.instant(); // called once + return ofInstant(now, clock.getZone().getRules().getOffset(now)); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code OffsetTime} from a local time and an offset. + * + * @param time the local time, not null + * @param offset the zone offset, not null + * @return the offset time, not null + */ + public static OffsetTime of(LocalTime time, ZoneOffset offset) { + return new OffsetTime(time, offset); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code OffsetTime} from an {@code Instant} and zone ID. + *

+ * This creates an offset time with the same instant as that specified. + * Finding the offset from UTC/Greenwich is simple as there is only one valid + * offset for each instant. + *

+ * The date component of the instant is dropped during the conversion. + * This means that the conversion can never fail due to the instant being + * out of the valid range of dates. + * + * @param instant the instant to create the time from, not null + * @param zone the time-zone, which may be an offset, not null + * @return the offset time, not null + */ + public static OffsetTime ofInstant(Instant instant, ZoneId zone) { + Objects.requireNonNull(instant, "instant"); + Objects.requireNonNull(zone, "zone"); + ZoneRules rules = zone.getRules(); + ZoneOffset offset = rules.getOffset(instant); + long secsOfDay = instant.getEpochSecond() % SECONDS_PER_DAY; + secsOfDay = (secsOfDay + offset.getTotalSeconds()) % SECONDS_PER_DAY; + if (secsOfDay < 0) { + secsOfDay += SECONDS_PER_DAY; + } + LocalTime time = LocalTime.ofSecondOfDay(secsOfDay, instant.getNano()); + return new OffsetTime(time, offset); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code OffsetTime} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code OffsetTime}. + *

+ * The conversion extracts and combines {@code LocalTime} and {@code ZoneOffset}. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used in queries via method reference, {@code OffsetTime::from}. + * + * @param temporal the temporal object to convert, not null + * @return the offset time, not null + * @throws DateTimeException if unable to convert to an {@code OffsetTime} + */ + public static OffsetTime from(TemporalAccessor temporal) { + if (temporal instanceof OffsetTime) { + return (OffsetTime) temporal; + } + try { + LocalTime time = LocalTime.from(temporal); + ZoneOffset offset = ZoneOffset.from(temporal); + return new OffsetTime(time, offset); + } catch (DateTimeException ex) { + throw new DateTimeException("Unable to obtain OffsetTime from TemporalAccessor: " + temporal.getClass(), ex); + } + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code OffsetTime} from a text string such as {@code 10:15:30+01:00}. + *

+ * The string must represent a valid time and is parsed using + * {@link java.time.format.DateTimeFormatters#isoOffsetTime()}. + * + * @param text the text to parse such as "10:15:30+01:00", not null + * @return the parsed local time, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static OffsetTime parse(CharSequence text) { + return parse(text, DateTimeFormatters.isoOffsetTime()); + } + + /** + * Obtains an instance of {@code OffsetTime} from a text string using a specific formatter. + *

+ * The text is parsed using the formatter, returning a time. + * + * @param text the text to parse, not null + * @param formatter the formatter to use, not null + * @return the parsed offset time, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static OffsetTime parse(CharSequence text, DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.parse(text, OffsetTime::from); + } + + //----------------------------------------------------------------------- + /** + * Constructor. + * + * @param time the local time, not null + * @param offset the zone offset, not null + */ + private OffsetTime(LocalTime time, ZoneOffset offset) { + this.time = Objects.requireNonNull(time, "time"); + this.offset = Objects.requireNonNull(offset, "offset"); + } + + /** + * Returns a new time based on this one, returning {@code this} where possible. + * + * @param time the time to create with, not null + * @param offset the zone offset to create with, not null + */ + private OffsetTime with(LocalTime time, ZoneOffset offset) { + if (this.time == time && this.offset.equals(offset)) { + return this; + } + return new OffsetTime(time, offset); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this time can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and + * {@link #get(TemporalField) get} methods will throw an exception. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The supported fields are: + *

    + *
  • {@code NANO_OF_SECOND} + *
  • {@code NANO_OF_DAY} + *
  • {@code MICRO_OF_SECOND} + *
  • {@code MICRO_OF_DAY} + *
  • {@code MILLI_OF_SECOND} + *
  • {@code MILLI_OF_DAY} + *
  • {@code SECOND_OF_MINUTE} + *
  • {@code SECOND_OF_DAY} + *
  • {@code MINUTE_OF_HOUR} + *
  • {@code MINUTE_OF_DAY} + *
  • {@code HOUR_OF_AMPM} + *
  • {@code CLOCK_HOUR_OF_AMPM} + *
  • {@code HOUR_OF_DAY} + *
  • {@code CLOCK_HOUR_OF_DAY} + *
  • {@code AMPM_OF_DAY} + *
  • {@code OFFSET_SECONDS} + *
+ * All other {@code ChronoField} instances will return false. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the field is supported is determined by the field. + * + * @param field the field to check, null returns false + * @return true if the field is supported on this time, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return ((ChronoField) field).isTimeField() || field == OFFSET_SECONDS; + } + return field != null && field.doIsSupported(this); + } + + /** + * Gets the range of valid values for the specified field. + *

+ * The range object expresses the minimum and maximum valid values for a field. + * This time is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return + * appropriate range instances. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + @Override + public ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + if (field == OFFSET_SECONDS) { + return field.range(); + } + return time.range(field); + } + return field.doRange(this); + } + + /** + * Gets the value of the specified field from this time as an {@code int}. + *

+ * This queries this time for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this time, except {@code NANO_OF_DAY} and {@code MICRO_OF_DAY} + * which are too large to fit in an {@code int} and throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override // override for Javadoc + public int get(TemporalField field) { + return Temporal.super.get(field); + } + + /** + * Gets the value of the specified field from this time as a {@code long}. + *

+ * This queries this time for the value for the specified field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this time. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + if (field == OFFSET_SECONDS) { + return getOffset().getTotalSeconds(); + } + return time.getLong(field); + } + return field.doGet(this); + } + + //----------------------------------------------------------------------- + /** + * Gets the zone offset, such as '+01:00'. + *

+ * This is the offset of the local time from UTC/Greenwich. + * + * @return the zone offset, not null + */ + public ZoneOffset getOffset() { + return offset; + } + + /** + * Returns a copy of this {@code OffsetTime} with the specified offset ensuring + * that the result has the same local time. + *

+ * This method returns an object with the same {@code LocalTime} and the specified {@code ZoneOffset}. + * No calculation is needed or performed. + * For example, if this time represents {@code 10:30+02:00} and the offset specified is + * {@code +03:00}, then this method will return {@code 10:30+03:00}. + *

+ * To take into account the difference between the offsets, and adjust the time fields, + * use {@link #withOffsetSameInstant}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param offset the zone offset to change to, not null + * @return an {@code OffsetTime} based on this time with the requested offset, not null + */ + public OffsetTime withOffsetSameLocal(ZoneOffset offset) { + return offset != null && offset.equals(this.offset) ? this : new OffsetTime(time, offset); + } + + /** + * Returns a copy of this {@code OffsetTime} with the specified offset ensuring + * that the result is at the same instant on an implied day. + *

+ * This method returns an object with the specified {@code ZoneOffset} and a {@code LocalTime} + * adjusted by the difference between the two offsets. + * This will result in the old and new objects representing the same instant an an implied day. + * This is useful for finding the local time in a different offset. + * For example, if this time represents {@code 10:30+02:00} and the offset specified is + * {@code +03:00}, then this method will return {@code 11:30+03:00}. + *

+ * To change the offset without adjusting the local time use {@link #withOffsetSameLocal}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param offset the zone offset to change to, not null + * @return an {@code OffsetTime} based on this time with the requested offset, not null + */ + public OffsetTime withOffsetSameInstant(ZoneOffset offset) { + if (offset.equals(this.offset)) { + return this; + } + int difference = offset.getTotalSeconds() - this.offset.getTotalSeconds(); + LocalTime adjusted = time.plusSeconds(difference); + return new OffsetTime(adjusted, offset); + } + + //----------------------------------------------------------------------- + /** + * Gets the {@code LocalTime} part of this date-time. + *

+ * 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(); + } + + /** + * Gets the nano-of-second field. + * + * @return the nano-of-second, from 0 to 999,999,999 + */ + public int getNano() { + return time.getNano(); + } + + //----------------------------------------------------------------------- + /** + * Returns an adjusted copy of this time. + *

+ * This returns a new {@code OffsetTime}, based on this one, with the time adjusted. + * The adjustment takes place using the specified adjuster strategy object. + * Read the documentation of the adjuster to understand what adjustment will be made. + *

+ * A simple adjuster might simply set the one of the fields, such as the hour field. + * A more complex adjuster might set the time to the last hour of the day. + *

+ * The classes {@link LocalTime} and {@link ZoneOffset} implement {@code TemporalAdjuster}, + * thus this method can be used to change the time or offset: + *

+     *  result = offsetTime.with(time);
+     *  result = offsetTime.with(offset);
+     * 
+ *

+ * The result of this method is obtained by invoking the + * {@link TemporalAdjuster#adjustInto(Temporal)} method on the + * specified adjuster passing {@code this} as the argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adjuster the adjuster to use, not null + * @return an {@code OffsetTime} based on {@code this} with the adjustment made, not null + * @throws DateTimeException if the adjustment cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public OffsetTime with(TemporalAdjuster adjuster) { + // optimizations + if (adjuster instanceof LocalTime) { + return with((LocalTime) adjuster, offset); + } else if (adjuster instanceof ZoneOffset) { + return with(time, (ZoneOffset) adjuster); + } else if (adjuster instanceof OffsetTime) { + return (OffsetTime) adjuster; + } + return (OffsetTime) adjuster.adjustInto(this); + } + + /** + * Returns a copy of this time with the specified field set to a new value. + *

+ * This returns a new {@code OffsetTime}, based on this one, with the value + * for the specified field changed. + * This can be used to change any supported field, such as the hour, minute or second. + * If it is not possible to set the value, because the field is not supported or for + * some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the adjustment is implemented here. + *

+ * The {@code OFFSET_SECONDS} field will return a time with the specified offset. + * The local time is unaltered. If the new offset value is outside the valid range + * then a {@code DateTimeException} will be thrown. + *

+ * The other {@link #isSupported(TemporalField) supported fields} will behave as per + * the matching method on {@link LocalTime#with(TemporalField, long)} LocalTime}. + * In this case, the offset is not part of the calculation and will be unchanged. + *

+ * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doWith(Temporal, long)} + * passing {@code this} as the argument. In this case, the field determines + * whether and how to adjust the instant. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param field the field to set in the result, not null + * @param newValue the new value of the field in the result + * @return an {@code OffsetTime} based on {@code this} with the specified field set, not null + * @throws DateTimeException if the field cannot be set + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public OffsetTime with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + if (field == OFFSET_SECONDS) { + ChronoField f = (ChronoField) field; + return with(time, ZoneOffset.ofTotalSeconds(f.checkValidIntValue(newValue))); + } + return with(time.with(field, newValue), offset); + } + return field.doWith(this, newValue); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code OffsetTime} with the hour-of-day value altered. + *

+ * The offset does not affect the calculation and will be the same in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hour the hour-of-day to set in the result, from 0 to 23 + * @return an {@code OffsetTime} based on this time with the requested hour, not null + * @throws DateTimeException if the hour value is invalid + */ + public OffsetTime withHour(int hour) { + return with(time.withHour(hour), offset); + } + + /** + * Returns a copy of this {@code OffsetTime} with the minute-of-hour value altered. + *

+ * The offset does not affect the calculation and will be the same in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param minute the minute-of-hour to set in the result, from 0 to 59 + * @return an {@code OffsetTime} based on this time with the requested minute, not null + * @throws DateTimeException if the minute value is invalid + */ + public OffsetTime withMinute(int minute) { + return with(time.withMinute(minute), offset); + } + + /** + * Returns a copy of this {@code OffsetTime} with the second-of-minute value altered. + *

+ * The offset does not affect the calculation and will be the same in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param second the second-of-minute to set in the result, from 0 to 59 + * @return an {@code OffsetTime} based on this time with the requested second, not null + * @throws DateTimeException if the second value is invalid + */ + public OffsetTime withSecond(int second) { + return with(time.withSecond(second), offset); + } + + /** + * Returns a copy of this {@code OffsetTime} with the nano-of-second value altered. + *

+ * The offset does not affect the calculation and will be the same in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanoOfSecond the nano-of-second to set in the result, from 0 to 999,999,999 + * @return an {@code OffsetTime} based on this time with the requested nanosecond, not null + * @throws DateTimeException if the nanos value is invalid + */ + public OffsetTime withNano(int nanoOfSecond) { + return with(time.withNano(nanoOfSecond), offset); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code OffsetTime} with the time truncated. + *

+ * Truncation returns a copy of the original time with fields + * smaller than the specified unit set to zero. + * For example, truncating with the {@link ChronoUnit#MINUTES minutes} unit + * will set the second-of-minute and nano-of-second field to zero. + *

+ * Not all units are accepted. The {@link ChronoUnit#DAYS days} unit and time + * units with an exact duration can be used, other units throw an exception. + *

+ * The offset does not affect the calculation and will be the same in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param unit the unit to truncate to, not null + * @return an {@code OffsetTime} based on this time with the time truncated, not null + * @throws DateTimeException if unable to truncate + */ + public OffsetTime truncatedTo(TemporalUnit unit) { + return with(time.truncatedTo(unit), offset); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this date with the specified period added. + *

+ * This method returns a new time based on this time with the specified period added. + * The adder is typically {@link java.time.Period} but may be any other type implementing + * the {@link TemporalAdder} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #plus(long, TemporalUnit)}. + * The offset is not part of the calculation and will be unchanged in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adder the adder to use, not null + * @return an {@code OffsetTime} based on this time with the addition made, not null + * @throws DateTimeException if the addition cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public OffsetTime plus(TemporalAdder adder) { + return (OffsetTime) adder.addTo(this); + } + + /** + * Returns a copy of this time with the specified period added. + *

+ * This method returns a new time based on this time with the specified period added. + * This can be used to add any period that is defined by a unit, for example to add hours, minutes or seconds. + * The unit is responsible for the details of the calculation, including the resolution + * of any edge cases in the calculation. + * The offset is not part of the calculation and will be unchanged in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToAdd the amount of the unit to add to the result, may be negative + * @param unit the unit of the period to add, not null + * @return an {@code OffsetTime} based on this time with the specified period added, not null + * @throws DateTimeException if the unit cannot be added to this type + */ + @Override + public OffsetTime plus(long amountToAdd, TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + return with(time.plus(amountToAdd, unit), offset); + } + return unit.doPlus(this, amountToAdd); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code OffsetTime} with the specified period in hours added. + *

+ * This adds the specified number of hours to this time, returning a new time. + * The calculation wraps around midnight. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hours the hours to add, may be negative + * @return an {@code OffsetTime} based on this time with the hours added, not null + */ + public OffsetTime plusHours(long hours) { + return with(time.plusHours(hours), offset); + } + + /** + * Returns a copy of this {@code OffsetTime} with the specified period in minutes added. + *

+ * This adds the specified number of minutes to this time, returning a new time. + * The calculation wraps around midnight. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param minutes the minutes to add, may be negative + * @return an {@code OffsetTime} based on this time with the minutes added, not null + */ + public OffsetTime plusMinutes(long minutes) { + return with(time.plusMinutes(minutes), offset); + } + + /** + * Returns a copy of this {@code OffsetTime} with the specified period in seconds added. + *

+ * This adds the specified number of seconds to this time, returning a new time. + * The calculation wraps around midnight. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param seconds the seconds to add, may be negative + * @return an {@code OffsetTime} based on this time with the seconds added, not null + */ + public OffsetTime plusSeconds(long seconds) { + return with(time.plusSeconds(seconds), offset); + } + + /** + * Returns a copy of this {@code OffsetTime} with the specified period in nanoseconds added. + *

+ * This adds the specified number of nanoseconds to this time, returning a new time. + * The calculation wraps around midnight. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanos the nanos to add, may be negative + * @return an {@code OffsetTime} based on this time with the nanoseconds added, not null + */ + public OffsetTime plusNanos(long nanos) { + return with(time.plusNanos(nanos), offset); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this time with the specified period subtracted. + *

+ * This method returns a new time based on this time with the specified period subtracted. + * The subtractor is typically {@link java.time.Period} but may be any other type implementing + * the {@link TemporalSubtractor} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #minus(long, TemporalUnit)}. + * The offset is not part of the calculation and will be unchanged in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param subtractor the subtractor to use, not null + * @return an {@code OffsetTime} based on this time with the subtraction made, not null + * @throws DateTimeException if the subtraction cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public OffsetTime minus(TemporalSubtractor subtractor) { + return (OffsetTime) subtractor.subtractFrom(this); + } + + /** + * Returns a copy of this time with the specified period subtracted. + *

+ * This method returns a new time based on this time with the specified period subtracted. + * This can be used to subtract any period that is defined by a unit, for example to subtract hours, minutes or seconds. + * The unit is responsible for the details of the calculation, including the resolution + * of any edge cases in the calculation. + * The offset is not part of the calculation and will be unchanged in the result. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amountToSubtract the amount of the unit to subtract from the result, may be negative + * @param unit the unit of the period to subtract, not null + * @return an {@code OffsetTime} based on this time with the specified period subtracted, not null + * @throws DateTimeException if the unit cannot be added to this type + */ + @Override + public OffsetTime minus(long amountToSubtract, TemporalUnit unit) { + return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit)); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code OffsetTime} with the specified period in hours subtracted. + *

+ * This subtracts the specified number of hours from this time, returning a new time. + * The calculation wraps around midnight. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param hours the hours to subtract, may be negative + * @return an {@code OffsetTime} based on this time with the hours subtracted, not null + */ + public OffsetTime minusHours(long hours) { + return with(time.minusHours(hours), offset); + } + + /** + * Returns a copy of this {@code OffsetTime} with the specified period in minutes subtracted. + *

+ * This subtracts the specified number of minutes from this time, returning a new time. + * The calculation wraps around midnight. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param minutes the minutes to subtract, may be negative + * @return an {@code OffsetTime} based on this time with the minutes subtracted, not null + */ + public OffsetTime minusMinutes(long minutes) { + return with(time.minusMinutes(minutes), offset); + } + + /** + * Returns a copy of this {@code OffsetTime} with the specified period in seconds subtracted. + *

+ * This subtracts the specified number of seconds from this time, returning a new time. + * The calculation wraps around midnight. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param seconds the seconds to subtract, may be negative + * @return an {@code OffsetTime} based on this time with the seconds subtracted, not null + */ + public OffsetTime minusSeconds(long seconds) { + return with(time.minusSeconds(seconds), offset); + } + + /** + * Returns a copy of this {@code OffsetTime} with the specified period in nanoseconds subtracted. + *

+ * This subtracts the specified number of nanoseconds from this time, returning a new time. + * The calculation wraps around midnight. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanos the nanos to subtract, may be negative + * @return an {@code OffsetTime} based on this time with the nanoseconds subtracted, not null + */ + public OffsetTime minusNanos(long nanos) { + return with(time.minusNanos(nanos), offset); + } + + //----------------------------------------------------------------------- + /** + * Queries this time using the specified query. + *

+ * This queries this time using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.precision()) { + return (R) NANOS; + } else if (query == Queries.offset() || query == Queries.zone()) { + return (R) getOffset(); + } + return Temporal.super.query(query); + } + + /** + * Adjusts the specified temporal object to have the same offset and time + * as this object. + *

+ * This returns a temporal object of the same observable type as the input + * with the offset and time changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * twice, passing {@link ChronoField#OFFSET_SECONDS} and + * {@link ChronoField#NANO_OF_DAY} as the fields. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisOffsetTime.adjustInto(temporal);
+     *   temporal = temporal.with(thisOffsetTime);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal adjustInto(Temporal temporal) { + return temporal + .with(OFFSET_SECONDS, getOffset().getTotalSeconds()) + .with(NANO_OF_DAY, time.toNanoOfDay()); + } + + /** + * Calculates the period between this time and another time in + * terms of the specified unit. + *

+ * This calculates the period between two times in terms of a single unit. + * The start and end points are {@code this} and the specified time. + * The result will be negative if the end is before the start. + * For example, the period in hours between two times can be calculated + * using {@code startTime.periodUntil(endTime, HOURS)}. + *

+ * The {@code Temporal} passed to this method must be an {@code OffsetTime}. + * If the offset differs between the two times, then the specified + * end time is normalized to have the same offset as this time. + *

+ * The calculation returns a whole number, representing the number of + * complete units between the two times. + * For example, the period in hours between 11:30Z and 13:29Z will only + * be one hour as it is one minute short of two hours. + *

+ * This method operates in association with {@link TemporalUnit#between}. + * The result of this method is a {@code long} representing the amount of + * the specified unit. By contrast, the result of {@code between} is an + * object that can be used directly in addition/subtraction: + *

+     *   long period = start.periodUntil(end, HOURS);   // this method
+     *   dateTime.plus(HOURS.between(start, end));      // use in plus/minus
+     * 
+ *

+ * The calculation is implemented in this method for {@link ChronoUnit}. + * The units {@code NANOS}, {@code MICROS}, {@code MILLIS}, {@code SECONDS}, + * {@code MINUTES}, {@code HOURS} and {@code HALF_DAYS} are supported. + * Other {@code ChronoUnit} values will throw an exception. + *

+ * If the unit is not a {@code ChronoUnit}, then the result of this method + * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)} + * passing {@code this} as the first argument and the input temporal as + * the second argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param endTime the end time, which must be an {@code OffsetTime}, not null + * @param unit the unit to measure the period in, not null + * @return the amount of the period between this time and the end time + * @throws DateTimeException if the period cannot be calculated + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long periodUntil(Temporal endTime, TemporalUnit unit) { + if (endTime instanceof OffsetTime == false) { + Objects.requireNonNull(endTime, "endTime"); + throw new DateTimeException("Unable to calculate period between objects of two different types"); + } + if (unit instanceof ChronoUnit) { + OffsetTime end = (OffsetTime) endTime; + long nanosUntil = end.toEpochNano() - toEpochNano(); // no overflow + switch ((ChronoUnit) unit) { + case NANOS: return nanosUntil; + case MICROS: return nanosUntil / 1000; + case MILLIS: return nanosUntil / 1000_000; + case SECONDS: return nanosUntil / NANOS_PER_SECOND; + case MINUTES: return nanosUntil / NANOS_PER_MINUTE; + case HOURS: return nanosUntil / NANOS_PER_HOUR; + case HALF_DAYS: return nanosUntil / (12 * NANOS_PER_HOUR); + } + throw new DateTimeException("Unsupported unit: " + unit.getName()); + } + return unit.between(this, endTime).getAmount(); + } + + //----------------------------------------------------------------------- + /** + * Returns an offset date-time formed from this time at the specified date. + *

+ * This combines this time with the specified date to form an {@code OffsetDateTime}. + * All possible combinations of date and time are valid. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param date the date to combine with, not null + * @return the offset date-time formed from this time and the specified date, not null + */ + public OffsetDateTime atDate(LocalDate date) { + return OffsetDateTime.of(date, time, offset); + } + + //----------------------------------------------------------------------- + /** + * Converts this time to epoch nanos based on 1970-01-01Z. + * + * @return the epoch nanos value + */ + private long toEpochNano() { + long nod = time.toNanoOfDay(); + long offsetNanos = offset.getTotalSeconds() * NANOS_PER_SECOND; + return nod - offsetNanos; + } + + //----------------------------------------------------------------------- + /** + * Compares this {@code OffsetTime} to another time. + *

+ * The comparison is based first on the UTC equivalent instant, then on the local time. + * It is "consistent with equals", as defined by {@link Comparable}. + *

+ * For example, the following is the comparator order: + *

    + *
  1. {@code 10:30+01:00}
  2. + *
  3. {@code 11:00+01:00}
  4. + *
  5. {@code 12:00+02:00}
  6. + *
  7. {@code 11:30+01:00}
  8. + *
  9. {@code 12:00+01:00}
  10. + *
  11. {@code 12:30+01:00}
  12. + *
+ * Values #2 and #3 represent the same instant on the time-line. + * When two values represent the same instant, the local time is compared + * to distinguish them. This step is needed to make the ordering + * consistent with {@code equals()}. + *

+ * To compare the underlying local time of two {@code TemporalAccessor} instances, + * use {@link ChronoField#NANO_OF_DAY} as a comparator. + * + * @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 + */ + @Override + public int compareTo(OffsetTime other) { + if (offset.equals(other.offset)) { + return time.compareTo(other.time); + } + int compare = Long.compare(toEpochNano(), other.toEpochNano()); + if (compare == 0) { + compare = time.compareTo(other.time); + } + return compare; + } + + //----------------------------------------------------------------------- + /** + * Checks if the instant of this {@code OffsetTime} is after that of the + * specified time applying both times to a common date. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the instant of the time. This is equivalent to converting both + * times to an instant using the same date and comparing the instants. + * + * @param other the other time to compare to, not null + * @return true if this is after the instant of the specified time + */ + public boolean isAfter(OffsetTime other) { + return toEpochNano() > other.toEpochNano(); + } + + /** + * Checks if the instant of this {@code OffsetTime} is before that of the + * specified time applying both times to a common date. + *

+ * This method differs from the comparison in {@link #compareTo} in that it + * only compares the instant of the time. This is equivalent to converting both + * times to an instant using the same date and comparing the instants. + * + * @param other the other time to compare to, not null + * @return true if this is before the instant of the specified time + */ + public boolean isBefore(OffsetTime other) { + return toEpochNano() < other.toEpochNano(); + } + + /** + * Checks if the instant of this {@code OffsetTime} is equal to that of the + * specified time applying both times to a common date. + *

+ * This method differs from the comparison in {@link #compareTo} and {@link #equals} + * in that it only compares the instant of the time. This is equivalent to converting both + * times to an instant using the same date and comparing the instants. + * + * @param other the other time to compare to, not null + * @return true if this is equal to the instant of the specified time + */ + public boolean isEqual(OffsetTime other) { + return toEpochNano() == other.toEpochNano(); + } + + //----------------------------------------------------------------------- + /** + * Checks if this time is equal to another time. + *

+ * The comparison is based on the local-time and the offset. + * To compare for the same instant on the time-line, use {@link #isEqual(OffsetTime)}. + *

+ * Only objects of type {@code OffsetTime} are compared, other types return false. + * To compare the underlying local time 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 OffsetTime) { + OffsetTime other = (OffsetTime) obj; + return time.equals(other.time) && offset.equals(other.offset); + } + return false; + } + + /** + * A hash code for this time. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return time.hashCode() ^ offset.hashCode(); + } + + //----------------------------------------------------------------------- + /** + * Outputs this time as a {@code String}, such as {@code 10:15:30+01:00}. + *

+ * The output will be one of the following ISO-8601 formats: + *

    + *
  • {@code HH:mmXXXXX}
  • + *
  • {@code HH:mm:ssXXXXX}
  • + *
  • {@code HH:mm:ss.SSSXXXXX}
  • + *
  • {@code HH:mm:ss.SSSSSSXXXXX}
  • + *
  • {@code HH:mm:ss.SSSSSSSSSXXXXX}
  • + *

+ * The format used will be the shortest that outputs the full value of + * the time where the omitted parts are implied to be zero. + * + * @return a string representation of this time, not null + */ + @Override + public String toString() { + return time.toString() + offset.toString(); + } + + /** + * Outputs this time as a {@code String} using the formatter. + *

+ * This time will be passed to the formatter + * {@link DateTimeFormatter#print(TemporalAccessor) print method}. + * + * @param formatter the formatter to use, not null + * @return the formatted time string, not null + * @throws DateTimeException if an error occurs during printing + */ + public String toString(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.print(this); + } + + // ----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(2);  // identifies this as a OffsetDateTime
+     *  out.writeObject(time);
+     *  out.writeObject(offset);
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.OFFSET_TIME_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(ObjectOutput out) throws IOException { + out.writeObject(time); + out.writeObject(offset); + } + + static OffsetTime readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + LocalTime time = (LocalTime) in.readObject(); + ZoneOffset offset = (ZoneOffset) in.readObject(); + return OffsetTime.of(time, offset); + } + +} diff --git a/src/share/classes/java/time/temporal/Queries.java b/src/share/classes/java/time/temporal/Queries.java new file mode 100644 index 0000000000000000000000000000000000000000..74b8f1adf856394a57c4c5a19c99476af741e0ac --- /dev/null +++ b/src/share/classes/java/time/temporal/Queries.java @@ -0,0 +1,302 @@ +/* + * 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 java.time.temporal; + +import static java.time.temporal.ChronoField.OFFSET_SECONDS; + +import java.time.ZoneId; +import java.time.ZoneOffset; + +/** + * Common implementations of {@code TemporalQuery}. + *

+ * This class provides common implementations of {@link TemporalQuery}. + * These queries are primarily used as optimizations, allowing the internals + * of other objects to be extracted effectively. Note that application code + * can also use the {@code from(TemporalAccessor)} method on most temporal + * objects as a method reference matching the query interface, such as + * {@code LocalDate::from} and {@code ZoneId::from}. + *

+ * There are two equivalent ways of using a {@code TemporalQuery}. + * The first is to invoke the method on the interface directly. + * The second is to use {@link TemporalAccessor#query(TemporalQuery)}: + *

+ *   // these two lines are equivalent, but the second approach is recommended
+ *   dateTime = query.queryFrom(dateTime);
+ *   dateTime = dateTime.query(query);
+ * 
+ * It is recommended to use the second approach, {@code query(TemporalQuery)}, + * as it is a lot clearer to read in code. + * + *

Specification for implementors

+ * This is a thread-safe utility class. + * All returned adjusters are immutable and thread-safe. + * + * @since 1.8 + */ +public final class Queries { + // note that it is vital that each method supplies a constant, not a + // calculated value, as they will be checked for using == + // it is also vital that each constant is different (due to the == checking) + // as such, alterations to use lambdas must be done with extreme care + + /** + * Private constructor since this is a utility class. + */ + private Queries() { + } + + //----------------------------------------------------------------------- + // special constants should be used to extract information from a TemporalAccessor + // that cannot be derived in other ways + // Javadoc added here, so as to pretend they are more normal than they really are + + /** + * A strict query for the {@code ZoneId}. + *

+ * This queries a {@code TemporalAccessor} for the zone. + * The zone is only returned if the date-time conceptually contains a {@code ZoneId}. + * It will not be returned if the date-time only conceptually has an {@code ZoneOffset}. + * Thus a {@link java.time.ZonedDateTime ZonedDateTime} will return the result of + * {@code getZone()}, but an {@link java.time.temporal.OffsetDateTime OffsetDateTime} will + * return null. + *

+ * In most cases, applications should use {@link #ZONE} as this query is too strict. + *

+ * The result from JDK classes implementing {@code TemporalAccessor} is as follows:
+ * {@code LocalDate} returns null
+ * {@code LocalTime} returns null
+ * {@code LocalDateTime} returns null
+ * {@code ZonedDateTime} returns the associated zone
+ * {@code OffsetDate} returns null
+ * {@code OffsetTime} returns null
+ * {@code OffsetDateTime} returns null
+ * {@code ChronoLocalDate} returns null
+ * {@code ChronoLocalDateTime} returns null
+ * {@code ChronoZonedDateTime} returns the associated zone
+ * {@code Era} returns null
+ * {@code DayOfWeek} returns null
+ * {@code Month} returns null
+ * {@code Year} returns null
+ * {@code YearMonth} returns null
+ * {@code MonthDay} returns null
+ * {@code ZoneOffset} returns null
+ * {@code Instant} returns null
+ * @return a ZoneId, may be null + */ + public static final TemporalQuery zoneId() { + return ZONE_ID; + } + static final TemporalQuery ZONE_ID = new TemporalQuery() { + @Override + public ZoneId queryFrom(TemporalAccessor temporal) { + return temporal.query(this); + } + }; + + /** + * A query for the {@code Chrono}. + *

+ * This queries a {@code TemporalAccessor} for the chronology. + * If the target {@code TemporalAccessor} represents a date, or part of a date, + * then it should return the chronology that the date is expressed in. + * As a result of this definition, objects only representing time, such as + * {@code LocalTime}, will return null. + *

+ * The result from JDK classes implementing {@code TemporalAccessor} is as follows:
+ * {@code LocalDate} returns {@code ISOChrono.INSTANCE}
+ * {@code LocalTime} returns null (does not represent a date)
+ * {@code LocalDateTime} returns {@code ISOChrono.INSTANCE}
+ * {@code ZonedDateTime} returns {@code ISOChrono.INSTANCE}
+ * {@code OffsetDate} returns {@code ISOChrono.INSTANCE}
+ * {@code OffsetTime} returns null (does not represent a date)
+ * {@code OffsetDateTime} returns {@code ISOChrono.INSTANCE}
+ * {@code ChronoLocalDate} returns the associated chronology
+ * {@code ChronoLocalDateTime} returns the associated chronology
+ * {@code ChronoZonedDateTime} returns the associated chronology
+ * {@code Era} returns the associated chronology
+ * {@code DayOfWeek} returns null (shared across chronologies)
+ * {@code Month} returns {@code ISOChrono.INSTANCE}
+ * {@code Year} returns {@code ISOChrono.INSTANCE}
+ * {@code YearMonth} returns {@code ISOChrono.INSTANCE}
+ * {@code MonthDay} returns null {@code ISOChrono.INSTANCE}
+ * {@code ZoneOffset} returns null (does not represent a date)
+ * {@code Instant} returns null (does not represent a date)
+ *

+ * The method {@link Chrono#from(TemporalAccessor)} can be used as a + * {@code TemporalQuery} via a method reference, {@code Chrono::from}. + * That method is equivalent to this query, except that it throws an + * exception if a chronology cannot be obtained. + * @return a Chrono, may be null + */ + public static final TemporalQuery> chrono() { + return CHRONO; + } + static final TemporalQuery> CHRONO = new TemporalQuery>() { + @Override + public Chrono queryFrom(TemporalAccessor temporal) { + return temporal.query(this); + } + }; + + /** + * A query for the smallest supported unit. + *

+ * This queries a {@code TemporalAccessor} for the time precision. + * If the target {@code TemporalAccessor} represents a consistent or complete date-time, + * date or time then this must return the smallest precision actually supported. + * Note that fields such as {@code NANO_OF_DAY} and {@code NANO_OF_SECOND} + * are defined to always return ignoring the precision, thus this is the only + * way to find the actual smallest supported unit. + * For example, were {@code GregorianCalendar} to implement {@code TemporalAccessor} + * it would return a precision of {@code MILLIS}. + *

+ * The result from JDK classes implementing {@code TemporalAccessor} is as follows:
+ * {@code LocalDate} returns {@code DAYS}
+ * {@code LocalTime} returns {@code NANOS}
+ * {@code LocalDateTime} returns {@code NANOS}
+ * {@code ZonedDateTime} returns {@code NANOS}
+ * {@code OffsetDate} returns {@code DAYS}
+ * {@code OffsetTime} returns {@code NANOS}
+ * {@code OffsetDateTime} returns {@code NANOS}
+ * {@code ChronoLocalDate} returns {@code DAYS}
+ * {@code ChronoLocalDateTime} returns {@code NANOS}
+ * {@code ChronoZonedDateTime} returns {@code NANOS}
+ * {@code Era} returns {@code ERAS}
+ * {@code DayOfWeek} returns {@code DAYS}
+ * {@code Month} returns {@code MONTHS}
+ * {@code Year} returns {@code YEARS}
+ * {@code YearMonth} returns {@code MONTHS}
+ * {@code MonthDay} returns null (does not represent a complete date or time)
+ * {@code ZoneOffset} returns null (does not represent a date or time)
+ * {@code Instant} returns {@code NANOS}
+ * @return a ChronoUnit, may be null + */ + public static final TemporalQuery precision() { + return PRECISION; + } + static final TemporalQuery PRECISION = new TemporalQuery() { + @Override + public ChronoUnit queryFrom(TemporalAccessor temporal) { + return temporal.query(this); + } + }; + + //----------------------------------------------------------------------- + // non-special constants are standard queries that derive information from other information + /** + * A lenient query for the {@code ZoneId}, falling back to the {@code ZoneOffset}. + *

+ * This queries a {@code TemporalAccessor} for the zone. + * It first tries to obtain the zone, using {@link #zoneId()}. + * If that is not found it tries to obtain the {@link #offset()}. + *

+ * In most cases, applications should use this query rather than {@code #zoneId()}. + *

+ * This query examines the {@link java.time.temporal.ChronoField#OFFSET_SECONDS offset-seconds} + * field and uses it to create a {@code ZoneOffset}. + *

+ * The method {@link ZoneId#from(TemporalAccessor)} can be used as a + * {@code TemporalQuery} via a method reference, {@code ZoneId::from}. + * That method is equivalent to this query, except that it throws an + * exception if a zone cannot be obtained. + * @return a ZoneId, may be null + */ + public static final TemporalQuery zone() { + return ZONE; + } + static final TemporalQuery ZONE = new TemporalQuery() { + @Override + public ZoneId queryFrom(TemporalAccessor temporal) { + ZoneId zone = temporal.query(ZONE_ID); + return (zone != null ? zone : temporal.query(OFFSET)); + } + }; + + /** + * A query for the {@code ZoneOffset}. + *

+ * This queries a {@code TemporalAccessor} for the offset. + *

+ * This query examines the {@link java.time.temporal.ChronoField#OFFSET_SECONDS offset-seconds} + * field and uses it to create a {@code ZoneOffset}. + *

+ * The method {@link ZoneOffset#from(TemporalAccessor)} can be used as a + * {@code TemporalQuery} via a method reference, {@code ZoneOffset::from}. + * That method is equivalent to this query, except that it throws an + * exception if an offset cannot be obtained. + * @return a ZoneOffset, may be null + */ + public static final TemporalQuery offset() { + return OFFSET; + } + static final TemporalQuery OFFSET = new TemporalQuery() { + @Override + public ZoneOffset queryFrom(TemporalAccessor temporal) { + if (temporal.isSupported(OFFSET_SECONDS)) { + return ZoneOffset.ofTotalSeconds(temporal.get(OFFSET_SECONDS)); + } + return null; + } + }; + +} diff --git a/src/share/classes/java/time/temporal/Ser.java b/src/share/classes/java/time/temporal/Ser.java new file mode 100644 index 0000000000000000000000000000000000000000..2f182e0439bb2bc40b72f705910c60380e946653 --- /dev/null +++ b/src/share/classes/java/time/temporal/Ser.java @@ -0,0 +1,223 @@ +/* + * 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) 2011-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 java.time.temporal; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.StreamCorruptedException; +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * The shared serialization delegate for this package. + * + *

Implementation notes

+ * This class wraps the object being serialized, and takes a byte representing the type of the class to + * be serialized. This byte can also be used for versioning the serialization format. In this case another + * byte flag would be used in order to specify an alternative version of the type format. + * For example {@code CHRONO_TYPE_VERSION_2 = 21} + *

+ * In order to serialise the object it writes its byte and then calls back to the appropriate class where + * the serialisation is performed. In order to deserialise the object it read in the type byte, switching + * in order to select which class to call back into. + *

+ * The serialisation format is determined on a per class basis. In the case of field based classes each + * of the fields is written out with an appropriate size format in descending order of the field's size. For + * example in the case of {@link LocalDate} year is written before month. Composite classes, such as + * {@link LocalDateTime} are serialised as one object. Enum classes are serialised using the index of their + * element. + *

+ * This class is mutable and should be created once per serialization. + * + * @serial include + * @since 1.8 + */ +final class Ser implements Externalizable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = -6103370247208168577L; + + static final byte OFFSET_DATE_TYPE = 1; + static final byte OFFSET_TIME_TYPE = 2; + static final byte OFFSET_DATE_TIME_TYPE = 3; + static final byte YEAR_TYPE = 4; + static final byte YEAR_MONTH_TYPE = 5; + static final byte MONTH_DAY_TYPE = 6; + static final byte CHRONO_TYPE = 7; + static final byte CHRONO_LOCAL_DATE_TIME_TYPE = 8; + static final byte CHRONO_ZONE_DATE_TIME_TYPE = 9; + static final byte SIMPLE_PERIOD_TYPE = 10; + + /** The type being serialized. */ + private byte type; + /** The object being serialized. */ + private Object object; + + /** + * Constructor for deserialization. + */ + public Ser() { + } + + /** + * Creates an instance for serialization. + * + * @param type the type + * @param object the object + */ + Ser(byte type, Object object) { + this.type = type; + this.object = object; + } + + //----------------------------------------------------------------------- + /** + * Implements the {@code Externalizable} interface to write the object. + * + * @param out the data stream to write to, not null + */ + @Override + public void writeExternal(ObjectOutput out) throws IOException { + writeInternal(type, object, out); + } + + private static void writeInternal(byte type, Object object, ObjectOutput out) throws IOException { + out.writeByte(type); + switch (type) { + case OFFSET_DATE_TYPE: + ((OffsetDate) object).writeExternal(out); + break; + case OFFSET_TIME_TYPE: + ((OffsetTime) object).writeExternal(out); + break; + case OFFSET_DATE_TIME_TYPE: + ((OffsetDateTime) object).writeExternal(out); + break; + case YEAR_TYPE: + ((Year) object).writeExternal(out); + break; + case YEAR_MONTH_TYPE: + ((YearMonth) object).writeExternal(out); + break; + case MONTH_DAY_TYPE: + ((MonthDay) object).writeExternal(out); + break; + case CHRONO_TYPE: + ((Chrono) object).writeExternal(out); + break; + case CHRONO_LOCAL_DATE_TIME_TYPE: + ((ChronoLocalDateTimeImpl) object).writeExternal(out); + break; + case CHRONO_ZONE_DATE_TIME_TYPE: + ((ChronoZonedDateTimeImpl) object).writeExternal(out); + break; + case SIMPLE_PERIOD_TYPE: + ((SimplePeriod) object).writeExternal(out); + break; + default: + throw new InvalidClassException("Unknown serialized type"); + } + } + + //----------------------------------------------------------------------- + /** + * Implements the {@code Externalizable} interface to read the object. + * + * @param in the data to read, not null + */ + @Override + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + type = in.readByte(); + object = readInternal(type, in); + } + + static Object read(ObjectInput in) throws IOException, ClassNotFoundException { + byte type = in.readByte(); + return readInternal(type, in); + } + + private static Object readInternal(byte type, ObjectInput in) throws IOException, ClassNotFoundException { + switch (type) { + case OFFSET_DATE_TYPE: return OffsetDate.readExternal(in); + case OFFSET_TIME_TYPE: return OffsetTime.readExternal(in); + case OFFSET_DATE_TIME_TYPE: return OffsetDateTime.readExternal(in); + case YEAR_TYPE: return Year.readExternal(in); + case YEAR_MONTH_TYPE: return YearMonth.readExternal(in); + case MONTH_DAY_TYPE: return MonthDay.readExternal(in); + case CHRONO_TYPE: return Chrono.readExternal(in); + case CHRONO_LOCAL_DATE_TIME_TYPE: return ChronoLocalDateTimeImpl.readExternal(in); + case CHRONO_ZONE_DATE_TIME_TYPE: return ChronoZonedDateTimeImpl.readExternal(in); + case SIMPLE_PERIOD_TYPE: return SimplePeriod.readExternal(in); + default: throw new StreamCorruptedException("Unknown serialized type"); + } + } + + /** + * Returns the object that will replace this one. + * + * @return the read object, should never be null + */ + private Object readResolve() { + return object; + } + +} diff --git a/src/share/classes/java/time/temporal/SimplePeriod.java b/src/share/classes/java/time/temporal/SimplePeriod.java new file mode 100644 index 0000000000000000000000000000000000000000..5eb60f5c9038ad4948e1493181d7629efd54e46d --- /dev/null +++ b/src/share/classes/java/time/temporal/SimplePeriod.java @@ -0,0 +1,348 @@ +/* + * 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 java.time.temporal; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.DateTimeException; +import java.util.Objects; + +/** + * A period of time, measured as an amount of a single unit, such as '3 Months'. + *

+ * A {@code SimplePeriod} represents an amount of time measured in terms of a + * single {@link TemporalUnit unit}. Any unit may be used with this class. + * An alternative period implementation is {@link java.time.Period Period}, which + * allows a combination of date and time units. + *

+ * This class is the return type from {@link TemporalUnit#between}. + * It can be used more generally, but is designed to enable the following code: + *

+ *  date = date.minus(MONTHS.between(start, end));
+ * 
+ * The unit determines which calendar systems it can be added to. + *

+ * The period is modeled as a directed amount of time, meaning that the period may + * be negative. See {@link #abs()} to ensure the period is positive. + * + *

Specification for implementors

+ * This class is immutable and thread-safe, providing that the unit is immutable, + * which it is required to be. + * + * @since 1.8 + */ +public final class SimplePeriod + implements TemporalAdder, TemporalSubtractor, Comparable, Serializable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = 3752975649629L; + + /** + * The amount of the unit. + */ + private final long amount; + /** + * The unit. + */ + private final TemporalUnit unit; + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code SimplePeriod} from a period in the specified unit. + *

+ * The parameters represent the two parts of a phrase like '6 Days'. For example: + *

+     *  SimplePeriod.of(3, SECONDS);
+     *  SimplePeriod.of(5, YEARS);
+     * 
+ * + * @param amount the amount of the period, measured in terms of the unit, positive or negative + * @param unit the unit that the period is measured in, not null + * @return the period, not null + */ + public static SimplePeriod of(long amount, TemporalUnit unit) { + Objects.requireNonNull(unit, "unit"); + return new SimplePeriod(amount, unit); + } + + //----------------------------------------------------------------------- + /** + * Constructor. + * + * @param amount the amount of the period, measured in terms of the unit, positive or negative + * @param unit the unit that the period is measured in, not null + */ + SimplePeriod(long amount, TemporalUnit unit) { + this.amount = amount; + this.unit = unit; + } + + //----------------------------------------------------------------------- + /** + * Gets the amount of this period. + *

+ * In the phrase "2 Months", the amount is 2. + * + * @return the amount of the period, may be negative + */ + public long getAmount() { + return amount; + } + + /** + * Gets the unit of this period. + *

+ * In the phrase "2 Months", the unit is "Months". + * + * @return the unit of the period, not null + */ + public TemporalUnit getUnit() { + return unit; + } + + //------------------------------------------------------------------------- + /** + * Adds this period to the specified temporal object. + *

+ * This returns a temporal object of the same observable type as the input + * with this period added. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#plus(TemporalAdder)}. + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   dateTime = thisPeriod.addTo(dateTime);
+     *   dateTime = dateTime.plus(thisPeriod);
+     * 
+ *

+ * The calculation is equivalent to invoking {@link Temporal#plus(long, TemporalUnit)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the temporal object to adjust, not null + * @return an object of the same type with the adjustment made, not null + * @throws DateTimeException if unable to add + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal addTo(Temporal temporal) { + return temporal.plus(amount, unit); + } + + /** + * Subtracts this period to the specified temporal object. + *

+ * This returns a temporal object of the same observable type as the input + * with this period subtracted. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#plus(TemporalAdder)}. + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   dateTime = thisPeriod.subtractFrom(dateTime);
+     *   dateTime = dateTime.minus(thisPeriod);
+     * 
+ *

+ * The calculation is equivalent to invoking {@link Temporal#minus(long, TemporalUnit)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the temporal object to adjust, not null + * @return an object of the same type with the adjustment made, not null + * @throws DateTimeException if unable to subtract + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal subtractFrom(Temporal temporal) { + return temporal.minus(amount, unit); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this period with a positive amount. + *

+ * This returns a period with the absolute value of the amount and the same unit. + * If the amount of this period is positive or zero, then this period is returned. + * If the amount of this period is negative, then a period with the negated + * amount is returned. If the amount equals {@code Long.MIN_VALUE}, + * an {@code ArithmeticException} is thrown + *

+ * This is useful to convert the result of {@link TemporalUnit#between} to + * a positive amount when you do not know which date is the earlier and + * which is the later. + * + * @return a period with a positive amount and the same unit, not null + * @throws ArithmeticException if the amount is {@code Long.MIN_VALUE} + */ + public SimplePeriod abs() { + if (amount == Long.MIN_VALUE) { + throw new ArithmeticException("Unable to call abs() on MIN_VALUE"); + } + return (amount >= 0 ? this : new SimplePeriod(-amount, unit)); + } + + //----------------------------------------------------------------------- + /** + * Compares this {@code SimplePeriod} to another period. + *

+ * The comparison is based on the amount within the unit. + * Only two periods with the same unit can be compared. + * + * @param other the other period to compare to, not null + * @return the comparator value, negative if less, positive if greater + * @throws IllegalArgumentException if the units do not match + */ + @Override + public int compareTo(SimplePeriod other) { + if (unit.equals(other.unit) == false) { + throw new IllegalArgumentException("Unable to compare periods with different units"); + } + return Long.compare(amount, other.amount); + } + + //----------------------------------------------------------------------- + /** + * Checks if this period is equal to another period. + *

+ * The comparison is based on the amount and unit. + * + * @param obj the object to check, null returns false + * @return true if this is equal to the other period + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof SimplePeriod) { + SimplePeriod other = (SimplePeriod) obj; + return amount == other.amount && unit.equals(other.unit); + } + return false; + } + + /** + * A hash code for this period. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return unit.hashCode() ^ (int) (amount ^ (amount >>> 32)); + } + + //----------------------------------------------------------------------- + /** + * Outputs this period as a {@code String}, such as {@code 2 Months}. + *

+ * The string consists of the amount, then a space, then the unit name. + * + * @return a string representation of this period, not null + */ + @Override + public String toString() { + return amount + " " + unit.getName(); + } + + //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(10);  // identifies this as a SimplePeriod
+     *  out.writeLong(amount);
+     *  out.writeObject(unit);
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.SIMPLE_PERIOD_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(ObjectOutput out) throws IOException { + out.writeLong(amount); + out.writeObject(unit); + } + + static SimplePeriod readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + long amount = in.readLong(); + TemporalUnit unit = (TemporalUnit) in.readObject(); + return SimplePeriod.of(amount, unit); + } + +} diff --git a/src/share/classes/java/time/temporal/Temporal.java b/src/share/classes/java/time/temporal/Temporal.java new file mode 100644 index 0000000000000000000000000000000000000000..0eddf27b96449a8308f9b65c80dea5e8482176ee --- /dev/null +++ b/src/share/classes/java/time/temporal/Temporal.java @@ -0,0 +1,410 @@ +/* + * 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) 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 java.time.temporal; + +import java.time.DateTimeException; +import java.time.ZoneId; + +/** + * Framework-level interface defining read-write access to a temporal object, + * such as a date, time, offset or some combination of these. + *

+ * This is the base interface type for date, time and offset objects that + * are complete enough to be manipulated using plus and minus. + * It is implemented by those classes that can provide and manipulate information + * as {@linkplain TemporalField fields} or {@linkplain TemporalQuery queries}. + * See {@link TemporalAccessor} for the read-only version of this interface. + *

+ * Most date and time information can be represented as a number. + * These are modeled using {@code TemporalField} with the number held using + * a {@code long} to handle large values. Year, month and day-of-month are + * simple examples of fields, but they also include instant and offsets. + * See {@link ChronoField} for the standard set of fields. + *

+ * Two pieces of date/time information cannot be represented by numbers, + * the {@linkplain Chrono chronology} and the {@linkplain ZoneId time-zone}. + * These can be accessed via {@link #query(TemporalQuery) queries} using + * the static methods defined on {@link Queries}. + *

+ * This interface is a framework-level interface that should not be widely + * used in application code. Instead, applications should create and pass + * around instances of concrete types, such as {@code LocalDate}. + * There are many reasons for this, part of which is that implementations + * of this interface may be in calendar systems other than ISO. + * See {@link ChronoLocalDate} for a fuller discussion of the issues. + * + *

When to implement

+ *

+ * A class should implement this interface if it meets three criteria: + *

    + *
  • it provides access to date/time/offset information, as per {@code TemporalAccessor} + *
  • the set of fields are contiguous from the largest to the smallest + *
  • the set of fields are complete, such that no other field is needed to define the + * valid range of values for the fields that are represented + *

+ *

+ * Four examples make this clear: + *

    + *
  • {@code LocalDate} implements this interface as it represents a set of fields + * that are contiguous from days to forever and require no external information to determine + * the validity of each date. It is therefore able to implement plus/minus correctly. + *
  • {@code LocalTime} implements this interface as it represents a set of fields + * that are contiguous from nanos to within days and require no external information to determine + * validity. It is able to implement plus/minus correctly, by wrapping around the day. + *
  • {@code MonthDay}, the combination of month-of-year and day-of-month, does not implement + * this interface. While the combination is contiguous, from days to months within years, + * the combination does not have sufficient information to define the valid range of values + * for day-of-month. As such, it is unable to implement plus/minus correctly. + *
  • The combination day-of-week and day-of-month ("Friday the 13th") should not implement + * this interface. It does not represent a contiguous set of fields, as days to weeks overlaps + * days to months. + *

+ * + *

Specification for implementors

+ * This interface places no restrictions on the mutability of implementations, + * however immutability is strongly recommended. + * All implementations must be {@link Comparable}. + * + * @since 1.8 + */ +public interface Temporal extends TemporalAccessor { + + /** + * Returns an adjusted object of the same type as this object with the adjustment made. + *

+ * This adjusts this date-time according to the rules of the specified adjuster. + * A simple adjuster might simply set the one of the fields, such as the year field. + * A more complex adjuster might set the date to the last day of the month. + * A selection of common adjustments is provided in {@link Adjusters}. + * These include finding the "last day of the month" and "next Wednesday". + * The adjuster is responsible for handling special cases, such as the varying + * lengths of month and leap years. + *

+ * Some example code indicating how and why this method is used: + *

+     *  date = date.with(Month.JULY);        // most key classes implement TemporalAdjuster
+     *  date = date.with(lastDayOfMonth());  // static import from Adjusters
+     *  date = date.with(next(WEDNESDAY));   // static import from Adjusters and DayOfWeek
+     * 
+ * + *

Specification for implementors

+ * Implementations must not alter either this object. + * Instead, an adjusted copy of the original must be returned. + * This provides equivalent, safe behavior for immutable and mutable implementations. + *

+ * The default implementation must behave equivalent to this code: + *

+     *  return adjuster.adjustInto(this);
+     * 
+ * + * @param adjuster the adjuster to use, not null + * @return an object of the same type with the specified adjustment made, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + public default Temporal with(TemporalAdjuster adjuster) { + return adjuster.adjustInto(this); + } + + /** + * Returns an object of the same type as this object with the specified field altered. + *

+ * This returns a new object based on this one with the value for the specified field changed. + * For example, on a {@code LocalDate}, this could be used to set the year, month or day-of-month. + * The returned object will have the same observable type as this object. + *

+ * In some cases, changing a field is not fully defined. For example, if the target object is + * a date representing the 31st January, then changing the month to February would be unclear. + * In cases like this, the field is responsible for resolving the result. Typically it will choose + * the previous valid date, which would be the last valid day of February in this example. + * + *

Specification for implementors

+ * Implementations must check and handle all fields defined in {@link ChronoField}. + * If the field is supported, then the adjustment must be performed. + * If unsupported, then a {@code DateTimeException} must be thrown. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doWith(Temporal, long)} + * passing {@code this} as the first argument. + *

+ * Implementations must not alter either this object or the specified temporal object. + * Instead, an adjusted copy of the original must be returned. + * This provides equivalent, safe behavior for immutable and mutable implementations. + * + * @param field the field to set in the result, not null + * @param newValue the new value of the field in the result + * @return an object of the same type with the specified field set, not null + * @throws DateTimeException if the field cannot be set + * @throws ArithmeticException if numeric overflow occurs + */ + Temporal with(TemporalField field, long newValue); + + //----------------------------------------------------------------------- + /** + * Returns an object of the same type as this object with an amount added. + *

+ * This adjusts this temporal, adding according to the rules of the specified adder. + * The adder is typically a {@link java.time.Period} but may be any other type implementing + * the {@link TemporalAdder} interface, such as {@link java.time.Duration}. + *

+ * Some example code indicating how and why this method is used: + *

+     *  date = date.plus(period);                      // add a Period instance
+     *  date = date.plus(duration);                    // add a Duration instance
+     *  date = date.plus(MONTHS.between(start, end));  // static import of MONTHS field
+     *  date = date.plus(workingDays(6));              // example user-written workingDays method
+     * 
+ *

+ * Note that calling {@code plus} followed by {@code minus} is not guaranteed to + * return the same date-time. + * + *

Specification for implementors

+ * Implementations must not alter either this object. + * Instead, an adjusted copy of the original must be returned. + * This provides equivalent, safe behavior for immutable and mutable implementations. + *

+ * The default implementation must behave equivalent to this code: + *

+     *  return adder.addTo(this);
+     * 
+ * + * @param adder the adder to use, not null + * @return an object of the same type with the specified adjustment made, not null + * @throws DateTimeException if the addition cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + public default Temporal plus(TemporalAdder adder) { + return adder.addTo(this); + } + + /** + * Returns an object of the same type as this object with the specified period added. + *

+ * This method returns a new object based on this one with the specified period added. + * For example, on a {@code LocalDate}, this could be used to add a number of years, months or days. + * The returned object will have the same observable type as this object. + *

+ * In some cases, changing a field is not fully defined. For example, if the target object is + * a date representing the 31st January, then adding one month would be unclear. + * In cases like this, the field is responsible for resolving the result. Typically it will choose + * the previous valid date, which would be the last valid day of February in this example. + *

+ * If the implementation represents a date-time that has boundaries, such as {@code LocalTime}, + * then the permitted units must include the boundary unit, but no multiples of the boundary unit. + * For example, {@code LocalTime} must accept {@code DAYS} but not {@code WEEKS} or {@code MONTHS}. + * + *

Specification for implementors

+ * Implementations must check and handle all units defined in {@link ChronoUnit}. + * If the unit is supported, then the addition must be performed. + * If unsupported, then a {@code DateTimeException} must be thrown. + *

+ * If the unit is not a {@code ChronoUnit}, then the result of this method + * is obtained by invoking {@code TemporalUnit.doPlus(Temporal, long)} + * passing {@code this} as the first argument. + *

+ * Implementations must not alter either this object or the specified temporal object. + * Instead, an adjusted copy of the original must be returned. + * This provides equivalent, safe behavior for immutable and mutable implementations. + * + * @param amountToAdd the amount of the specified unit to add, may be negative + * @param unit the unit of the period to add, not null + * @return an object of the same type with the specified period added, not null + * @throws DateTimeException if the unit cannot be added + * @throws ArithmeticException if numeric overflow occurs + */ + Temporal plus(long amountToAdd, TemporalUnit unit); + + //----------------------------------------------------------------------- + /** + * Returns an object of the same type as this object with an amount subtracted. + *

+ * This adjusts this temporal, subtracting according to the rules of the specified subtractor. + * The subtractor is typically a {@link java.time.Period} but may be any other type implementing + * the {@link TemporalSubtractor} interface, such as {@link java.time.Duration}. + *

+ * Some example code indicating how and why this method is used: + *

+     *  date = date.minus(period);                      // subtract a Period instance
+     *  date = date.minus(duration);                    // subtract a Duration instance
+     *  date = date.minus(MONTHS.between(start, end));  // static import of MONTHS field
+     *  date = date.minus(workingDays(6));              // example user-written workingDays method
+     * 
+ *

+ * Note that calling {@code plus} followed by {@code minus} is not guaranteed to + * return the same date-time. + * + *

Specification for implementors

+ * Implementations must not alter either this object. + * Instead, an adjusted copy of the original must be returned. + * This provides equivalent, safe behavior for immutable and mutable implementations. + *

+ * The default implementation must behave equivalent to this code: + *

+     *  return subtractor.subtractFrom(this);
+     * 
+ * + * @param subtractor the subtractor to use, not null + * @return an object of the same type with the specified adjustment made, not null + * @throws DateTimeException if the subtraction cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + public default Temporal minus(TemporalSubtractor subtractor) { + return subtractor.subtractFrom(this); + } + + /** + * Returns an object of the same type as this object with the specified period subtracted. + *

+ * This method returns a new object based on this one with the specified period subtracted. + * For example, on a {@code LocalDate}, this could be used to subtract a number of years, months or days. + * The returned object will have the same observable type as this object. + *

+ * In some cases, changing a field is not fully defined. For example, if the target object is + * a date representing the 31st March, then subtracting one month would be unclear. + * In cases like this, the field is responsible for resolving the result. Typically it will choose + * the previous valid date, which would be the last valid day of February in this example. + *

+ * If the implementation represents a date-time that has boundaries, such as {@code LocalTime}, + * then the permitted units must include the boundary unit, but no multiples of the boundary unit. + * For example, {@code LocalTime} must accept {@code DAYS} but not {@code WEEKS} or {@code MONTHS}. + * + *

Specification for implementors

+ * Implementations must behave in a manor equivalent to the default method behavior. + *

+ * Implementations must not alter either this object or the specified temporal object. + * Instead, an adjusted copy of the original must be returned. + * This provides equivalent, safe behavior for immutable and mutable implementations. + *

+ * The default implementation must behave equivalent to this code: + *

+     *  return (amountToSubtract == Long.MIN_VALUE ?
+     *      plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit));
+     * 
+ * + * @param amountToSubtract the amount of the specified unit to subtract, may be negative + * @param unit the unit of the period to subtract, not null + * @return an object of the same type with the specified period subtracted, not null + * @throws DateTimeException if the unit cannot be subtracted + * @throws ArithmeticException if numeric overflow occurs + */ + public default Temporal minus(long amountToSubtract, TemporalUnit unit) { + return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit)); + } + + //----------------------------------------------------------------------- + /** + * Calculates the period between this temporal and another temporal in + * terms of the specified unit. + *

+ * This calculates the period between two temporals in terms of a single unit. + * The start and end points are {@code this} and the specified temporal. + * The result will be negative if the end is before the start. + * For example, the period in hours between two temporal objects can be + * calculated using {@code startTime.periodUntil(endTime, HOURS)}. + *

+ * The calculation returns a whole number, representing the number of + * complete units between the two temporals. + * For example, the period in hours between the times 11:30 and 13:29 + * will only be one hour as it is one minute short of two hours. + *

+ * This method operates in association with {@link TemporalUnit#between}. + * The result of this method is a {@code long} representing the amount of + * the specified unit. By contrast, the result of {@code between} is an + * object that can be used directly in addition/subtraction: + *

+     *   long period = start.periodUntil(end, HOURS);   // this method
+     *   dateTime.plus(HOURS.between(start, end));      // use in plus/minus
+     * 
+ * + *

Specification for implementors

+ * Implementations must begin by checking to ensure that the input temporal + * object is of the same observable type as the implementation. + * They must then perform the calculation for all instances of {@link ChronoUnit}. + * A {@code DateTimeException} must be thrown for {@code ChronoUnit} + * instances that are unsupported. + *

+ * If the unit is not a {@code ChronoUnit}, then the result of this method + * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)} + * passing {@code this} as the first argument and the input temporal as + * the second argument. + *

+ * In summary, implementations must behave in a manner equivalent to this code: + *

+     *  // check input temporal is the same type as this class
+     *  if (unit instanceof ChronoUnit) {
+     *    // if unit is supported, then calculate and return result
+     *    // else throw DateTimeException for unsupported units
+     *  }
+     *  return unit.between(this, endTime).getAmount();
+     * 
+ *

+ * The target object must not be altered by this method. + * + * @param endTemporal the end temporal, of the same type as this object, not null + * @param unit the unit to measure the period in, not null + * @return the amount of the period between this and the end + * @throws DateTimeException if the period cannot be calculated + * @throws ArithmeticException if numeric overflow occurs + */ + long periodUntil(Temporal endTemporal, TemporalUnit unit); + +} diff --git a/src/share/classes/java/time/temporal/TemporalAccessor.java b/src/share/classes/java/time/temporal/TemporalAccessor.java new file mode 100644 index 0000000000000000000000000000000000000000..2f88fa74c7a1b43fd559a9aca30cd381f8aae04e --- /dev/null +++ b/src/share/classes/java/time/temporal/TemporalAccessor.java @@ -0,0 +1,292 @@ +/* + * 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) 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 java.time.temporal; + +import java.time.DateTimeException; +import java.time.ZoneId; + +/** + * Framework-level interface defining read-only access to a temporal object, + * such as a date, time, offset or some combination of these. + *

+ * This is the base interface type for date, time and offset objects. + * It is implemented by those classes that can provide information + * as {@linkplain TemporalField fields} or {@linkplain TemporalQuery queries}. + *

+ * Most date and time information can be represented as a number. + * These are modeled using {@code TemporalField} with the number held using + * a {@code long} to handle large values. Year, month and day-of-month are + * simple examples of fields, but they also include instant and offsets. + * See {@link ChronoField} for the standard set of fields. + *

+ * Two pieces of date/time information cannot be represented by numbers, + * the {@linkplain Chrono chronology} and the {@linkplain ZoneId time-zone}. + * These can be accessed via {@link #query(TemporalQuery) queries} using + * the static methods defined on {@link Queries}. + *

+ * A sub-interface, {@link Temporal}, extends this definition to one that also + * supports adjustment and manipulation on more complete temporal objects. + *

+ * This interface is a framework-level interface that should not be widely + * used in application code. Instead, applications should create and pass + * around instances of concrete types, such as {@code LocalDate}. + * There are many reasons for this, part of which is that implementations + * of this interface may be in calendar systems other than ISO. + * See {@link ChronoLocalDate} for a fuller discussion of the issues. + * + *

Specification for implementors

+ * This interface places no restrictions on the mutability of implementations, + * however immutability is strongly recommended. + * + * @since 1.8 + */ +public interface TemporalAccessor { + + /** + * Checks if the specified field is supported. + *

+ * This checks if the date-time can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and {@link #get(TemporalField) get} + * methods will throw an exception. + * + *

Specification for implementors

+ * Implementations must check and handle all fields defined in {@link ChronoField}. + * If the field is supported, then true is returned, otherwise false + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + *

+ * Implementations must not alter either this object. + * + * @param field the field to check, null returns false + * @return true if this date-time can be queried for the field, false if not + */ + boolean isSupported(TemporalField field); + + /** + * Gets the range of valid values for the specified field. + *

+ * All fields can be expressed as a {@code long} integer. + * This method returns an object that describes the valid range for that value. + * The value of this temporal object is used to enhance the accuracy of the returned range. + * If the date-time cannot return the range, because the field is unsupported or for + * some other reason, an exception will be thrown. + *

+ * Note that the result only describes the minimum and maximum valid values + * and it is important not to read too much into them. For example, there + * could be values within the range that are invalid for the field. + * + *

Specification for implementors

+ * Implementations must check and handle all fields defined in {@link ChronoField}. + * If the field is supported, then the range of the field must be returned. + * If unsupported, then a {@code DateTimeException} must be thrown. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessorl)} + * passing {@code this} as the argument. + *

+ * Implementations must not alter either this object. + *

+ * The default implementation must behave equivalent to this code: + *

+     *  if (field instanceof ChronoField) {
+     *    if (isSupported(field)) {
+     *      return field.range();
+     *    }
+     *    throw new DateTimeException("Unsupported field: " + field.getName());
+     *  }
+     *  return field.doRange(this);
+     * 
+ * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + public default ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + if (isSupported(field)) { + return field.range(); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doRange(this); + } + + /** + * Gets the value of the specified field as an {@code int}. + *

+ * This queries the date-time for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If the date-time cannot return the value, because the field is unsupported or for + * some other reason, an exception will be thrown. + * + *

Specification for implementors

+ * Implementations must check and handle all fields defined in {@link ChronoField}. + * If the field is supported and has an {@code int} range, then the value of + * the field must be returned. + * If unsupported, then a {@code DateTimeException} must be thrown. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. + *

+ * Implementations must not alter either this object. + *

+ * The default implementation must behave equivalent to this code: + *

+     *  return range(field).checkValidIntValue(getLong(field), field);
+     * 
+ * + * @param field the field to get, not null + * @return the value for the field, within the valid range of values + * @throws DateTimeException if a value for the field cannot be obtained + * @throws DateTimeException if the range of valid values for the field exceeds an {@code int} + * @throws DateTimeException if the value is outside the range of valid values for the field + * @throws ArithmeticException if numeric overflow occurs + */ + public default int get(TemporalField field) { + return range(field).checkValidIntValue(getLong(field), field); + } + + /** + * Gets the value of the specified field as a {@code long}. + *

+ * This queries the date-time for the value for the specified field. + * The returned value may be outside the valid range of values for the field. + * If the date-time cannot return the value, because the field is unsupported or for + * some other reason, an exception will be thrown. + * + *

Specification for implementors

+ * Implementations must check and handle all fields defined in {@link ChronoField}. + * If the field is supported, then the value of the field must be returned. + * If unsupported, then a {@code DateTimeException} must be thrown. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. + *

+ * Implementations must not alter either this object. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + long getLong(TemporalField field); + + /** + * Queries this date-time. + *

+ * This queries this date-time using the specified query strategy object. + *

+ * Queries are a key tool for extracting information from date-times. + * They exists to externalize the process of querying, permitting different + * approaches, as per the strategy design pattern. + * Examples might be a query that checks if the date is the day before February 29th + * in a leap year, or calculates the number of days to your next birthday. + *

+ * The most common query implementations are method references, such as + * {@code LocalDate::from} and {@code ZoneId::from}. + * Further implementations are on {@link Queries}. + * Queries may also be defined by applications. + * + *

Specification for implementors

+ * The default implementation must behave equivalent to this code: + *
+     *  if (query == Queries.zoneId() || query == Queries.chrono() || query == Queries.precision()) {
+     *    return null;
+     *  }
+     *  return query.queryFrom(this);
+     * 
+ * Future versions are permitted to add further queries to the if statement. + *

+ * All classes implementing this interface and overriding this method must call + * {@code TemporalAccessor.super.query(query)}. JDK classes may avoid calling + * super if they provide behavior equivalent to the default behaviour, however + * non-JDK classes may not utilize this optimization and must call {@code super}. + *

+ * If the implementation can supply a value for one of the queries listed in the + * if statement of the default implementation, then it must do so. + * For example, an application-defined {@code HourMin} class storing the hour + * and minute must override this method as follows: + *

+     *  if (query == Queries.precision()) {
+     *    return MINUTES;
+     *  }
+     *  return TemporalAccessor.super.query(query);
+     * 
+ * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query + * @throws ArithmeticException if numeric overflow occurs + */ + public default R query(TemporalQuery query) { + if (query == Queries.zoneId() || query == Queries.chrono() || query == Queries.precision()) { + return null; + } + return query.queryFrom(this); + } + +} diff --git a/src/share/classes/java/time/temporal/TemporalAdder.java b/src/share/classes/java/time/temporal/TemporalAdder.java new file mode 100644 index 0000000000000000000000000000000000000000..fad2db42abbb5421df4c9698e8480e185a26cfee --- /dev/null +++ b/src/share/classes/java/time/temporal/TemporalAdder.java @@ -0,0 +1,139 @@ +/* + * 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) 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 java.time.temporal; + +import java.time.DateTimeException; +import java.time.Duration; +import java.time.Period; + +/** + * Strategy for adding to a temporal object. + *

+ * Adders are a key tool for modifying temporal objects. + * They exist to externalize the process of addition, permitting different + * approaches, as per the strategy design pattern. + *

+ * There are two equivalent ways of using a {@code TemporalAdder}. + * The first is to invoke the method on this interface directly. + * The second is to use {@link Temporal#plus(TemporalAdder)}: + *

+ *   // these two lines are equivalent, but the second approach is recommended
+ *   dateTime = adder.addTo(dateTime);
+ *   dateTime = dateTime.plus(adder);
+ * 
+ * It is recommended to use the second approach, {@code plus(TemporalAdder)}, + * as it is a lot clearer to read in code. + *

+ * The {@link Period} and {@link Duration} classes implement this interface. + * Adders may also be defined by applications. + * + *

Specification for implementors

+ * This interface places no restrictions on the mutability of implementations, + * however immutability is strongly recommended. + * + * @since 1.8 + */ +public interface TemporalAdder { + + /** + * Adds to the specified temporal object. + *

+ * This adds to the specified temporal object using the logic + * encapsulated in the implementing class. + *

+ * There are two equivalent ways of using this method. + * The first is to invoke this method directly. + * The second is to use {@link Temporal#plus(TemporalAdder)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   dateTime = adder.addTo(dateTime);
+     *   dateTime = dateTime.plus(adder);
+     * 
+ * It is recommended to use the second approach, {@code plus(TemporalAdder)}, + * as it is a lot clearer to read in code. + * + *

Specification for implementors

+ * The implementation must take the input object and add to it. + * The implementation defines the logic of the addition and is responsible for + * documenting that logic. It may use any method on {@code Temporal} to + * query the temporal object and perform the addition. + * The returned object must have the same observable type as the input object + *

+ * The input object must not be altered. + * Instead, an adjusted copy of the original must be returned. + * This provides equivalent, safe behavior for immutable and mutable temporal objects. + *

+ * The input temporal object may be in a calendar system other than ISO. + * Implementations may choose to document compatibility with other calendar systems, + * or reject non-ISO temporal objects by {@link Queries#chrono() querying the chronology}. + *

+ * This method may be called from multiple threads in parallel. + * It must be thread-safe when invoked. + * + * @param temporal the temporal object to adjust, not null + * @return an object of the same observable type with the addition made, not null + * @throws DateTimeException if unable to add + * @throws ArithmeticException if numeric overflow occurs + */ + Temporal addTo(Temporal temporal); + +} diff --git a/src/share/classes/java/time/temporal/TemporalAdjuster.java b/src/share/classes/java/time/temporal/TemporalAdjuster.java new file mode 100644 index 0000000000000000000000000000000000000000..4a6f03d287587704f42bae24298764b04f081d19 --- /dev/null +++ b/src/share/classes/java/time/temporal/TemporalAdjuster.java @@ -0,0 +1,142 @@ +/* + * 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) 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 java.time.temporal; + +import java.time.DateTimeException; + +/** + * Strategy for adjusting a temporal object. + *

+ * Adjusters are a key tool for modifying temporal objects. + * They exist to externalize the process of adjustment, permitting different + * approaches, as per the strategy design pattern. + * Examples might be an adjuster that sets the date avoiding weekends, or one that + * sets the date to the last day of the month. + *

+ * There are two equivalent ways of using a {@code TemporalAdjuster}. + * The first is to invoke the method on this interface directly. + * The second is to use {@link Temporal#with(TemporalAdjuster)}: + *

+ *   // these two lines are equivalent, but the second approach is recommended
+ *   temporal = thisAdjuster.adjustInto(temporal);
+ *   temporal = temporal.with(thisAdjuster);
+ * 
+ * It is recommended to use the second approach, {@code with(TemporalAdjuster)}, + * as it is a lot clearer to read in code. + *

+ * See {@link Adjusters} for a standard set of adjusters, including finding the + * last day of the month. + * Adjusters may also be defined by applications. + * + *

Specification for implementors

+ * This interface places no restrictions on the mutability of implementations, + * however immutability is strongly recommended. + * + * @since 1.8 + */ +public interface TemporalAdjuster { + + /** + * Adjusts the specified temporal object. + *

+ * This adjusts the specified temporal object using the logic + * encapsulated in the implementing class. + * Examples might be an adjuster that sets the date avoiding weekends, or one that + * sets the date to the last day of the month. + *

+ * There are two equivalent ways of using this method. + * The first is to invoke this method directly. + * The second is to use {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisAdjuster.adjustInto(temporal);
+     *   temporal = temporal.with(thisAdjuster);
+     * 
+ * It is recommended to use the second approach, {@code with(TemporalAdjuster)}, + * as it is a lot clearer to read in code. + * + *

Specification for implementors

+ * The implementation must take the input object and adjust it. + * The implementation defines the logic of the adjustment and is responsible for + * documenting that logic. It may use any method on {@code Temporal} to + * query the temporal object and perform the adjustment. + * The returned object must have the same observable type as the input object + *

+ * The input object must not be altered. + * Instead, an adjusted copy of the original must be returned. + * This provides equivalent, safe behavior for immutable and mutable temporal objects. + *

+ * The input temporal object may be in a calendar system other than ISO. + * Implementations may choose to document compatibility with other calendar systems, + * or reject non-ISO temporal objects by {@link Queries#chrono() querying the chronology}. + *

+ * This method may be called from multiple threads in parallel. + * It must be thread-safe when invoked. + * + * @param temporal the temporal object to adjust, not null + * @return an object of the same observable type with the adjustment made, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + Temporal adjustInto(Temporal temporal); + +} diff --git a/src/share/classes/java/time/temporal/TemporalField.java b/src/share/classes/java/time/temporal/TemporalField.java new file mode 100644 index 0000000000000000000000000000000000000000..187d9e9b0497b475542fde7b7535e5be274de1f9 --- /dev/null +++ b/src/share/classes/java/time/temporal/TemporalField.java @@ -0,0 +1,305 @@ +/* + * 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) 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 java.time.temporal; + +import java.time.DateTimeException; +import java.time.format.DateTimeBuilder; +import java.util.Comparator; + +/** + * A field of date-time, such as month-of-year or hour-of-minute. + *

+ * Date and time is expressed using fields which partition the time-line into something + * meaningful for humans. Implementations of this interface represent those fields. + *

+ * The most commonly used units are defined in {@link ChronoField}. + * Further fields are supplied in {@link ISOFields}, {@link WeekFields} and {@link JulianFields}. + * Fields can also be written by application code by implementing this interface. + *

+ * The field works using double dispatch. Client code calls methods on a date-time like + * {@code LocalDateTime} which check if the field is a {@code ChronoField}. + * If it is, then the date-time must handle it. + * Otherwise, the method call is re-dispatched to the matching method in this interface. + * + *

Specification for implementors

+ * This interface must be implemented with care to ensure other classes operate correctly. + * All implementations that can be instantiated must be final, immutable and thread-safe. + * It is recommended to use an enum where possible. + * + * @since 1.8 + */ +public interface TemporalField extends Comparator { + + /** + * Gets a descriptive name for the field. + *

+ * The should be of the format 'BaseOfRange', such as 'MonthOfYear', + * unless the field has a range of {@code FOREVER}, when only + * the base unit is mentioned, such as 'Year' or 'Era'. + * + * @return the name, not null + */ + String getName(); + + /** + * Gets the unit that the field is measured in. + *

+ * The unit of the field is the period that varies within the range. + * For example, in the field 'MonthOfYear', the unit is 'Months'. + * See also {@link #getRangeUnit()}. + * + * @return the period unit defining the base unit of the field, not null + */ + TemporalUnit getBaseUnit(); + + /** + * Gets the range that the field is bound by. + *

+ * The range of the field is the period that the field varies within. + * For example, in the field 'MonthOfYear', the range is 'Years'. + * See also {@link #getBaseUnit()}. + *

+ * The range is never null. For example, the 'Year' field is shorthand for + * 'YearOfForever'. It therefore has a unit of 'Years' and a range of 'Forever'. + * + * @return the period unit defining the range of the field, not null + */ + TemporalUnit getRangeUnit(); + + //----------------------------------------------------------------------- + /** + * Compares the value of this field in two temporal objects. + *

+ * All fields implement {@link Comparator} on {@link TemporalAccessor}. + * This allows a list of date-times to be compared using the value of a field. + * For example, you could sort a list of arbitrary temporal objects by the value of + * the month-of-year field - {@code Collections.sort(list, MONTH_OF_YEAR)} + *

+ * The default implementation must behave equivalent to this code: + *

+     *  return Long.compare(temporal1.getLong(this), temporal2.getLong(this));
+     * 
+ * + * @param temporal1 the first temporal object to compare, not null + * @param temporal2 the second temporal object to compare, not null + * @throws DateTimeException if unable to obtain the value for this field + */ + public default int compare(TemporalAccessor temporal1, TemporalAccessor temporal2) { + return Long.compare(temporal1.getLong(this), temporal2.getLong(this)); + } + + /** + * Gets the range of valid values for the field. + *

+ * All fields can be expressed as a {@code long} integer. + * This method returns an object that describes the valid range for that value. + * This method is generally only applicable to the ISO-8601 calendar system. + *

+ * Note that the result only describes the minimum and maximum valid values + * and it is important not to read too much into them. For example, there + * could be values within the range that are invalid for the field. + * + * @return the range of valid values for the field, not null + */ + ValueRange range(); + + //----------------------------------------------------------------------- + /** + * Checks if this field is supported by the temporal object. + *

+ * This determines whether the temporal accessor supports this field. + * If this returns false, the the temporal cannot be queried for this field. + *

+ * There are two equivalent ways of using this method. + * The first is to invoke this method directly. + * The second is to use {@link TemporalAccessor#isSupported(TemporalField)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisField.doIsSupported(temporal);
+     *   temporal = temporal.isSupported(thisField);
+     * 
+ * It is recommended to use the second approach, {@code isSupported(TemporalField)}, + * as it is a lot clearer to read in code. + *

+ * Implementations should determine whether they are supported using the fields + * available in {@link ChronoField}. + * + * @param temporal the temporal object to query, not null + * @return true if the date-time can be queried for this field, false if not + */ + boolean doIsSupported(TemporalAccessor temporal); + + /** + * Get the range of valid values for this field using the temporal object to + * refine the result. + *

+ * This uses the temporal object to find the range of valid values for the field. + * This is similar to {@link #range()}, however this method refines the result + * using the temporal. For example, if the field is {@code DAY_OF_MONTH} the + * {@code range} method is not accurate as there are four possible month lengths, + * 28, 29, 30 and 31 days. Using this method with a date allows the range to be + * accurate, returning just one of those four options. + *

+ * There are two equivalent ways of using this method. + * The first is to invoke this method directly. + * The second is to use {@link TemporalAccessor#range(TemporalField)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisField.doRange(temporal);
+     *   temporal = temporal.range(thisField);
+     * 
+ * It is recommended to use the second approach, {@code range(TemporalField)}, + * as it is a lot clearer to read in code. + *

+ * Implementations should perform any queries or calculations using the fields + * available in {@link ChronoField}. + * If the field is not supported a {@code DateTimeException} must be thrown. + * + * @param temporal the temporal object used to refine the result, not null + * @return the range of valid values for this field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + ValueRange doRange(TemporalAccessor temporal); + + /** + * Gets the value of this field from the specified temporal object. + *

+ * This queries the temporal object for the value of this field. + *

+ * There are two equivalent ways of using this method. + * The first is to invoke this method directly. + * The second is to use {@link TemporalAccessor#getLong(TemporalField)} + * (or {@link TemporalAccessor#get(TemporalField)}): + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisField.doGet(temporal);
+     *   temporal = temporal.getLong(thisField);
+     * 
+ * It is recommended to use the second approach, {@code getLong(TemporalField)}, + * as it is a lot clearer to read in code. + *

+ * Implementations should perform any queries or calculations using the fields + * available in {@link ChronoField}. + * If the field is not supported a {@code DateTimeException} must be thrown. + * + * @param temporal the temporal object to query, not null + * @return the value of this field, not null + * @throws DateTimeException if a value for the field cannot be obtained + */ + long doGet(TemporalAccessor temporal); + + /** + * Returns a copy of the specified temporal object with the value of this field set. + *

+ * This returns a new temporal object based on the specified one with the value for + * this field changed. For example, on a {@code LocalDate}, this could be used to + * set the year, month or day-of-month. + * The returned object has the same observable type as the specified object. + *

+ * In some cases, changing a field is not fully defined. For example, if the target object is + * a date representing the 31st January, then changing the month to February would be unclear. + * In cases like this, the implementation is responsible for resolving the result. + * Typically it will choose the previous valid date, which would be the last valid + * day of February in this example. + *

+ * There are two equivalent ways of using this method. + * The first is to invoke this method directly. + * The second is to use {@link Temporal#with(TemporalField, long)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisField.doWith(temporal);
+     *   temporal = temporal.with(thisField);
+     * 
+ * It is recommended to use the second approach, {@code with(TemporalField)}, + * as it is a lot clearer to read in code. + *

+ * Implementations should perform any queries or calculations using the fields + * available in {@link ChronoField}. + * If the field is not supported a {@code DateTimeException} must be thrown. + *

+ * Implementations must not alter the specified temporal object. + * Instead, an adjusted copy of the original must be returned. + * This provides equivalent, safe behavior for immutable and mutable implementations. + * + * @param the type of the Temporal object + * @param temporal the temporal object to adjust, not null + * @param newValue the new value of the field + * @return the adjusted temporal object, not null + * @throws DateTimeException if the field cannot be set + */ + R doWith(R temporal, long newValue); + + /** + * Resolves the date/time information in the builder + *

+ * This method is invoked during the resolve of the builder. + * Implementations should combine the associated field with others to form + * objects like {@code LocalDate}, {@code LocalTime} and {@code LocalDateTime} + * + * @param builder the builder to resolve, not null + * @param value the value of the associated field + * @return true if builder has been changed, false otherwise + * @throws DateTimeException if unable to resolve + */ + boolean resolve(DateTimeBuilder builder, long value); + +} diff --git a/src/share/classes/java/time/temporal/TemporalQuery.java b/src/share/classes/java/time/temporal/TemporalQuery.java new file mode 100644 index 0000000000000000000000000000000000000000..a31964567b4181fc240f8ebd59e6a470f7d85319 --- /dev/null +++ b/src/share/classes/java/time/temporal/TemporalQuery.java @@ -0,0 +1,143 @@ +/* + * 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) 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 java.time.temporal; + +import java.time.DateTimeException; + +/** + * Strategy for querying a temporal object. + *

+ * Queries are a key tool for extracting information from temporal objects. + * They exist to externalize the process of querying, permitting different + * approaches, as per the strategy design pattern. + * Examples might be a query that checks if the date is the day before February 29th + * in a leap year, or calculates the number of days to your next birthday. + *

+ * The {@link TemporalField} interface provides another mechanism for querying + * temporal objects. That interface is limited to returning a {@code long}. + * By contrast, queries can return any type. + *

+ * There are two equivalent ways of using a {@code TemporalQuery}. + * The first is to invoke the method on this interface directly. + * The second is to use {@link TemporalAccessor#query(TemporalQuery)}: + *

+ *   // these two lines are equivalent, but the second approach is recommended
+ *   temporal = thisQuery.queryFrom(temporal);
+ *   temporal = temporal.query(thisQuery);
+ * 
+ * It is recommended to use the second approach, {@code query(TemporalQuery)}, + * as it is a lot clearer to read in code. + *

+ * The most common implementations are method references, such as + * {@code LocalDate::from} and {@code ZoneId::from}. + * Further implementations are on {@link Queries}. + * Queries may also be defined by applications. + * + *

Specification for implementors

+ * This interface places no restrictions on the mutability of implementations, + * however immutability is strongly recommended. + * + * @since 1.8 + */ +public interface TemporalQuery { + + /** + * Queries the specified temporal object. + *

+ * This queries the specified temporal object to return an object using the logic + * encapsulated in the implementing class. + * Examples might be a query that checks if the date is the day before February 29th + * in a leap year, or calculates the number of days to your next birthday. + *

+ * There are two equivalent ways of using this method. + * The first is to invoke this method directly. + * The second is to use {@link TemporalAccessor#query(TemporalQuery)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisQuery.queryFrom(temporal);
+     *   temporal = temporal.query(thisQuery);
+     * 
+ * It is recommended to use the second approach, {@code query(TemporalQuery)}, + * as it is a lot clearer to read in code. + * + *

Specification for implementors

+ * The implementation must take the input object and query it. + * The implementation defines the logic of the query and is responsible for + * documenting that logic. + * It may use any method on {@code TemporalAccessor} to determine the result. + * The input object must not be altered. + *

+ * The input temporal object may be in a calendar system other than ISO. + * Implementations may choose to document compatibility with other calendar systems, + * or reject non-ISO temporal objects by {@link Queries#chrono() querying the chronology}. + *

+ * This method may be called from multiple threads in parallel. + * It must be thread-safe when invoked. + * + * @param temporal the temporal object to query, not null + * @return the queried value, may return null to indicate not found + * @throws DateTimeException if unable to query + * @throws ArithmeticException if numeric overflow occurs + */ + R queryFrom(TemporalAccessor temporal); + +} diff --git a/src/share/classes/java/time/temporal/TemporalSubtractor.java b/src/share/classes/java/time/temporal/TemporalSubtractor.java new file mode 100644 index 0000000000000000000000000000000000000000..4da8f30bea42af2893625eb139402e96ea052279 --- /dev/null +++ b/src/share/classes/java/time/temporal/TemporalSubtractor.java @@ -0,0 +1,139 @@ +/* + * 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) 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 java.time.temporal; + +import java.time.DateTimeException; +import java.time.Duration; +import java.time.Period; + +/** + * Strategy for subtracting from a temporal object. + *

+ * Subtractors are a key tool for modifying temporal objects. + * They exist to externalize the process of subtraction, permitting different + * approaches, as per the strategy design pattern. + *

+ * There are two equivalent ways of using a {@code TemporalSubtractor}. + * The first is to invoke the method on this interface directly. + * The second is to use {@link Temporal#minus(TemporalSubtractor)}: + *

+ *   // these two lines are equivalent, but the second approach is recommended
+ *   dateTime = subtractor.subtractFrom(dateTime);
+ *   dateTime = dateTime.minus(subtractor);
+ * 
+ * It is recommended to use the second approach, {@code minus(TemporalSubtractor)}, + * as it is a lot clearer to read in code. + *

+ * The {@link Period} and {@link Duration} classes implement this interface. + * Subtractors may also be defined by applications. + * + *

Specification for implementors

+ * This interface places no restrictions on the mutability of implementations, + * however immutability is strongly recommended. + * + * @since 1.8 + */ +public interface TemporalSubtractor { + + /** + * Subtracts this object from the specified temporal object. + *

+ * This adds to the specified temporal object using the logic + * encapsulated in the implementing class. + *

+ * There are two equivalent ways of using this method. + * The first is to invoke this method directly. + * The second is to use {@link Temporal#minus(TemporalSubtractor)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   dateTime = subtractor.subtractFrom(dateTime);
+     *   dateTime = dateTime.minus(subtractor);
+     * 
+ * It is recommended to use the second approach, {@code minus(TemporalSubtractor)}, + * as it is a lot clearer to read in code. + * + *

Specification for implementors

+ * The implementation must take the input object and subtract from it. + * The implementation defines the logic of the subtraction and is responsible for + * documenting that logic. It may use any method on {@code Temporal} to + * query the temporal object and perform the subtraction. + * The returned object must have the same observable type as the input object + *

+ * The input object must not be altered. + * Instead, an adjusted copy of the original must be returned. + * This provides equivalent, safe behavior for immutable and mutable temporal objects. + *

+ * The input temporal object may be in a calendar system other than ISO. + * Implementations may choose to document compatibility with other calendar systems, + * or reject non-ISO temporal objects by {@link Queries#chrono() querying the chronology}. + *

+ * This method may be called from multiple threads in parallel. + * It must be thread-safe when invoked. + * + * @param temporal the temporal object to adjust, not null + * @return an object of the same observable type with the subtraction made, not null + * @throws DateTimeException if unable to subtract + * @throws ArithmeticException if numeric overflow occurs + */ + Temporal subtractFrom(Temporal temporal); + +} diff --git a/src/share/classes/java/time/temporal/TemporalUnit.java b/src/share/classes/java/time/temporal/TemporalUnit.java new file mode 100644 index 0000000000000000000000000000000000000000..7c8a32b4a3d92b0874a9ff85f05737ed6b03ce2c --- /dev/null +++ b/src/share/classes/java/time/temporal/TemporalUnit.java @@ -0,0 +1,226 @@ +/* + * 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) 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 java.time.temporal; + +import java.time.DateTimeException; +import java.time.Duration; +import java.time.Period; + +/** + * A unit of date-time, such as Days or Hours. + *

+ * Measurement of time is built on units, such as years, months, days, hours, minutes and seconds. + * Implementations of this interface represent those units. + *

+ * An instance of this interface represents the unit itself, rather than an amount of the unit. + * See {@link Period} for a class that represents an amount in terms of the common units. + *

+ * The most commonly used units are defined in {@link ChronoUnit}. + * Further units are supplied in {@link ISOFields}. + * Units can also be written by application code by implementing this interface. + *

+ * The unit works using double dispatch. Client code calls methods on a date-time like + * {@code LocalDateTime} which check if the unit is a {@code ChronoUnit}. + * If it is, then the date-time must handle it. + * Otherwise, the method call is re-dispatched to the matching method in this interface. + * + *

Specification for implementors

+ * This interface must be implemented with care to ensure other classes operate correctly. + * All implementations that can be instantiated must be final, immutable and thread-safe. + * It is recommended to use an enum where possible. + * + * @since 1.8 + */ +public interface TemporalUnit { + + /** + * Gets a descriptive name for the unit. + *

+ * This should be in the plural and upper-first camel case, such as 'Days' or 'Minutes'. + * + * @return the name, not null + */ + String getName(); + + /** + * Gets the duration of this unit, which may be an estimate. + *

+ * All units return a duration measured in standard nanoseconds from this method. + * For example, an hour has a duration of {@code 60 * 60 * 1,000,000,000ns}. + *

+ * Some units may return an accurate duration while others return an estimate. + * For example, days have an estimated duration due to the possibility of + * daylight saving time changes. + * To determine if the duration is an estimate, use {@link #isDurationEstimated()}. + * + * @return the duration of this unit, which may be an estimate, not null + */ + Duration getDuration(); + + /** + * Checks if the duration of the unit is an estimate. + *

+ * All units have a duration, however the duration is not always accurate. + * For example, days have an estimated duration due to the possibility of + * daylight saving time changes. + * This method returns true if the duration is an estimate and false if it is + * accurate. Note that accurate/estimated ignores leap seconds. + * + * @return true if the duration is estimated, false if accurate + */ + boolean isDurationEstimated(); + + //----------------------------------------------------------------------- + /** + * Checks if this unit is supported by the specified temporal object. + *

+ * This checks that the implementing date-time can add/subtract this unit. + * This can be used to avoid throwing an exception. + *

+ * This default implementation derives the value using + * {@link Temporal#plus(long, TemporalUnit)}. + * + * @param temporal the temporal object to check, not null + * @return true if the unit is supported + */ + public default boolean isSupported(Temporal temporal) { + try { + temporal.plus(1, this); + return true; + } catch (RuntimeException ex) { + try { + temporal.plus(-1, this); + return true; + } catch (RuntimeException ex2) { + return false; + } + } + } + + /** + * Returns a copy of the specified temporal object with the specified period added. + *

+ * The period added is a multiple of this unit. For example, this method + * could be used to add "3 days" to a date by calling this method on the + * instance representing "days", passing the date and the period "3". + * The period to be added may be negative, which is equivalent to subtraction. + *

+ * There are two equivalent ways of using this method. + * The first is to invoke this method directly. + * The second is to use {@link Temporal#plus(long, TemporalUnit)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisUnit.doPlus(temporal);
+     *   temporal = temporal.plus(thisUnit);
+     * 
+ * It is recommended to use the second approach, {@code plus(TemporalUnit)}, + * as it is a lot clearer to read in code. + *

+ * Implementations should perform any queries or calculations using the units + * available in {@link ChronoUnit} or the fields available in {@link ChronoField}. + * If the field is not supported a {@code DateTimeException} must be thrown. + *

+ * Implementations must not alter the specified temporal object. + * Instead, an adjusted copy of the original must be returned. + * This provides equivalent, safe behavior for immutable and mutable implementations. + * + * @param the type of the Temporal object + * @param dateTime the temporal object to adjust, not null + * @param periodToAdd the period of this unit to add, positive or negative + * @return the adjusted temporal object, not null + * @throws DateTimeException if the period cannot be added + */ + R doPlus(R dateTime, long periodToAdd); + + //----------------------------------------------------------------------- + /** + * Calculates the period in terms of this unit between two temporal objects of the same type. + *

+ * The period will be positive if the second date-time is after the first, and + * negative if the second date-time is before the first. + * Call {@link SimplePeriod#abs() abs()} on the result to ensure that the result + * is always positive. + *

+ * The result can be queried for the {@link SimplePeriod#getAmount() amount}, the + * {@link SimplePeriod#getUnit() unit} and used directly in addition/subtraction: + *

+     *  date = date.minus(MONTHS.between(start, end));
+     * 
+ * + * @param the type of the Temporal object; the two date-times must be of the same type + * @param dateTime1 the base temporal object, not null + * @param dateTime2 the other temporal object, not null + * @return the period between datetime1 and datetime2 in terms of this unit; + * positive if datetime2 is later than datetime1, not null + */ + SimplePeriod between(R dateTime1, R dateTime2); + + //----------------------------------------------------------------------- + /** + * Outputs this unit as a {@code String} using the name. + * + * @return the name of this unit, not null + */ + @Override + String toString(); + +} diff --git a/src/share/classes/java/time/temporal/ValueRange.java b/src/share/classes/java/time/temporal/ValueRange.java new file mode 100644 index 0000000000000000000000000000000000000000..cd47f256d4d20a00524b20314d18ae0aca2ccc05 --- /dev/null +++ b/src/share/classes/java/time/temporal/ValueRange.java @@ -0,0 +1,396 @@ +/* + * 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) 2011-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 java.time.temporal; + +import java.io.Serializable; +import java.time.DateTimeException; + +/** + * The range of valid values for a date-time field. + *

+ * All {@link TemporalField} instances have a valid range of values. + * For example, the ISO day-of-month runs from 1 to somewhere between 28 and 31. + * This class captures that valid range. + *

+ * It is important to be aware of the limitations of this class. + * Only the minimum and maximum values are provided. + * It is possible for there to be invalid values within the outer range. + * For example, a weird field may have valid values of 1, 2, 4, 6, 7, thus + * have a range of '1 - 7', despite that fact that values 3 and 5 are invalid. + *

+ * Instances of this class are not tied to a specific field. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class ValueRange implements Serializable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = -7317881728594519368L; + + /** + * The smallest minimum value. + */ + private final long minSmallest; + /** + * The largest minimum value. + */ + private final long minLargest; + /** + * The smallest maximum value. + */ + private final long maxSmallest; + /** + * The largest maximum value. + */ + private final long maxLargest; + + /** + * Obtains a fixed value range. + *

+ * This factory obtains a range where the minimum and maximum values are fixed. + * For example, the ISO month-of-year always runs from 1 to 12. + * + * @param min the minimum value + * @param max the maximum value + * @return the ValueRange for min, max, not null + * @throws IllegalArgumentException if the minimum is greater than the maximum + */ + public static ValueRange of(long min, long max) { + if (min > max) { + throw new IllegalArgumentException("Minimum value must be less than maximum value"); + } + return new ValueRange(min, min, max, max); + } + + /** + * Obtains a variable value range. + *

+ * This factory obtains a range where the minimum value is fixed and the maximum value may vary. + * For example, the ISO day-of-month always starts at 1, but ends between 28 and 31. + * + * @param min the minimum value + * @param maxSmallest the smallest maximum value + * @param maxLargest the largest maximum value + * @return the ValueRange for min, smallest max, largest max, not null + * @throws IllegalArgumentException if + * the minimum is greater than the smallest maximum, + * or the smallest maximum is greater than the largest maximum + */ + public static ValueRange of(long min, long maxSmallest, long maxLargest) { + return of(min, min, maxSmallest, maxLargest); + } + + /** + * Obtains a fully variable value range. + *

+ * This factory obtains a range where both the minimum and maximum value may vary. + * + * @param minSmallest the smallest minimum value + * @param minLargest the largest minimum value + * @param maxSmallest the smallest maximum value + * @param maxLargest the largest maximum value + * @return the ValueRange for smallest min, largest min, smallest max, largest max, not null + * @throws IllegalArgumentException if + * the smallest minimum is greater than the smallest maximum, + * or the smallest maximum is greater than the largest maximum + * or the largest minimum is greater than the largest maximum + */ + public static ValueRange of(long minSmallest, long minLargest, long maxSmallest, long maxLargest) { + if (minSmallest > minLargest) { + throw new IllegalArgumentException("Smallest minimum value must be less than largest minimum value"); + } + if (maxSmallest > maxLargest) { + throw new IllegalArgumentException("Smallest maximum value must be less than largest maximum value"); + } + if (minLargest > maxLargest) { + throw new IllegalArgumentException("Minimum value must be less than maximum value"); + } + return new ValueRange(minSmallest, minLargest, maxSmallest, maxLargest); + } + + /** + * Restrictive constructor. + * + * @param minSmallest the smallest minimum value + * @param minLargest the largest minimum value + * @param maxSmallest the smallest minimum value + * @param maxLargest the largest minimum value + */ + private ValueRange(long minSmallest, long minLargest, long maxSmallest, long maxLargest) { + this.minSmallest = minSmallest; + this.minLargest = minLargest; + this.maxSmallest = maxSmallest; + this.maxLargest = maxLargest; + } + + //----------------------------------------------------------------------- + /** + * Is the value range fixed and fully known. + *

+ * For example, the ISO day-of-month runs from 1 to between 28 and 31. + * Since there is uncertainty about the maximum value, the range is not fixed. + * However, for the month of January, the range is always 1 to 31, thus it is fixed. + * + * @return true if the set of values is fixed + */ + public boolean isFixed() { + return minSmallest == minLargest && maxSmallest == maxLargest; + } + + //----------------------------------------------------------------------- + /** + * Gets the minimum value that the field can take. + *

+ * For example, the ISO day-of-month always starts at 1. + * The minimum is therefore 1. + * + * @return the minimum value for this field + */ + public long getMinimum() { + return minSmallest; + } + + /** + * Gets the largest possible minimum value that the field can take. + *

+ * For example, the ISO day-of-month always starts at 1. + * The largest minimum is therefore 1. + * + * @return the largest possible minimum value for this field + */ + public long getLargestMinimum() { + return minLargest; + } + + /** + * Gets the smallest possible maximum value that the field can take. + *

+ * For example, the ISO day-of-month runs to between 28 and 31 days. + * The smallest maximum is therefore 28. + * + * @return the smallest possible maximum value for this field + */ + public long getSmallestMaximum() { + return maxSmallest; + } + + /** + * Gets the maximum value that the field can take. + *

+ * For example, the ISO day-of-month runs to between 28 and 31 days. + * The maximum is therefore 31. + * + * @return the maximum value for this field + */ + public long getMaximum() { + return maxLargest; + } + + //----------------------------------------------------------------------- + /** + * Checks if all values in the range fit in an {@code int}. + *

+ * This checks that all valid values are within the bounds of an {@code int}. + *

+ * For example, the ISO month-of-year has values from 1 to 12, which fits in an {@code int}. + * By comparison, ISO nano-of-day runs from 1 to 86,400,000,000,000 which does not fit in an {@code int}. + *

+ * This implementation uses {@link #getMinimum()} and {@link #getMaximum()}. + * + * @return true if a valid value always fits in an {@code int} + */ + public boolean isIntValue() { + return getMinimum() >= Integer.MIN_VALUE && getMaximum() <= Integer.MAX_VALUE; + } + + /** + * Checks if the value is within the valid range. + *

+ * This checks that the value is within the stored range of values. + * + * @param value the value to check + * @return true if the value is valid + */ + public boolean isValidValue(long value) { + return (value >= getMinimum() && value <= getMaximum()); + } + + /** + * Checks if the value is within the valid range and that all values + * in the range fit in an {@code int}. + *

+ * This method combines {@link #isIntValue()} and {@link #isValidValue(long)}. + * + * @param value the value to check + * @return true if the value is valid and fits in an {@code int} + */ + public boolean isValidIntValue(long value) { + return isIntValue() && isValidValue(value); + } + + /** + * Checks that the specified value is valid. + *

+ * This validates that the value is within the valid range of values. + * The field is only used to improve the error message. + * + * @param value the value to check + * @param field the field being checked, may be null + * @return the value that was passed in + * @see #isValidValue(long) + */ + public long checkValidValue(long value, TemporalField field) { + if (isValidValue(value) == false) { + if (field != null) { + throw new DateTimeException("Invalid value for " + field.getName() + " (valid values " + this + "): " + value); + } else { + throw new DateTimeException("Invalid value (valid values " + this + "): " + value); + } + } + return value; + } + + /** + * Checks that the specified value is valid and fits in an {@code int}. + *

+ * This validates that the value is within the valid range of values and that + * all valid values are within the bounds of an {@code int}. + * The field is only used to improve the error message. + * + * @param value the value to check + * @param field the field being checked, may be null + * @return the value that was passed in + * @see #isValidIntValue(long) + */ + public int checkValidIntValue(long value, TemporalField field) { + if (isValidIntValue(value) == false) { + throw new DateTimeException("Invalid int value for " + field.getName() + ": " + value); + } + return (int) value; + } + + //----------------------------------------------------------------------- + /** + * Checks if this range is equal to another range. + *

+ * The comparison is based on the four values, minimum, largest minimum, + * smallest maximum and maximum. + * Only objects of type {@code ValueRange} are compared, other types return false. + * + * @param obj the object to check, null returns false + * @return true if this is equal to the other range + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof ValueRange) { + ValueRange other = (ValueRange) obj; + return minSmallest == other.minSmallest && minLargest == other.minLargest && + maxSmallest == other.maxSmallest && maxLargest == other.maxLargest; + } + return false; + } + + /** + * A hash code for this range. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + long hash = minSmallest + minLargest << 16 + minLargest >> 48 + maxSmallest << 32 + + maxSmallest >> 32 + maxLargest << 48 + maxLargest >> 16; + return (int) (hash ^ (hash >>> 32)); + } + + //----------------------------------------------------------------------- + /** + * Outputs this range as a {@code String}. + *

+ * The format will be '{min}/{largestMin} - {smallestMax}/{max}', + * where the largestMin or smallestMax sections may be omitted, together + * with associated slash, if they are the same as the min or max. + * + * @return a string representation of this range, not null + */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(minSmallest); + if (minSmallest != minLargest) { + buf.append('/').append(minLargest); + } + buf.append(" - ").append(maxSmallest); + if (maxSmallest != maxLargest) { + buf.append('/').append(maxLargest); + } + return buf.toString(); + } + +} diff --git a/src/share/classes/java/time/temporal/WeekFields.java b/src/share/classes/java/time/temporal/WeekFields.java new file mode 100644 index 0000000000000000000000000000000000000000..364aa91798eec67dda2a59ef2d0469d143c019eb --- /dev/null +++ b/src/share/classes/java/time/temporal/WeekFields.java @@ -0,0 +1,663 @@ +/* + * 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) 2011-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 java.time.temporal; + +import java.io.InvalidObjectException; +import java.io.Serializable; +import java.time.DayOfWeek; +import java.time.format.DateTimeBuilder; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Localized definitions of the day-of-week, week-of-month and week-of-year fields. + *

+ * A standard week is seven days long, but cultures have different definitions for some + * other aspects of a week. This class represents the definition of the week, for the + * purpose of providing {@link TemporalField} instances. + *

+ * WeekFields provides three fields, + * {@link #dayOfWeek()}, {@link #weekOfMonth()}, and {@link #weekOfYear()} + * that provide access to the values from any {@linkplain Temporal temporal object}. + *

+ * The computations for day-of-week, week-of-month, and week-of-year are based + * on the {@linkplain ChronoField#YEAR proleptic-year}, + * {@linkplain ChronoField#MONTH_OF_YEAR month-of-year}, + * {@linkplain ChronoField#DAY_OF_MONTH day-of-month}, and + * {@linkplain ChronoField#DAY_OF_WEEK ISO day-of-week} which are based on the + * {@linkplain ChronoField#EPOCH_DAY epoch-day} and the chronology. + * The values may not be aligned with the {@linkplain ChronoField#YEAR_OF_ERA year-of-Era} + * depending on the Chronology. + *

A week is defined by: + *

    + *
  • The first day-of-week. + * For example, the ISO-8601 standard considers Monday to be the first day-of-week. + *
  • The minimal number of days in the first week. + * For example, the ISO-08601 standard counts the first week as needing at least 4 days. + *

+ * Together these two values allow a year or month to be divided into weeks. + *

+ *

Week of Month

+ * One field is used: week-of-month. + * The calculation ensures that weeks never overlap a month boundary. + * The month is divided into periods where each period starts on the defined first day-of-week. + * The earliest period is referred to as week 0 if it has less than the minimal number of days + * and week 1 if it has at least the minimal number of days. + *

+ * + * + * + * + * + * + * + * + * + * + * + * + *
Examples of WeekFields
DateDay-of-weekFirst day: Monday
Minimal days: 4
First day: Monday
Minimal days: 5
2008-12-31WednesdayWeek 5 of December 2008Week 5 of December 2008
2009-01-01ThursdayWeek 1 of January 2009Week 0 of January 2009
2009-01-04SundayWeek 1 of January 2009Week 0 of January 2009
2009-01-05MondayWeek 2 of January 2009Week 1 of January 2009
+ *

+ *

Week of Year

+ * One field is used: week-of-year. + * The calculation ensures that weeks never overlap a year boundary. + * The year is divided into periods where each period starts on the defined first day-of-week. + * The earliest period is referred to as week 0 if it has less than the minimal number of days + * and week 1 if it has at least the minimal number of days. + *

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class WeekFields implements Serializable { + // implementation notes + // querying week-of-month or week-of-year should return the week value bound within the month/year + // however, setting the week value should be lenient (use plus/minus weeks) + // allow week-of-month outer range [0 to 5] + // allow week-of-year outer range [0 to 53] + // this is because callers shouldn't be expected to know the details of validity + + /** + * The cache of rules by firstDayOfWeek plus minimalDays. + * Initialized first to be available for definition of ISO, etc. + */ + private static final ConcurrentMap CACHE = new ConcurrentHashMap<>(4, 0.75f, 2); + + /** + * The ISO-8601 definition, where a week starts on Monday and the first week + * has a minimum of 4 days. + *

+ * The ISO-8601 standard defines a calendar system based on weeks. + * It uses the week-based-year and week-of-week-based-year concepts to split + * up the passage of days instead of the standard year/month/day. + *

+ * Note that the first week may start in the previous calendar year. + * Note also that the first few days of a calendar year may be in the + * week-based-year corresponding to the previous calendar year. + */ + public static final WeekFields ISO = new WeekFields(DayOfWeek.MONDAY, 4); + + /** + * The common definition of a week that starts on Sunday. + *

+ * Defined as starting on Sunday and with a minimum of 1 day in the month. + * This week definition is in use in the US and other European countries. + * + */ + public static final WeekFields SUNDAY_START = WeekFields.of(DayOfWeek.SUNDAY, 1); + + /** + * Serialization version. + */ + private static final long serialVersionUID = -1177360819670808121L; + + /** + * The first day-of-week. + */ + private final DayOfWeek firstDayOfWeek; + /** + * The minimal number of days in the first week. + */ + private final int minimalDays; + + /** + * The field used to access the computed DayOfWeek. + */ + private transient final TemporalField dayOfWeek = ComputedDayOfField.ofDayOfWeekField(this); + + /** + * The field used to access the computed WeekOfMonth. + */ + private transient final TemporalField weekOfMonth = ComputedDayOfField.ofWeekOfMonthField(this); + + /** + * The field used to access the computed WeekOfYear. + */ + private transient final TemporalField weekOfYear = ComputedDayOfField.ofWeekOfYearField(this); + + /** + * Obtains an instance of {@code WeekFields} appropriate for a locale. + *

+ * This will look up appropriate values from the provider of localization data. + * + * @param locale the locale to use, not null + * @return the week-definition, not null + */ + public static WeekFields of(Locale locale) { + Objects.requireNonNull(locale, "locale"); + locale = new Locale(locale.getLanguage(), locale.getCountry()); // elminate variants + + // obtain these from GregorianCalendar for now + // TODO: consider getting them directly from the spi + GregorianCalendar gcal = new GregorianCalendar(locale); + int calDow = gcal.getFirstDayOfWeek(); + DayOfWeek dow = DayOfWeek.SUNDAY.plus(calDow - 1); + int minDays = gcal.getMinimalDaysInFirstWeek(); + return WeekFields.of(dow, minDays); + } + + /** + * Obtains an instance of {@code WeekFields} from the first day-of-week and minimal days. + *

+ * The first day-of-week defines the ISO {@code DayOfWeek} that is day 1 of the week. + * The minimal number of days in the first week defines how many days must be present + * in a month or year, starting from the first day-of-week, before the week is counted + * as the first week. A value of 1 will count the first day of the month or year as part + * of the first week, whereas a value of 7 will require the whole seven days to be in + * the new month or year. + *

+ * WeekFields instances are singletons; for each unique combination + * of {@code firstDayOfWeek} and {@code minimalDaysInFirstWeek} the + * the same instance will be returned. + * + * @param firstDayOfWeek the first day of the week, not null + * @param minimalDaysInFirstWeek the minimal number of days in the first week, from 1 to 7 + * @return the week-definition, not null + * @throws IllegalArgumentException if the minimal days value is less than one + * or greater than 7 + */ + public static WeekFields of(DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek) { + String key = firstDayOfWeek.toString() + minimalDaysInFirstWeek; + WeekFields rules = CACHE.get(key); + if (rules == null) { + rules = new WeekFields(firstDayOfWeek, minimalDaysInFirstWeek); + CACHE.putIfAbsent(key, rules); + rules = CACHE.get(key); + } + return rules; + } + + //----------------------------------------------------------------------- + /** + * Creates an instance of the definition. + * + * @param firstDayOfWeek the first day of the week, not null + * @param minimalDaysInFirstWeek the minimal number of days in the first week, from 1 to 7 + * @throws IllegalArgumentException if the minimal days value is invalid + */ + private WeekFields(DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek) { + Objects.requireNonNull(firstDayOfWeek, "firstDayOfWeek"); + if (minimalDaysInFirstWeek < 1 || minimalDaysInFirstWeek > 7) { + throw new IllegalArgumentException("Minimal number of days is invalid"); + } + this.firstDayOfWeek = firstDayOfWeek; + this.minimalDays = minimalDaysInFirstWeek; + } + + //----------------------------------------------------------------------- + /** + * Return the singleton WeekFields associated with the + * {@code firstDayOfWeek} and {@code minimalDays}. + * @return the singleton WeekFields for the firstDayOfWeek and minimalDays. + * @throws InvalidObjectException if the serialized object has invalid + * values for firstDayOfWeek or minimalDays. + */ + private Object readResolve() throws InvalidObjectException { + try { + return WeekFields.of(firstDayOfWeek, minimalDays); + } catch (IllegalArgumentException iae) { + throw new InvalidObjectException("Invalid serialized WeekFields: " + + iae.getMessage()); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the first day-of-week. + *

+ * The first day-of-week varies by culture. + * For example, the US uses Sunday, while France and the ISO-8601 standard use Monday. + * This method returns the first day using the standard {@code DayOfWeek} enum. + * + * @return the first day-of-week, not null + */ + public DayOfWeek getFirstDayOfWeek() { + return firstDayOfWeek; + } + + /** + * Gets the minimal number of days in the first week. + *

+ * The number of days considered to define the first week of a month or year + * varies by culture. + * For example, the ISO-8601 requires 4 days (more than half a week) to + * be present before counting the first week. + * + * @return the minimal number of days in the first week of a month or year, from 1 to 7 + */ + public int getMinimalDaysInFirstWeek() { + return minimalDays; + } + + //----------------------------------------------------------------------- + /** + * Returns a field to access the day of week, + * computed based on this WeekFields. + *

+ * The days of week are numbered from 1 to 7. + * Day number 1 is the {@link #getFirstDayOfWeek() first day-of-week}. + * + * @return the field for day-of-week using this week definition, not null + */ + public TemporalField dayOfWeek() { + return dayOfWeek; + } + + /** + * Returns a field to access the week of month, + * computed based on this WeekFields. + *

+ * This represents concept of the count of weeks within the month where weeks + * start on a fixed day-of-week, such as Monday. + * This field is typically used with {@link WeekFields#dayOfWeek()}. + *

+ * Week one (1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} + * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the month. + * Thus, week one may start up to {@code minDays} days before the start of the month. + * If the first week starts after the start of the month then the period before is week zero (0). + *

+ * For example:
+ * - if the 1st day of the month is a Monday, week one starts on the 1st and there is no week zero
+ * - if the 2nd day of the month is a Monday, week one starts on the 2nd and the 1st is in week zero
+ * - if the 4th day of the month is a Monday, week one starts on the 4th and the 1st to 3rd is in week zero
+ * - if the 5th day of the month is a Monday, week two starts on the 5th and the 1st to 4th is in week one
+ *

+ * This field can be used with any calendar system. + * @return a TemporalField to access the WeekOfMonth, not null + */ + public TemporalField weekOfMonth() { + return weekOfMonth; + } + + /** + * Returns a field to access the week of year, + * computed based on this WeekFields. + *

+ * This represents concept of the count of weeks within the year where weeks + * start on a fixed day-of-week, such as Monday. + * This field is typically used with {@link WeekFields#dayOfWeek()}. + *

+ * Week one(1) is the week starting on the {@link WeekFields#getFirstDayOfWeek} + * where there are at least {@link WeekFields#getMinimalDaysInFirstWeek()} days in the month. + * Thus, week one may start up to {@code minDays} days before the start of the year. + * If the first week starts after the start of the year then the period before is week zero (0). + *

+ * For example:
+ * - if the 1st day of the year is a Monday, week one starts on the 1st and there is no week zero
+ * - if the 2nd day of the year is a Monday, week one starts on the 2nd and the 1st is in week zero
+ * - if the 4th day of the year is a Monday, week one starts on the 4th and the 1st to 3rd is in week zero
+ * - if the 5th day of the year is a Monday, week two starts on the 5th and the 1st to 4th is in week one
+ *

+ * This field can be used with any calendar system. + * @return a TemporalField to access the WeekOfYear, not null + */ + public TemporalField weekOfYear() { + return weekOfYear; + } + + /** + * Checks if these rules are equal to the specified rules. + *

+ * The comparison is based on the entire state of the rules, which is + * the first day-of-week and minimal days. + * + * @param object the other rules to compare to, null returns false + * @return true if this is equal to the specified rules + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof WeekFields) { + return hashCode() == object.hashCode(); + } + return false; + } + + /** + * A hash code for these rules. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return firstDayOfWeek.ordinal() * 7 + minimalDays; + } + + //----------------------------------------------------------------------- + /** + * A string representation of this definition. + * + * @return the string representation, not null + */ + @Override + public String toString() { + return "WeekFields[" + firstDayOfWeek + ',' + minimalDays + ']'; + } + + //----------------------------------------------------------------------- + /** + * Field type that computes DayOfWeek, WeekOfMonth, and WeekOfYear + * based on a WeekFields. + * A separate Field instance is required for each different WeekFields; + * combination of start of week and minimum number of days. + * Constructors are provided to create fields for DayOfWeek, WeekOfMonth, + * and WeekOfYear. + */ + static class ComputedDayOfField implements TemporalField { + + /** + * Returns a field to access the day of week, + * computed based on a WeekFields. + *

+ * The WeekDefintion of the first day of the week is used with + * the ISO DAY_OF_WEEK field to compute week boundaries. + */ + static ComputedDayOfField ofDayOfWeekField(WeekFields weekDef) { + return new ComputedDayOfField("DayOfWeek", weekDef, + ChronoUnit.DAYS, ChronoUnit.WEEKS, DAY_OF_WEEK_RANGE); + } + + /** + * Returns a field to access the week of month, + * computed based on a WeekFields. + * @see WeekFields#weekOfMonth() + */ + static ComputedDayOfField ofWeekOfMonthField(WeekFields weekDef) { + return new ComputedDayOfField("WeekOfMonth", weekDef, + ChronoUnit.WEEKS, ChronoUnit.MONTHS, WEEK_OF_MONTH_RANGE); + } + + /** + * Returns a field to access the week of year, + * computed based on a WeekFields. + * @see WeekFields#weekOfYear() + */ + static ComputedDayOfField ofWeekOfYearField(WeekFields weekDef) { + return new ComputedDayOfField("WeekOfYear", weekDef, + ChronoUnit.WEEKS, ChronoUnit.YEARS, WEEK_OF_YEAR_RANGE); + } + private final String name; + private final WeekFields weekDef; + private final TemporalUnit baseUnit; + private final TemporalUnit rangeUnit; + private final ValueRange range; + + private ComputedDayOfField(String name, WeekFields weekDef, TemporalUnit baseUnit, TemporalUnit rangeUnit, ValueRange range) { + this.name = name; + this.weekDef = weekDef; + this.baseUnit = baseUnit; + this.rangeUnit = rangeUnit; + this.range = range; + } + + private static final ValueRange DAY_OF_WEEK_RANGE = ValueRange.of(1, 7); + private static final ValueRange WEEK_OF_MONTH_RANGE = ValueRange.of(0, 1, 4, 5); + private static final ValueRange WEEK_OF_YEAR_RANGE = ValueRange.of(0, 1, 52, 53); + + @Override + public long doGet(TemporalAccessor temporal) { + // Offset the ISO DOW by the start of this week + int sow = weekDef.getFirstDayOfWeek().getValue(); + int isoDow = temporal.get(ChronoField.DAY_OF_WEEK); + int dow = Math.floorMod(isoDow - sow, 7) + 1; + + if (rangeUnit == ChronoUnit.WEEKS) { + return dow; + } else if (rangeUnit == ChronoUnit.MONTHS) { + int dom = temporal.get(ChronoField.DAY_OF_MONTH); + int offset = startOfWeekOffset(dom, dow); + return computeWeek(offset, dom); + } else if (rangeUnit == ChronoUnit.YEARS) { + int doy = temporal.get(ChronoField.DAY_OF_YEAR); + int offset = startOfWeekOffset(doy, dow); + return computeWeek(offset, doy); + } else { + throw new IllegalStateException("unreachable"); + } + } + + /** + * Returns an offset to align week start with a day of month or day of year. + * + * @param day the day; 1 through infinity + * @param dow the day of the week of that day; 1 through 7 + * @return an offset in days to align a day with the start of the first 'full' week + */ + private int startOfWeekOffset(int day, int dow) { + // offset of first day corresponding to the day of week in first 7 days (zero origin) + int weekStart = Math.floorMod(day - dow, 7); + int offset = -weekStart; + if (weekStart + 1 > weekDef.getMinimalDaysInFirstWeek()) { + // The previous week has the minimum days in the current month to be a 'week' + offset = 7 - weekStart; + } + return offset; + } + + /** + * Returns the week number computed from the reference day and reference dayOfWeek. + * + * @param offset the offset to align a date with the start of week + * from {@link #startOfWeekOffset}. + * @param day the day for which to compute the week number + * @return the week number where zero is used for a partial week and 1 for the first full week + */ + private int computeWeek(int offset, int day) { + return ((7 + offset + (day - 1)) / 7); + } + + @Override + public R doWith(R temporal, long newValue) { + // Check the new value and get the old value of the field + int newVal = range.checkValidIntValue(newValue, this); + int currentVal = temporal.get(this); + if (newVal == currentVal) { + return temporal; + } + // Compute the difference and add that using the base using of the field + int delta = newVal - currentVal; + return (R) temporal.plus(delta, baseUnit); + } + + @Override + public boolean resolve(DateTimeBuilder builder, long value) { + int newValue = range.checkValidIntValue(value, this); + // DOW and YEAR are necessary for all fields; Chrono defaults to ISO if not present + int sow = weekDef.getFirstDayOfWeek().getValue(); + int dow = builder.get(weekDef.dayOfWeek()); + int year = builder.get(ChronoField.YEAR); + Chrono chrono = Chrono.from(builder); + + // The WOM and WOY fields are the critical values + if (rangeUnit == ChronoUnit.MONTHS) { + // Process WOM value by combining with DOW and MONTH, YEAR + int month = builder.get(ChronoField.MONTH_OF_YEAR); + ChronoLocalDate cd = chrono.date(year, month, 1); + int offset = startOfWeekOffset(1, cd.get(weekDef.dayOfWeek())); + offset += dow - 1; // offset to desired day of week + offset += 7 * (newValue - 1); // offset by week number + ChronoLocalDate result = cd.plus(offset, ChronoUnit.DAYS); + builder.addFieldValue(ChronoField.DAY_OF_MONTH, result.get(ChronoField.DAY_OF_MONTH)); + builder.removeFieldValue(this); + builder.removeFieldValue(weekDef.dayOfWeek()); + return true; + } else if (rangeUnit == ChronoUnit.YEARS) { + // Process WOY + ChronoLocalDate cd = chrono.date(year, 1, 1); + int offset = startOfWeekOffset(1, cd.get(weekDef.dayOfWeek())); + offset += dow - 1; // offset to desired day of week + offset += 7 * (newValue - 1); // offset by week number + ChronoLocalDate result = cd.plus(offset, ChronoUnit.DAYS); + builder.addFieldValue(ChronoField.DAY_OF_MONTH, result.get(ChronoField.DAY_OF_MONTH)); + builder.addFieldValue(ChronoField.MONTH_OF_YEAR, result.get(ChronoField.MONTH_OF_YEAR)); + builder.removeFieldValue(this); + builder.removeFieldValue(weekDef.dayOfWeek()); + return true; + } else { + // ignore DOW of WEEK field; the value will be processed by WOM or WOY + int isoDow = Math.floorMod((sow - 1) + (dow - 1), 7) + 1; + builder.addFieldValue(ChronoField.DAY_OF_WEEK, isoDow); + // Not removed, the week-of-xxx fields need this value + return true; + } + } + + //----------------------------------------------------------------------- + @Override + public String getName() { + return name; + } + + @Override + public TemporalUnit getBaseUnit() { + return baseUnit; + } + + @Override + public TemporalUnit getRangeUnit() { + return rangeUnit; + } + + @Override + public ValueRange range() { + return range; + } + + //------------------------------------------------------------------------- + @Override + public int compare(TemporalAccessor temporal1, TemporalAccessor temporal2) { + return Long.compare(temporal1.getLong(this), temporal2.getLong(this)); + } + + //----------------------------------------------------------------------- + @Override + public boolean doIsSupported(TemporalAccessor temporal) { + if (temporal.isSupported(ChronoField.DAY_OF_WEEK)) { + if (rangeUnit == ChronoUnit.WEEKS) { + return true; + } else if (rangeUnit == ChronoUnit.MONTHS) { + return temporal.isSupported(ChronoField.DAY_OF_MONTH); + } else if (rangeUnit == ChronoUnit.YEARS) { + return temporal.isSupported(ChronoField.DAY_OF_YEAR); + } + } + return false; + } + + @Override + public ValueRange doRange(TemporalAccessor temporal) { + if (rangeUnit == ChronoUnit.WEEKS) { + return range; + } + + TemporalField field = null; + if (rangeUnit == ChronoUnit.MONTHS) { + field = ChronoField.DAY_OF_MONTH; + } else if (rangeUnit == ChronoUnit.YEARS) { + field = ChronoField.DAY_OF_YEAR; + } else { + throw new IllegalStateException("unreachable"); + } + + // Offset the ISO DOW by the start of this week + int sow = weekDef.getFirstDayOfWeek().getValue(); + int isoDow = temporal.get(ChronoField.DAY_OF_WEEK); + int dow = Math.floorMod(isoDow - sow, 7) + 1; + + int offset = startOfWeekOffset(temporal.get(field), dow); + ValueRange fieldRange = temporal.range(field); + return ValueRange.of(computeWeek(offset, (int) fieldRange.getMinimum()), + computeWeek(offset, (int) fieldRange.getMaximum())); + } + + //----------------------------------------------------------------------- + @Override + public String toString() { + return getName() + "[" + weekDef.toString() + "]"; + } + } +} diff --git a/src/share/classes/java/time/temporal/Year.java b/src/share/classes/java/time/temporal/Year.java new file mode 100644 index 0000000000000000000000000000000000000000..f54acf721f8ac26f38c7ef6138fcdecf0ada21fd --- /dev/null +++ b/src/share/classes/java/time/temporal/Year.java @@ -0,0 +1,996 @@ +/* + * 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 java.time.temporal; + +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; +import static java.time.temporal.ChronoUnit.YEARS; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.Clock; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.Month; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.format.SignStyle; +import java.util.Objects; + +/** + * A year in the ISO-8601 calendar system, such as {@code 2007}. + *

+ * {@code Year} is an immutable date-time object that represents a year. + * Any field that can be derived from a year can be obtained. + *

+ * Note that years in the ISO chronology only align with years in the + * Gregorian-Julian system for modern years. Parts of Russia did not switch to the + * modern Gregorian/ISO rules until 1920. + * As such, historical years must be treated with caution. + *

+ * This class does not store or represent a month, day, time or time-zone. + * For example, the value "2007" can be stored in a {@code Year}. + *

+ * Years represented by this class follow the ISO-8601 standard and use + * the proleptic numbering system. Year 1 is preceded by year 0, then by year -1. + *

+ * The ISO-8601 calendar system is the modern civil calendar system used today + * in most of the world. It is equivalent to the proleptic Gregorian calendar + * system, in which today's rules for leap years are applied for all time. + * For most applications written today, the ISO-8601 rules are entirely suitable. + * However, any application that makes use of historical dates, and requires them + * to be accurate will find the ISO-8601 approach unsuitable. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class Year + implements Temporal, TemporalAdjuster, Comparable, Serializable { + + /** + * The minimum supported year, '-999,999,999'. + */ + public static final int MIN_VALUE = -999_999_999; + /** + * The maximum supported year, '+999,999,999'. + */ + public static final int MAX_VALUE = 999_999_999; + + /** + * Serialization version. + */ + private static final long serialVersionUID = -23038383694477807L; + /** + * Parser. + */ + private static final DateTimeFormatter PARSER = new DateTimeFormatterBuilder() + .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) + .toFormatter(); + + /** + * The year being represented. + */ + private final int year; + + //----------------------------------------------------------------------- + /** + * Obtains the current year from the system clock in the default time-zone. + *

+ * This will query the {@link java.time.Clock#systemDefaultZone() system clock} in the default + * time-zone to obtain the current year. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @return the current year using the system clock and default time-zone, not null + */ + public static Year now() { + return now(Clock.systemDefaultZone()); + } + + /** + * Obtains the current year from the system clock in the specified time-zone. + *

+ * This will query the {@link Clock#system(java.time.ZoneId) system clock} to obtain the current year. + * Specifying the time-zone avoids dependence on the default time-zone. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @param zone the zone ID to use, not null + * @return the current year using the system clock, not null + */ + public static Year now(ZoneId zone) { + return now(Clock.system(zone)); + } + + /** + * Obtains the current year from the specified clock. + *

+ * This will query the specified clock to obtain the current year. + * Using this method allows the use of an alternate clock for testing. + * The alternate clock may be introduced using {@link Clock dependency injection}. + * + * @param clock the clock to use, not null + * @return the current year, not null + */ + public static Year now(Clock clock) { + final LocalDate now = LocalDate.now(clock); // called once + return Year.of(now.getYear()); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Year}. + *

+ * This method accepts a year value from the proleptic ISO calendar system. + *

+ * The year 2AD/CE is represented by 2.
+ * The year 1AD/CE is represented by 1.
+ * The year 1BC/BCE is represented by 0.
+ * The year 2BC/BCE is represented by -1.
+ * + * @param isoYear the ISO proleptic year to represent, from {@code MIN_VALUE} to {@code MAX_VALUE} + * @return the year, not null + * @throws DateTimeException if the field is invalid + */ + public static Year of(int isoYear) { + YEAR.checkValidValue(isoYear); + return new Year(isoYear); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Year} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code Year}. + *

+ * The conversion extracts the {@link ChronoField#YEAR year} field. + * The extraction is only permitted if the temporal object has an ISO + * chronology, or can be converted to a {@code LocalDate}. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used in queries via method reference, {@code Year::from}. + * + * @param temporal the temporal object to convert, not null + * @return the year, not null + * @throws DateTimeException if unable to convert to a {@code Year} + */ + public static Year from(TemporalAccessor temporal) { + if (temporal instanceof Year) { + return (Year) temporal; + } + try { + if (ISOChrono.INSTANCE.equals(Chrono.from(temporal)) == false) { + temporal = LocalDate.from(temporal); + } + return of(temporal.get(YEAR)); + } catch (DateTimeException ex) { + throw new DateTimeException("Unable to obtain Year from TemporalAccessor: " + temporal.getClass(), ex); + } + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code Year} from a text string such as {@code 2007}. + *

+ * The string must represent a valid year. + * Years outside the range 0000 to 9999 must be prefixed by the plus or minus symbol. + * + * @param text the text to parse such as "2007", not null + * @return the parsed year, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static Year parse(CharSequence text) { + return parse(text, PARSER); + } + + /** + * Obtains an instance of {@code Year} from a text string using a specific formatter. + *

+ * The text is parsed using the formatter, returning a year. + * + * @param text the text to parse, not null + * @param formatter the formatter to use, not null + * @return the parsed year, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static Year parse(CharSequence text, DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.parse(text, Year::from); + } + + //------------------------------------------------------------------------- + /** + * Checks if the year is a leap year, according to the ISO proleptic + * calendar system rules. + *

+ * This method applies the current rules for leap years across the whole time-line. + * In general, a year is a leap year if it is divisible by four without + * remainder. However, years divisible by 100, are not leap years, with + * the exception of years divisible by 400 which are. + *

+ * For example, 1904 is a leap year it is divisible by 4. + * 1900 was not a leap year as it is divisible by 100, however 2000 was a + * leap year as it is divisible by 400. + *

+ * The calculation is proleptic - applying the same rules into the far future and far past. + * This is historically inaccurate, but is correct for the ISO-8601 standard. + * + * @param year the year to check + * @return true if the year is leap, false otherwise + */ + public static boolean isLeap(long year) { + return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0); + } + + //----------------------------------------------------------------------- + /** + * Constructor. + * + * @param year the year to represent + */ + private Year(int year) { + this.year = year; + } + + //----------------------------------------------------------------------- + /** + * Gets the year value. + *

+ * The year returned by this method is proleptic as per {@code get(YEAR)}. + * + * @return the year, {@code MIN_VALUE} to {@code MAX_VALUE} + */ + public int getValue() { + return year; + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this year can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and + * {@link #get(TemporalField) get} methods will throw an exception. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date-time. + * The supported fields are: + *

    + *
  • {@code YEAR_OF_ERA} + *
  • {@code YEAR} + *
  • {@code ERA} + *
+ * All other {@code ChronoField} instances will return false. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the field is supported is determined by the field. + * + * @param field the field to check, null returns false + * @return true if the field is supported on this year, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == YEAR || field == YEAR_OF_ERA || field == ERA; + } + return field != null && field.doIsSupported(this); + } + + /** + * Gets the range of valid values for the specified field. + *

+ * The range object expresses the minimum and maximum valid values for a field. + * This year is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return + * appropriate range instances. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + @Override + public ValueRange range(TemporalField field) { + if (field == YEAR_OF_ERA) { + return (year <= 0 ? ValueRange.of(1, MAX_VALUE + 1) : ValueRange.of(1, MAX_VALUE)); + } + return Temporal.super.range(field); + } + + /** + * Gets the value of the specified field from this year as an {@code int}. + *

+ * This queries this year for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this year. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override // override for Javadoc + public int get(TemporalField field) { + return range(field).checkValidIntValue(getLong(field), field); + } + + /** + * Gets the value of the specified field from this year as a {@code long}. + *

+ * This queries this year for the value for the specified field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this year. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case YEAR_OF_ERA: return (year < 1 ? 1 - year : year); + case YEAR: return year; + case ERA: return (year < 1 ? 0 : 1); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doGet(this); + } + + //----------------------------------------------------------------------- + /** + * Checks if the year is a leap year, according to the ISO proleptic + * calendar system rules. + *

+ * This method applies the current rules for leap years across the whole time-line. + * In general, a year is a leap year if it is divisible by four without + * remainder. However, years divisible by 100, are not leap years, with + * the exception of years divisible by 400 which are. + *

+ * For example, 1904 is a leap year it is divisible by 4. + * 1900 was not a leap year as it is divisible by 100, however 2000 was a + * leap year as it is divisible by 400. + *

+ * The calculation is proleptic - applying the same rules into the far future and far past. + * This is historically inaccurate, but is correct for the ISO-8601 standard. + * + * @return true if the year is leap, false otherwise + */ + public boolean isLeap() { + return Year.isLeap(year); + } + + /** + * Checks if the month-day is valid for this year. + *

+ * This method checks whether this year and the input month and day form + * a valid date. + * + * @param monthDay the month-day to validate, null returns false + * @return true if the month and day are valid for this year + */ + public boolean isValidMonthDay(MonthDay monthDay) { + return monthDay != null && monthDay.isValidYear(year); + } + + /** + * Gets the length of this year in days. + * + * @return the length of this year in days, 365 or 366 + */ + public int length() { + return isLeap() ? 366 : 365; + } + + //----------------------------------------------------------------------- + /** + * Returns an adjusted copy of this year. + *

+ * This returns a new {@code Year}, based on this one, with the year adjusted. + * The adjustment takes place using the specified adjuster strategy object. + * Read the documentation of the adjuster to understand what adjustment will be made. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalAdjuster#adjustInto(Temporal)} method on the + * specified adjuster passing {@code this} as the argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adjuster the adjuster to use, not null + * @return a {@code Year} based on {@code this} with the adjustment made, not null + * @throws DateTimeException if the adjustment cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Year with(TemporalAdjuster adjuster) { + return (Year) adjuster.adjustInto(this); + } + + /** + * Returns a copy of this year with the specified field set to a new value. + *

+ * This returns a new {@code Year}, based on this one, with the value + * for the specified field changed. + * If it is not possible to set the value, because the field is not supported or for + * some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the adjustment is implemented here. + * The supported fields behave as follows: + *

    + *
  • {@code YEAR_OF_ERA} - + * Returns a {@code Year} with the specified year-of-era + * The era will be unchanged. + *
  • {@code YEAR} - + * Returns a {@code Year} with the specified year. + * This completely replaces the date and is equivalent to {@link #of(int)}. + *
  • {@code ERA} - + * Returns a {@code Year} with the specified era. + * The year-of-era will be unchanged. + *
+ *

+ * In all cases, if the new value is outside the valid range of values for the field + * then a {@code DateTimeException} will be thrown. + *

+ * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doWith(Temporal, long)} + * passing {@code this} as the argument. In this case, the field determines + * whether and how to adjust the instant. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param field the field to set in the result, not null + * @param newValue the new value of the field in the result + * @return a {@code Year} based on {@code this} with the specified field set, not null + * @throws DateTimeException if the field cannot be set + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Year with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + f.checkValidValue(newValue); + switch (f) { + case YEAR_OF_ERA: return Year.of((int) (year < 1 ? 1 - newValue : newValue)); + case YEAR: return Year.of((int) newValue); + case ERA: return (getLong(ERA) == newValue ? this : Year.of(1 - year)); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doWith(this, newValue); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this year with the specified period added. + *

+ * This method returns a new year based on this year with the specified period added. + * The adder is typically {@link java.time.Period} but may be any other type implementing + * the {@link TemporalAdder} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #plus(long, TemporalUnit)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adder the adder to use, not null + * @return a {@code Year} based on this year with the addition made, not null + * @throws DateTimeException if the addition cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Year plus(TemporalAdder adder) { + return (Year) adder.addTo(this); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public Year plus(long amountToAdd, TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + switch ((ChronoUnit) unit) { + case YEARS: return plusYears(amountToAdd); + case DECADES: return plusYears(Math.multiplyExact(amountToAdd, 10)); + case CENTURIES: return plusYears(Math.multiplyExact(amountToAdd, 100)); + case MILLENNIA: return plusYears(Math.multiplyExact(amountToAdd, 1000)); + case ERAS: return with(ERA, Math.addExact(getLong(ERA), amountToAdd)); + } + throw new DateTimeException("Unsupported unit: " + unit.getName()); + } + return unit.doPlus(this, amountToAdd); + } + + /** + * Returns a copy of this year with the specified number of years added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param yearsToAdd the years to add, may be negative + * @return a {@code Year} based on this year with the period added, not null + * @throws DateTimeException if the result exceeds the supported year range + */ + public Year plusYears(long yearsToAdd) { + if (yearsToAdd == 0) { + return this; + } + return of(YEAR.checkValidIntValue(year + yearsToAdd)); // overflow safe + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this year with the specified period subtracted. + *

+ * This method returns a new year based on this year with the specified period subtracted. + * The subtractor is typically {@link java.time.Period} but may be any other type implementing + * the {@link TemporalSubtractor} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #minus(long, TemporalUnit)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param subtractor the subtractor to use, not null + * @return a {@code Year} based on this year with the subtraction made, not null + * @throws DateTimeException if the subtraction cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Year minus(TemporalSubtractor subtractor) { + return (Year) subtractor.subtractFrom(this); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public Year minus(long amountToSubtract, TemporalUnit unit) { + return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit)); + } + + /** + * Returns a copy of this year with the specified number of years subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param yearsToSubtract the years to subtract, may be negative + * @return a {@code Year} based on this year with the period subtracted, not null + * @throws DateTimeException if the result exceeds the supported year range + */ + public Year minusYears(long yearsToSubtract) { + return (yearsToSubtract == Long.MIN_VALUE ? plusYears(Long.MAX_VALUE).plusYears(1) : plusYears(-yearsToSubtract)); + } + + //----------------------------------------------------------------------- + /** + * Queries this year using the specified query. + *

+ * This queries this year using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.chrono()) { + return (R) ISOChrono.INSTANCE; + } else if (query == Queries.precision()) { + return (R) YEARS; + } + return Temporal.super.query(query); + } + + /** + * Adjusts the specified temporal object to have this year. + *

+ * This returns a temporal object of the same observable type as the input + * with the year changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * passing {@link ChronoField#YEAR} as the field. + * If the specified temporal object does not use the ISO calendar system then + * a {@code DateTimeException} is thrown. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisYear.adjustInto(temporal);
+     *   temporal = temporal.with(thisYear);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal adjustInto(Temporal temporal) { + if (Chrono.from(temporal).equals(ISOChrono.INSTANCE) == false) { + throw new DateTimeException("Adjustment only supported on ISO date-time"); + } + return temporal.with(YEAR, year); + } + + /** + * Calculates the period between this year and another year in + * terms of the specified unit. + *

+ * This calculates the period between two years in terms of a single unit. + * The start and end points are {@code this} and the specified year. + * The result will be negative if the end is before the start. + * The {@code Temporal} passed to this method must be a {@code Year}. + * For example, the period in decades between two year can be calculated + * using {@code startYear.periodUntil(endYear, DECADES)}. + *

+ * The calculation returns a whole number, representing the number of + * complete units between the two years. + * For example, the period in decades between 2012 and 2031 + * will only be one decade as it is one year short of two decades. + *

+ * This method operates in association with {@link TemporalUnit#between}. + * The result of this method is a {@code long} representing the amount of + * the specified unit. By contrast, the result of {@code between} is an + * object that can be used directly in addition/subtraction: + *

+     *   long period = start.periodUntil(end, YEARS);   // this method
+     *   dateTime.plus(YEARS.between(start, end));      // use in plus/minus
+     * 
+ *

+ * The calculation is implemented in this method for {@link ChronoUnit}. + * The units {@code YEARS}, {@code DECADES}, {@code CENTURIES}, + * {@code MILLENNIA} and {@code ERAS} are supported. + * Other {@code ChronoUnit} values will throw an exception. + *

+ * If the unit is not a {@code ChronoUnit}, then the result of this method + * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)} + * passing {@code this} as the first argument and the input temporal as + * the second argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param endYear the end year, which must be a {@code Year}, not null + * @param unit the unit to measure the period in, not null + * @return the amount of the period between this year and the end year + * @throws DateTimeException if the period cannot be calculated + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long periodUntil(Temporal endYear, TemporalUnit unit) { + if (endYear instanceof Year == false) { + Objects.requireNonNull(endYear, "endYear"); + throw new DateTimeException("Unable to calculate period between objects of two different types"); + } + Year end = (Year) endYear; + if (unit instanceof ChronoUnit) { + long yearsUntil = ((long) end.year) - year; // no overflow + switch ((ChronoUnit) unit) { + case YEARS: return yearsUntil; + case DECADES: return yearsUntil / 10; + case CENTURIES: return yearsUntil / 100; + case MILLENNIA: return yearsUntil / 1000; + case ERAS: return end.getLong(ERA) - getLong(ERA); + } + throw new DateTimeException("Unsupported unit: " + unit.getName()); + } + return unit.between(this, endYear).getAmount(); + } + + //----------------------------------------------------------------------- + /** + * Returns a date formed from this year at the specified day-of-year. + *

+ * This combines this year and the specified day-of-year to form a {@code LocalDate}. + * The day-of-year value 366 is only valid in a leap year. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param dayOfYear the day-of-year to use, not null + * @return the local date formed from this year and the specified date of year, not null + * @throws DateTimeException if the day of year is 366 and this is not a leap year + */ + public LocalDate atDay(int dayOfYear) { + return LocalDate.ofYearDay(year, dayOfYear); + } + + /** + * Returns a year-month formed from this year at the specified month. + *

+ * This combines this year and the specified month to form a {@code YearMonth}. + * All possible combinations of year and month are valid. + *

+ * This method can be used as part of a chain to produce a date: + *

+     *  LocalDate date = year.atMonth(month).atDay(day);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param month the month-of-year to use, not null + * @return the year-month formed from this year and the specified month, not null + */ + public YearMonth atMonth(Month month) { + return YearMonth.of(year, month); + } + + /** + * Returns a year-month formed from this year at the specified month. + *

+ * This combines this year and the specified month to form a {@code YearMonth}. + * All possible combinations of year and month are valid. + *

+ * This method can be used as part of a chain to produce a date: + *

+     *  LocalDate date = year.atMonth(month).atDay(day);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param month the month-of-year to use, from 1 (January) to 12 (December) + * @return the year-month formed from this year and the specified month, not null + */ + public YearMonth atMonth(int month) { + return YearMonth.of(year, month); + } + + /** + * Returns a date formed from this year at the specified month-day. + *

+ * This combines this year and the specified month-day to form a {@code LocalDate}. + * The month-day value of February 29th is only valid in a leap year. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param monthDay the month-day to use, not null + * @return the local date formed from this year and the specified month-day, not null + * @throws DateTimeException if the month-day is February 29th and this is not a leap year + */ + public LocalDate atMonthDay(MonthDay monthDay) { + return LocalDate.of(year, monthDay.getMonth(), monthDay.getDayOfMonth()); + } + + //----------------------------------------------------------------------- + /** + * Compares this year to another year. + *

+ * The comparison is based on the value of the year. + * It is "consistent with equals", as defined by {@link Comparable}. + * + * @param other the other year to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + public int compareTo(Year other) { + return year - other.year; + } + + /** + * Is this year after the specified year. + * + * @param other the other year to compare to, not null + * @return true if this is after the specified year + */ + public boolean isAfter(Year other) { + return year > other.year; + } + + /** + * Is this year before the specified year. + * + * @param other the other year to compare to, not null + * @return true if this point is before the specified year + */ + public boolean isBefore(Year other) { + return year < other.year; + } + + //----------------------------------------------------------------------- + /** + * Checks if this year is equal to another year. + *

+ * The comparison is based on the time-line position of the years. + * + * @param obj the object to check, null returns false + * @return true if this is equal to the other year + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Year) { + return year == ((Year) obj).year; + } + return false; + } + + /** + * A hash code for this year. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return year; + } + + //----------------------------------------------------------------------- + /** + * Outputs this year as a {@code String}. + * + * @return a string representation of this year, not null + */ + @Override + public String toString() { + return Integer.toString(year); + } + + /** + * Outputs this year as a {@code String} using the formatter. + *

+ * This year will be passed to the formatter + * {@link DateTimeFormatter#print(TemporalAccessor) print method}. + * + * @param formatter the formatter to use, not null + * @return the formatted year string, not null + * @throws DateTimeException if an error occurs during printing + */ + public String toString(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.print(this); + } + + //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(4);  // identifies this as a Year
+     *  out.writeInt(year);
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.YEAR_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(DataOutput out) throws IOException { + out.writeInt(year); + } + + static Year readExternal(DataInput in) throws IOException { + return Year.of(in.readInt()); + } + +} diff --git a/src/share/classes/java/time/temporal/YearMonth.java b/src/share/classes/java/time/temporal/YearMonth.java new file mode 100644 index 0000000000000000000000000000000000000000..246112084ca15b999779617e7efe7a698bfd7ad3 --- /dev/null +++ b/src/share/classes/java/time/temporal/YearMonth.java @@ -0,0 +1,1085 @@ +/* + * 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 java.time.temporal; + +import static java.time.temporal.ChronoField.EPOCH_MONTH; +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; +import static java.time.temporal.ChronoUnit.MONTHS; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.time.Clock; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.Month; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.format.SignStyle; +import java.util.Objects; + +/** + * A year-month in the ISO-8601 calendar system, such as {@code 2007-12}. + *

+ * {@code YearMonth} is an immutable date-time object that represents the combination + * of a year and month. Any field that can be derived from a year and month, such as + * quarter-of-year, can be obtained. + *

+ * This class does not store or represent a day, time or time-zone. + * For example, the value "October 2007" can be stored in a {@code YearMonth}. + *

+ * The ISO-8601 calendar system is the modern civil calendar system used today + * in most of the world. It is equivalent to the proleptic Gregorian calendar + * system, in which today's rules for leap years are applied for all time. + * For most applications written today, the ISO-8601 rules are entirely suitable. + * However, any application that makes use of historical dates, and requires them + * to be accurate will find the ISO-8601 approach unsuitable. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class YearMonth + implements Temporal, TemporalAdjuster, Comparable, Serializable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = 4183400860270640070L; + /** + * Parser. + */ + private static final DateTimeFormatter PARSER = new DateTimeFormatterBuilder() + .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) + .appendLiteral('-') + .appendValue(MONTH_OF_YEAR, 2) + .toFormatter(); + + /** + * The year. + */ + private final int year; + /** + * The month-of-year, not null. + */ + private final int month; + + //----------------------------------------------------------------------- + /** + * Obtains the current year-month from the system clock in the default time-zone. + *

+ * This will query the {@link java.time.Clock#systemDefaultZone() system clock} in the default + * time-zone to obtain the current year-month. + * The zone and offset will be set based on the time-zone in the clock. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @return the current year-month using the system clock and default time-zone, not null + */ + public static YearMonth now() { + return now(Clock.systemDefaultZone()); + } + + /** + * Obtains the current year-month from the system clock in the specified time-zone. + *

+ * This will query the {@link Clock#system(java.time.ZoneId) system clock} to obtain the current year-month. + * Specifying the time-zone avoids dependence on the default time-zone. + *

+ * Using this method will prevent the ability to use an alternate clock for testing + * because the clock is hard-coded. + * + * @param zone the zone ID to use, not null + * @return the current year-month using the system clock, not null + */ + public static YearMonth now(ZoneId zone) { + return now(Clock.system(zone)); + } + + /** + * Obtains the current year-month from the specified clock. + *

+ * This will query the specified clock to obtain the current year-month. + * Using this method allows the use of an alternate clock for testing. + * The alternate clock may be introduced using {@link Clock dependency injection}. + * + * @param clock the clock to use, not null + * @return the current year-month, not null + */ + public static YearMonth now(Clock clock) { + final LocalDate now = LocalDate.now(clock); // called once + return YearMonth.of(now.getYear(), now.getMonth()); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code YearMonth} from a year and month. + * + * @param year the year to represent, from MIN_YEAR to MAX_YEAR + * @param month the month-of-year to represent, not null + * @return the year-month, not null + * @throws DateTimeException if the year value is invalid + */ + public static YearMonth of(int year, Month month) { + Objects.requireNonNull(month, "month"); + return of(year, month.getValue()); + } + + /** + * Obtains an instance of {@code YearMonth} from a year and month. + * + * @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) + * @return the year-month, not null + * @throws DateTimeException if either field value is invalid + */ + public static YearMonth of(int year, int month) { + YEAR.checkValidValue(year); + MONTH_OF_YEAR.checkValidValue(month); + return new YearMonth(year, month); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code YearMonth} from a temporal object. + *

+ * A {@code TemporalAccessor} represents some form of date and time information. + * This factory converts the arbitrary temporal object to an instance of {@code YearMonth}. + *

+ * The conversion extracts the {@link ChronoField#YEAR YEAR} and + * {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} fields. + * The extraction is only permitted if the temporal object has an ISO + * chronology, or can be converted to a {@code LocalDate}. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used in queries via method reference, {@code YearMonth::from}. + * + * @param temporal the temporal object to convert, not null + * @return the year-month, not null + * @throws DateTimeException if unable to convert to a {@code YearMonth} + */ + public static YearMonth from(TemporalAccessor temporal) { + if (temporal instanceof YearMonth) { + return (YearMonth) temporal; + } + try { + if (ISOChrono.INSTANCE.equals(Chrono.from(temporal)) == false) { + temporal = LocalDate.from(temporal); + } + return of(temporal.get(YEAR), temporal.get(MONTH_OF_YEAR)); + } catch (DateTimeException ex) { + throw new DateTimeException("Unable to obtain YearMonth from TemporalAccessor: " + temporal.getClass(), ex); + } + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code YearMonth} from a text string such as {@code 2007-12}. + *

+ * The string must represent a valid year-month. + * The format must be {@code yyyy-MM}. + * Years outside the range 0000 to 9999 must be prefixed by the plus or minus symbol. + * + * @param text the text to parse such as "2007-12", not null + * @return the parsed year-month, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static YearMonth parse(CharSequence text) { + return parse(text, PARSER); + } + + /** + * Obtains an instance of {@code YearMonth} from a text string using a specific formatter. + *

+ * The text is parsed using the formatter, returning a year-month. + * + * @param text the text to parse, not null + * @param formatter the formatter to use, not null + * @return the parsed year-month, not null + * @throws DateTimeParseException if the text cannot be parsed + */ + public static YearMonth parse(CharSequence text, DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.parse(text, YearMonth::from); + } + + //----------------------------------------------------------------------- + /** + * Constructor. + * + * @param year the year to represent, validated from MIN_YEAR to MAX_YEAR + * @param month the month-of-year to represent, validated from 1 (January) to 12 (December) + */ + private YearMonth(int year, int month) { + this.year = year; + this.month = month; + } + + /** + * Returns a copy of this year-month with the new year and month, checking + * to see if a new object is in fact required. + * + * @param newYear the year to represent, validated from MIN_YEAR to MAX_YEAR + * @param newMonth the month-of-year to represent, validated not null + * @return the year-month, not null + */ + private YearMonth with(int newYear, int newMonth) { + if (year == newYear && month == newMonth) { + return this; + } + return new YearMonth(newYear, newMonth); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this year-month can be queried for the specified field. + * If false, then calling the {@link #range(TemporalField) range} and + * {@link #get(TemporalField) get} methods will throw an exception. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this date-time. + * The supported fields are: + *

    + *
  • {@code MONTH_OF_YEAR} + *
  • {@code EPOCH_MONTH} + *
  • {@code YEAR_OF_ERA} + *
  • {@code YEAR} + *
  • {@code ERA} + *
+ * All other {@code ChronoField} instances will return false. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the field is supported is determined by the field. + * + * @param field the field to check, null returns false + * @return true if the field is supported on this year-month, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == YEAR || field == MONTH_OF_YEAR || + field == EPOCH_MONTH || field == YEAR_OF_ERA || field == ERA; + } + return field != null && field.doIsSupported(this); + } + + /** + * Gets the range of valid values for the specified field. + *

+ * The range object expresses the minimum and maximum valid values for a field. + * This year-month is used to enhance the accuracy of the returned range. + * If it is not possible to return the range, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return + * appropriate range instances. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)} + * passing {@code this} as the argument. + * Whether the range can be obtained is determined by the field. + * + * @param field the field to query the range for, not null + * @return the range of valid values for the field, not null + * @throws DateTimeException if the range for the field cannot be obtained + */ + @Override + public ValueRange range(TemporalField field) { + if (field == YEAR_OF_ERA) { + return (getYear() <= 0 ? ValueRange.of(1, Year.MAX_VALUE + 1) : ValueRange.of(1, Year.MAX_VALUE)); + } + return Temporal.super.range(field); + } + + /** + * Gets the value of the specified field from this year-month as an {@code int}. + *

+ * This queries this year-month for the value for the specified field. + * The returned value will always be within the valid range of values for the field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this year-month, except {@code EPOCH_MONTH} which is too + * large to fit in an {@code int} and throw a {@code DateTimeException}. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override // override for Javadoc + public int get(TemporalField field) { + return range(field).checkValidIntValue(getLong(field), field); + } + + /** + * Gets the value of the specified field from this year-month as a {@code long}. + *

+ * This queries this year-month for the value for the specified field. + * If it is not possible to return the value, because the field is not supported + * or for some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the query is implemented here. + * The {@link #isSupported(TemporalField) supported fields} will return valid + * values based on this year-month. + * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)} + * passing {@code this} as the argument. Whether the value can be obtained, + * and what the value represents, is determined by the field. + * + * @param field the field to get, not null + * @return the value for the field + * @throws DateTimeException if a value for the field cannot be obtained + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case MONTH_OF_YEAR: return month; + case EPOCH_MONTH: return getEpochMonth(); + case YEAR_OF_ERA: return (year < 1 ? 1 - year : year); + case YEAR: return year; + case ERA: return (year < 1 ? 0 : 1); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doGet(this); + } + + private long getEpochMonth() { + return ((year - 1970) * 12L) + (month - 1); + } + + //----------------------------------------------------------------------- + /** + * Gets the year field. + *

+ * This method returns the primitive {@code int} value for the year. + *

+ * The year returned by this method is proleptic as per {@code get(YEAR)}. + * + * @return the year, from MIN_YEAR to MAX_YEAR + */ + public int getYear() { + return year; + } + + /** + * Gets the month-of-year field using the {@code Month} enum. + *

+ * This method returns the enum {@link Month} for the month. + * This avoids confusion as to what {@code int} values mean. + * If you need access to the primitive {@code int} value then the enum + * provides the {@link Month#getValue() int value}. + * + * @return the month-of-year, not null + */ + public Month getMonth() { + return Month.of(month); + } + + //----------------------------------------------------------------------- + /** + * Checks if the year is a leap year, according to the ISO proleptic + * calendar system rules. + *

+ * This method applies the current rules for leap years across the whole time-line. + * In general, a year is a leap year if it is divisible by four without + * remainder. However, years divisible by 100, are not leap years, with + * the exception of years divisible by 400 which are. + *

+ * For example, 1904 is a leap year it is divisible by 4. + * 1900 was not a leap year as it is divisible by 100, however 2000 was a + * leap year as it is divisible by 400. + *

+ * The calculation is proleptic - applying the same rules into the far future and far past. + * This is historically inaccurate, but is correct for the ISO-8601 standard. + * + * @return true if the year is leap, false otherwise + */ + public boolean isLeapYear() { + return ISOChrono.INSTANCE.isLeapYear(year); + } + + /** + * Checks if the day-of-month is valid for this year-month. + *

+ * This method checks whether this year and month and the input day form + * a valid date. + * + * @param dayOfMonth the day-of-month to validate, from 1 to 31, invalid value returns false + * @return true if the day is valid for this year-month + */ + public boolean isValidDay(int dayOfMonth) { + return dayOfMonth >= 1 && dayOfMonth <= lengthOfMonth(); + } + + /** + * Returns the length of the month, taking account of the year. + *

+ * This returns the length of the month in days. + * For example, a date in January would return 31. + * + * @return the length of the month in days, from 28 to 31 + */ + public int lengthOfMonth() { + return getMonth().length(isLeapYear()); + } + + /** + * Returns the length of the year. + *

+ * This returns the length of the year in days, either 365 or 366. + * + * @return 366 if the year is leap, 365 otherwise + */ + public int lengthOfYear() { + return (isLeapYear() ? 366 : 365); + } + + //----------------------------------------------------------------------- + /** + * Returns an adjusted copy of this year-month. + *

+ * This returns a new {@code YearMonth}, based on this one, with the year-month adjusted. + * The adjustment takes place using the specified adjuster strategy object. + * Read the documentation of the adjuster to understand what adjustment will be made. + *

+ * A simple adjuster might simply set the one of the fields, such as the year field. + * A more complex adjuster might set the year-month to the next month that + * Halley's comet will pass the Earth. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalAdjuster#adjustInto(Temporal)} method on the + * specified adjuster passing {@code this} as the argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adjuster the adjuster to use, not null + * @return a {@code YearMonth} based on {@code this} with the adjustment made, not null + * @throws DateTimeException if the adjustment cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public YearMonth with(TemporalAdjuster adjuster) { + return (YearMonth) adjuster.adjustInto(this); + } + + /** + * Returns a copy of this year-month with the specified field set to a new value. + *

+ * This returns a new {@code YearMonth}, based on this one, with the value + * for the specified field changed. + * This can be used to change any supported field, such as the year or month. + * If it is not possible to set the value, because the field is not supported or for + * some other reason, an exception is thrown. + *

+ * If the field is a {@link ChronoField} then the adjustment is implemented here. + * The supported fields behave as follows: + *

    + *
  • {@code MONTH_OF_YEAR} - + * Returns a {@code YearMonth} with the specified month-of-year. + * The year will be unchanged. + *
  • {@code EPOCH_MONTH} - + * Returns a {@code YearMonth} with the specified epoch-month. + * This completely replaces the year and month of this object. + *
  • {@code YEAR_OF_ERA} - + * Returns a {@code YearMonth} with the specified year-of-era + * The month and era will be unchanged. + *
  • {@code YEAR} - + * Returns a {@code YearMonth} with the specified year. + * The month will be unchanged. + *
  • {@code ERA} - + * Returns a {@code YearMonth} with the specified era. + * The month and year-of-era will be unchanged. + *
+ *

+ * In all cases, if the new value is outside the valid range of values for the field + * then a {@code DateTimeException} will be thrown. + *

+ * All other {@code ChronoField} instances will throw a {@code DateTimeException}. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.doWith(Temporal, long)} + * passing {@code this} as the argument. In this case, the field determines + * whether and how to adjust the instant. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param field the field to set in the result, not null + * @param newValue the new value of the field in the result + * @return a {@code YearMonth} based on {@code this} with the specified field set, not null + * @throws DateTimeException if the field cannot be set + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public YearMonth with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + f.checkValidValue(newValue); + switch (f) { + case MONTH_OF_YEAR: return withMonth((int) newValue); + case EPOCH_MONTH: return plusMonths(newValue - getLong(EPOCH_MONTH)); + case YEAR_OF_ERA: return withYear((int) (year < 1 ? 1 - newValue : newValue)); + case YEAR: return withYear((int) newValue); + case ERA: return (getLong(ERA) == newValue ? this : withYear(1 - year)); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doWith(this, newValue); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this {@code YearMonth} with the year altered. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param year the year to set in the returned year-month, from MIN_YEAR to MAX_YEAR + * @return a {@code YearMonth} based on this year-month with the requested year, not null + * @throws DateTimeException if the year value is invalid + */ + public YearMonth withYear(int year) { + YEAR.checkValidValue(year); + return with(year, month); + } + + /** + * Returns a copy of this {@code YearMonth} with the month-of-year altered. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param month the month-of-year to set in the returned year-month, from 1 (January) to 12 (December) + * @return a {@code YearMonth} based on this year-month with the requested month, not null + * @throws DateTimeException if the month-of-year value is invalid + */ + public YearMonth withMonth(int month) { + MONTH_OF_YEAR.checkValidValue(month); + return with(year, month); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this year-month with the specified period added. + *

+ * This method returns a new year-month based on this year-month with the specified period added. + * The adder is typically {@link java.time.Period} but may be any other type implementing + * the {@link TemporalAdder} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #plus(long, TemporalUnit)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param adder the adder to use, not null + * @return a {@code YearMonth} based on this year-month with the addition made, not null + * @throws DateTimeException if the addition cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public YearMonth plus(TemporalAdder adder) { + return (YearMonth) adder.addTo(this); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public YearMonth plus(long amountToAdd, TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + switch ((ChronoUnit) unit) { + case MONTHS: return plusMonths(amountToAdd); + case YEARS: return plusYears(amountToAdd); + case DECADES: return plusYears(Math.multiplyExact(amountToAdd, 10)); + case CENTURIES: return plusYears(Math.multiplyExact(amountToAdd, 100)); + case MILLENNIA: return plusYears(Math.multiplyExact(amountToAdd, 1000)); + case ERAS: return with(ERA, Math.addExact(getLong(ERA), amountToAdd)); + } + throw new DateTimeException("Unsupported unit: " + unit.getName()); + } + return unit.doPlus(this, amountToAdd); + } + + /** + * Returns a copy of this year-month with the specified period in years added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param yearsToAdd the years to add, may be negative + * @return a {@code YearMonth} based on this year-month with the years added, not null + * @throws DateTimeException if the result exceeds the supported range + */ + public YearMonth plusYears(long yearsToAdd) { + if (yearsToAdd == 0) { + return this; + } + int newYear = YEAR.checkValidIntValue(year + yearsToAdd); // safe overflow + return with(newYear, month); + } + + /** + * Returns a copy of this year-month with the specified period in months added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param monthsToAdd the months to add, may be negative + * @return a {@code YearMonth} based on this year-month with the months added, not null + * @throws DateTimeException if the result exceeds the supported range + */ + public YearMonth plusMonths(long monthsToAdd) { + if (monthsToAdd == 0) { + return this; + } + long monthCount = year * 12L + (month - 1); + long calcMonths = monthCount + monthsToAdd; // safe overflow + int newYear = YEAR.checkValidIntValue(Math.floorDiv(calcMonths, 12)); + int newMonth = (int)Math.floorMod(calcMonths, 12) + 1; + return with(newYear, newMonth); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this year-month with the specified period subtracted. + *

+ * This method returns a new year-month based on this year-month with the specified period subtracted. + * The subtractor is typically {@link java.time.Period} but may be any other type implementing + * the {@link TemporalSubtractor} interface. + * The calculation is delegated to the specified adjuster, which typically calls + * back to {@link #minus(long, TemporalUnit)}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param subtractor the subtractor to use, not null + * @return a {@code YearMonth} based on this year-month with the subtraction made, not null + * @throws DateTimeException if the subtraction cannot be made + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public YearMonth minus(TemporalSubtractor subtractor) { + return (YearMonth) subtractor.subtractFrom(this); + } + + /** + * {@inheritDoc} + * @throws DateTimeException {@inheritDoc} + * @throws ArithmeticException {@inheritDoc} + */ + @Override + public YearMonth minus(long amountToSubtract, TemporalUnit unit) { + return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit)); + } + + /** + * Returns a copy of this year-month with the specified period in years subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param yearsToSubtract the years to subtract, may be negative + * @return a {@code YearMonth} based on this year-month with the years subtracted, not null + * @throws DateTimeException if the result exceeds the supported range + */ + public YearMonth minusYears(long yearsToSubtract) { + return (yearsToSubtract == Long.MIN_VALUE ? plusYears(Long.MAX_VALUE).plusYears(1) : plusYears(-yearsToSubtract)); + } + + /** + * Returns a copy of this year-month with the specified period in months subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param monthsToSubtract the months to subtract, may be negative + * @return a {@code YearMonth} based on this year-month with the months subtracted, not null + * @throws DateTimeException if the result exceeds the supported range + */ + public YearMonth minusMonths(long monthsToSubtract) { + return (monthsToSubtract == Long.MIN_VALUE ? plusMonths(Long.MAX_VALUE).plusMonths(1) : plusMonths(-monthsToSubtract)); + } + + //----------------------------------------------------------------------- + /** + * Queries this year-month using the specified query. + *

+ * This queries this year-month using the specified query strategy object. + * The {@code TemporalQuery} object defines the logic to be used to + * obtain the result. Read the documentation of the query to understand + * what the result of this method will be. + *

+ * The result of this method is obtained by invoking the + * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the + * specified query passing {@code this} as the argument. + * + * @param the type of the result + * @param query the query to invoke, not null + * @return the query result, null may be returned (defined by the query) + * @throws DateTimeException if unable to query (defined by the query) + * @throws ArithmeticException if numeric overflow occurs (defined by the query) + */ + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.chrono()) { + return (R) ISOChrono.INSTANCE; + } else if (query == Queries.precision()) { + return (R) MONTHS; + } + return Temporal.super.query(query); + } + + /** + * Adjusts the specified temporal object to have this year-month. + *

+ * This returns a temporal object of the same observable type as the input + * with the year and month changed to be the same as this. + *

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * passing {@link ChronoField#EPOCH_MONTH} as the field. + * If the specified temporal object does not use the ISO calendar system then + * a {@code DateTimeException} is thrown. + *

+ * In most cases, it is clearer to reverse the calling pattern by using + * {@link Temporal#with(TemporalAdjuster)}: + *

+     *   // these two lines are equivalent, but the second approach is recommended
+     *   temporal = thisYearMonth.adjustInto(temporal);
+     *   temporal = temporal.with(thisYearMonth);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param temporal the target object to be adjusted, not null + * @return the adjusted object, not null + * @throws DateTimeException if unable to make the adjustment + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public Temporal adjustInto(Temporal temporal) { + if (Chrono.from(temporal).equals(ISOChrono.INSTANCE) == false) { + throw new DateTimeException("Adjustment only supported on ISO date-time"); + } + return temporal.with(EPOCH_MONTH, getEpochMonth()); + } + + /** + * Calculates the period between this year-month and another year-month in + * terms of the specified unit. + *

+ * This calculates the period between two year-months in terms of a single unit. + * The start and end points are {@code this} and the specified year-month. + * The result will be negative if the end is before the start. + * The {@code Temporal} passed to this method must be a {@code YearMonth}. + * For example, the period in years between two year-months can be calculated + * using {@code startYearMonth.periodUntil(endYearMonth, YEARS)}. + *

+ * The calculation returns a whole number, representing the number of + * complete units between the two year-months. + * For example, the period in decades between 2012-06 and 2032-05 + * will only be one decade as it is one month short of two decades. + *

+ * This method operates in association with {@link TemporalUnit#between}. + * The result of this method is a {@code long} representing the amount of + * the specified unit. By contrast, the result of {@code between} is an + * object that can be used directly in addition/subtraction: + *

+     *   long period = start.periodUntil(end, YEARS);   // this method
+     *   dateTime.plus(YEARS.between(start, end));      // use in plus/minus
+     * 
+ *

+ * The calculation is implemented in this method for {@link ChronoUnit}. + * The units {@code MONTHS}, {@code YEARS}, {@code DECADES}, + * {@code CENTURIES}, {@code MILLENNIA} and {@code ERAS} are supported. + * Other {@code ChronoUnit} values will throw an exception. + *

+ * If the unit is not a {@code ChronoUnit}, then the result of this method + * is obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)} + * passing {@code this} as the first argument and the input temporal as + * the second argument. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param endYearMonth the end year-month, which must be a {@code YearMonth}, not null + * @param unit the unit to measure the period in, not null + * @return the amount of the period between this year-month and the end year-month + * @throws DateTimeException if the period cannot be calculated + * @throws ArithmeticException if numeric overflow occurs + */ + @Override + public long periodUntil(Temporal endYearMonth, TemporalUnit unit) { + if (endYearMonth instanceof YearMonth == false) { + Objects.requireNonNull(endYearMonth, "endYearMonth"); + throw new DateTimeException("Unable to calculate period between objects of two different types"); + } + YearMonth end = (YearMonth) endYearMonth; + if (unit instanceof ChronoUnit) { + long monthsUntil = end.getEpochMonth() - getEpochMonth(); // no overflow + switch ((ChronoUnit) unit) { + case MONTHS: return monthsUntil; + case YEARS: return monthsUntil / 12; + case DECADES: return monthsUntil / 120; + case CENTURIES: return monthsUntil / 1200; + case MILLENNIA: return monthsUntil / 12000; + case ERAS: return end.getLong(ERA) - getLong(ERA); + } + throw new DateTimeException("Unsupported unit: " + unit.getName()); + } + return unit.between(this, endYearMonth).getAmount(); + } + + //----------------------------------------------------------------------- + /** + * Returns a date formed from this year-month at the specified day-of-month. + *

+ * This combines this year-month and the specified day-of-month to form a {@code LocalDate}. + * The day-of-month value must be valid for the year-month. + *

+ * This method can be used as part of a chain to produce a date: + *

+     *  LocalDate date = year.atMonth(month).atDay(day);
+     * 
+ *

+ * This instance is immutable and unaffected by this method call. + * + * @param dayOfMonth the day-of-month to use, from 1 to 31 + * @return the date formed from this year-month and the specified day, not null + * @throws DateTimeException when the day is invalid for the year-month + * @see #isValidDay(int) + */ + public LocalDate atDay(int dayOfMonth) { + return LocalDate.of(year, month, dayOfMonth); + } + + //----------------------------------------------------------------------- + /** + * Compares this year-month to another year-month. + *

+ * The comparison is based first on the value of the year, then on the value of the month. + * It is "consistent with equals", as defined by {@link Comparable}. + * + * @param other the other year-month to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + @Override + public int compareTo(YearMonth other) { + int cmp = (year - other.year); + if (cmp == 0) { + cmp = (month - other.month); + } + return cmp; + } + + /** + * Is this year-month after the specified year-month. + * + * @param other the other year-month to compare to, not null + * @return true if this is after the specified year-month + */ + public boolean isAfter(YearMonth other) { + return compareTo(other) > 0; + } + + /** + * Is this year-month before the specified year-month. + * + * @param other the other year-month to compare to, not null + * @return true if this point is before the specified year-month + */ + public boolean isBefore(YearMonth other) { + return compareTo(other) < 0; + } + + //----------------------------------------------------------------------- + /** + * Checks if this year-month is equal to another year-month. + *

+ * The comparison is based on the time-line position of the year-months. + * + * @param obj the object to check, null returns false + * @return true if this is equal to the other year-month + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof YearMonth) { + YearMonth other = (YearMonth) obj; + return year == other.year && month == other.month; + } + return false; + } + + /** + * A hash code for this year-month. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return year ^ (month << 27); + } + + //----------------------------------------------------------------------- + /** + * Outputs this year-month as a {@code String}, such as {@code 2007-12}. + *

+ * The output will be in the format {@code yyyy-MM}: + * + * @return a string representation of this year-month, not null + */ + @Override + public String toString() { + int absYear = Math.abs(year); + StringBuilder buf = new StringBuilder(9); + if (absYear < 1000) { + if (year < 0) { + buf.append(year - 10000).deleteCharAt(1); + } else { + buf.append(year + 10000).deleteCharAt(0); + } + } else { + buf.append(year); + } + return buf.append(month < 10 ? "-0" : "-") + .append(month) + .toString(); + } + + /** + * Outputs this year-month as a {@code String} using the formatter. + *

+ * This year-month will be passed to the formatter + * {@link DateTimeFormatter#print(TemporalAccessor) print method}. + * + * @param formatter the formatter to use, not null + * @return the formatted year-month string, not null + * @throws DateTimeException if an error occurs during printing + */ + public String toString(DateTimeFormatter formatter) { + Objects.requireNonNull(formatter, "formatter"); + return formatter.print(this); + } + + //----------------------------------------------------------------------- + /** + * Writes the object using a + * dedicated serialized form. + *

+     *  out.writeByte(5);  // identifies this as a Year
+     *  out.writeInt(year);
+     *  out.writeByte(month);
+     * 
+ * + * @return the instance of {@code Ser}, not null + */ + private Object writeReplace() { + return new Ser(Ser.YEAR_MONTH_TYPE, this); + } + + /** + * Defend against malicious streams. + * @return never + * @throws InvalidObjectException always + */ + private Object readResolve() throws ObjectStreamException { + throw new InvalidObjectException("Deserialization via serialization delegate"); + } + + void writeExternal(DataOutput out) throws IOException { + out.writeInt(year); + out.writeByte(month); + } + + static YearMonth readExternal(DataInput in) throws IOException { + int year = in.readInt(); + byte month = in.readByte(); + return YearMonth.of(year, month); + } + +} diff --git a/src/share/classes/java/time/temporal/package-info.java b/src/share/classes/java/time/temporal/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..da9a4b702e8b6a1952148191dcef15e9d41da468 --- /dev/null +++ b/src/share/classes/java/time/temporal/package-info.java @@ -0,0 +1,214 @@ +/* + * 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) 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. + */ + +/** + *

+ * Access to date and time using fields and units, additional value type classes and + * base support for calendar systems other than the default ISO. + *

+ *

+ * This package expands on the base package to provide additional functionality for + * more powerful use cases. Support is included for: + *

+ *
    + *
  • Units of date-time, such as years, months, days and hours
  • + *
  • Fields of date-time, such as month-of-year, day-of-week or hour-of-day
  • + *
  • Date-time adjustment functions
  • + *
  • Different definitions of weeks
  • + *
  • Alternate calendar systems
  • + *
  • Additional value types
  • + *
+ * + *

Fields and Units

+ *

+ * Dates and times are expressed in terms of fields and units. + * A unit is used to measure an amount of time, such as years, days or minutes. + * All units implement {@link java.time.temporal.TemporalUnit}. + * The set of well known units is defined in {@link java.time.temporal.ChronoUnit}, such as {@code DAYS}. + * The unit interface is designed to allow applications defined units. + *

+ *

+ * A field is used to express part of a larger date-time, such as year, month-of-year or second-of-minute. + * All fields implement {@link java.time.temporal.TemporalField}. + * The set of well known fields are defined in {@link java.time.temporal.ChronoField}, such as {@code HOUR_OF_DAY}. + * Additional fields are defined by {@link java.time.temporal.JulianFields}, {@link java.time.temporal.WeekFields} + * and {@link java.time.temporal.ISOFields}. + * The field interface is designed to allow applications defined fields. + *

+ *

+ * This package provides tools that allow the units and fields of date and time to be accessed + * in a general way most suited for frameworks. + * {@link java.time.temporal.Temporal} provides the abstraction for date time types that support fields. + * Its methods support getting the value of a field, creating a new date time with the value of + * a field modified, and querying for additional information, typically used to extract the offset or time-zone. + *

+ *

+ * One use of fields in application code is to retrieve fields for which there is no convenience method. + * For example, getting the day-of-month is common enough that there is a method on {@code LocalDate} + * called {@code getDayOfMonth()}. However for more unusual fields it is necessary to use the field. + * For example, {@code date.get(ChronoField.ALIGNED_WEEK_OF_MONTH)}. + * The fields also provide access to the range of valid values. + *

+ * + *

Adjustment and Query

+ *

+ * A key part of the date-time problem space is adjusting a date to a new, related value, + * such as the "last day of the month", or "next Wednesday". + * These are modeled as functions that adjust a base date-time. + * The functions implement {@link java.time.temporal.TemporalAdjuster} and operate on {@code Temporal}. + * A set of common functions are provided in {@link java.time.temporal.Adjusters}. + * For example, to find the first occurrence of a day-of-week after a given date, use + * {@link java.time.temporal.Adjusters#next(DayOfWeek)}, such as + * {@code date.with(next(MONDAY))}. + * Applications can also define adjusters by implementing {@code TemporalAdjuster}. + *

+ *

+ * There are additional interfaces to model addition to and subtraction from a date-time. + * These are {@link java.time.temporal.TemporalAdder} and {@link java.time.temporal.TemporalSubtractor}. + *

+ *

+ * In addition to adjusting a date-time, an interface is provided to enable querying - + * {@link java.time.temporal.TemporalQuery}. + * The most common implementations of the query interface are method references. + * The {@code from(TemporalAccessor)} methods on major classes can all be used, such as + * {@code LocalDate::from} or {@code Month::from}. + * Further implementations are provided in {@link java.time.temporal.Queries}. + * Applications can also define queries by implementing {@code TemporalQuery}. + *

+ * + *

Weeks

+ *

+ * Different locales have different definitions of the week. + * For example, in Europe the week typically starts on a Monday, while in the US it starts on a Sunday. + * The {@link java.time.temporal.WeekFields} class models this distinction. + *

+ *

+ * The ISO calendar system defines an additional week-based division of years. + * This defines a year based on whole Monday to Monday weeks. + * This is modeled in {@link java.time.temporal.ISOFields}. + *

+ * + *

Alternate calendar systems

+ *

+ * The main API is based around the calendar system defined in ISO-8601. + * However, there are other calendar systems, and this package provides basic support for them. + * The alternate calendars are provided in the {@code java.time.calendar} package. + *

+ *

+ * A calendar system is defined by the {@link java.time.temporal.Chrono} interface, + * while a date in a calendar system is defined by the {@link java.time.temporal.ChronoLocalDate} interface. + *

+ *

+ * It is intended that applications use the main API whenever possible, including code to read and write + * from a persistent data store, such as a database, and to send dates and times across a network. + * The "chrono" classes are then used at the user interface level to deal with localized input/output. + *

+ *

+ * Using non-ISO calendar systems in an application introduces significant extra complexity. + * Ensure that the warnings and recommendations in {@code ChronoLocalDate} have been read before + * working with the "chrono" interfaces. + *

+ *

+ * This example creates and uses a date in a non-ISO calendar system. + *

+ *
+ *   // Print the Thai Buddhist date
+ *       ChronoLocalDate<ThaiBuddhistChrono> now1 = ThaiBuddhistChrono.INSTANCE.dateNow();
+ *       int day = now1.get(ChronoField.DAY_OF_MONTH);
+ *       int dow = now1.get(ChronoField.DAY_OF_WEEK);
+ *       int month = now1.get(ChronoField.MONTH_OF_YEAR);
+ *       int year = now1.get(ChronoField.YEAR);
+ *       System.out.printf("  Today is %s %s %d-%s-%d%n", now1.getChrono().getId(),
+ *                 dow, day, month, year);
+ *
+ *   // Enumerate the list of available calendars and print today for each
+ *       Set<Chrono<?>> chronos = Chrono.getAvailableChronologies();
+ *       for (Chrono<?> chrono : chronos) {
+ *         ChronoLocalDate<?> date = chrono.dateNow();
+ *         System.out.printf("   %20s: %s%n", chrono.getId(), date.toString());
+ *       }
+ *
+ *   // Print today's date and the last day of the year for the Thai Buddhist Calendar.
+ *       ChronoLocalDate<ThaiBuddhistChrono> first = now1
+ *                 .with(ChronoField.DAY_OF_MONTH, 1)
+ *                 .with(ChronoField.MONTH_OF_YEAR, 1);
+ *       ChronoLocalDate<ThaiBuddhistChrono> last = first
+ *                 .plus(1, ChronoUnit.YEARS)
+ *                 .minus(1, ChronoUnit.DAYS);
+ *       System.out.printf("  %s: 1st of year: %s; end of year: %s%n", last.getChrono().getId(),
+ *                 first, last);
+ *  
+ * + *

Package specification

+ *

+ * Unless otherwise noted, passing a null argument to a constructor or method in any class or interface + * in this package will cause a {@link java.lang.NullPointerException NullPointerException} to be thrown. + * The Javadoc "@param" definition is used to summarise the null-behavior. + * The "@throws {@link java.lang.NullPointerException}" is not explicitly documented in each method. + *

+ *

+ * All calculations should check for numeric overflow and throw either an {@link java.lang.ArithmeticException} + * or a {@link java.time.DateTimeException}. + *

+ * @since JDK1.8 + */ +package java.time.temporal; diff --git a/src/share/classes/java/time/zone/Ser.java b/src/share/classes/java/time/zone/Ser.java new file mode 100644 index 0000000000000000000000000000000000000000..7d5c0c6719b250c8617eee0d6f11b83cdd8477f7 --- /dev/null +++ b/src/share/classes/java/time/zone/Ser.java @@ -0,0 +1,256 @@ +/* + * 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) 2011-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 java.time.zone; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.Externalizable; +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.StreamCorruptedException; +import java.time.ZoneOffset; + +/** + * The shared serialization delegate for this package. + * + *

Implementation notes

+ * This class is mutable and should be created once per serialization. + * + * @serial include + * @since 1.8 + */ +final class Ser implements Externalizable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = -8885321777449118786L; + + /** Type for ZoneRules. */ + static final byte ZRULES = 1; + /** Type for ZoneOffsetTransition. */ + static final byte ZOT = 2; + /** Type for ZoneOffsetTransition. */ + static final byte ZOTRULE = 3; + + /** The type being serialized. */ + private byte type; + /** The object being serialized. */ + private Object object; + + /** + * Constructor for deserialization. + */ + public Ser() { + } + + /** + * Creates an instance for serialization. + * + * @param type the type + * @param object the object + */ + Ser(byte type, Object object) { + this.type = type; + this.object = object; + } + + //----------------------------------------------------------------------- + /** + * Implements the {@code Externalizable} interface to write the object. + * + * @param out the data stream to write to, not null + */ + public void writeExternal(ObjectOutput out) throws IOException { + writeInternal(type, object, out); + } + + static void write(Object object, DataOutput out) throws IOException { + writeInternal(ZRULES, object, out); + } + + private static void writeInternal(byte type, Object object, DataOutput out) throws IOException { + out.writeByte(type); + switch (type) { + case ZRULES: + ((ZoneRules) object).writeExternal(out); + break; + case ZOT: + ((ZoneOffsetTransition) object).writeExternal(out); + break; + case ZOTRULE: + ((ZoneOffsetTransitionRule) object).writeExternal(out); + break; + default: + throw new InvalidClassException("Unknown serialized type"); + } + } + + //----------------------------------------------------------------------- + /** + * Implements the {@code Externalizable} interface to read the object. + * + * @param in the data to read, not null + */ + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + type = in.readByte(); + object = readInternal(type, in); + } + + static Object read(DataInput in) throws IOException, ClassNotFoundException { + byte type = in.readByte(); + return readInternal(type, in); + } + + private static Object readInternal(byte type, DataInput in) throws IOException, ClassNotFoundException { + switch (type) { + case ZRULES: + return ZoneRules.readExternal(in); + case ZOT: + return ZoneOffsetTransition.readExternal(in); + case ZOTRULE: + return ZoneOffsetTransitionRule.readExternal(in); + default: + throw new StreamCorruptedException("Unknown serialized type"); + } + } + + /** + * Returns the object that will replace this one. + * + * @return the read object, should never be null + */ + private Object readResolve() { + return object; + } + + //----------------------------------------------------------------------- + /** + * Writes the state to the stream. + * + * @param offset the offset, not null + * @param out the output stream, not null + * @throws IOException if an error occurs + */ + static void writeOffset(ZoneOffset offset, DataOutput out) throws IOException { + final int offsetSecs = offset.getTotalSeconds(); + int offsetByte = offsetSecs % 900 == 0 ? offsetSecs / 900 : 127; // compress to -72 to +72 + out.writeByte(offsetByte); + if (offsetByte == 127) { + out.writeInt(offsetSecs); + } + } + + /** + * Reads the state from the stream. + * + * @param in the input stream, not null + * @return the created object, not null + * @throws IOException if an error occurs + */ + static ZoneOffset readOffset(DataInput in) throws IOException { + int offsetByte = in.readByte(); + return (offsetByte == 127 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds(offsetByte * 900)); + } + + //----------------------------------------------------------------------- + /** + * Writes the state to the stream. + * + * @param epochSec the epoch seconds, not null + * @param out the output stream, not null + * @throws IOException if an error occurs + */ + static void writeEpochSec(long epochSec, DataOutput out) throws IOException { + if (epochSec >= -4575744000L && epochSec < 10413792000L && epochSec % 900 == 0) { // quarter hours between 1825 and 2300 + int store = (int) ((epochSec + 4575744000L) / 900); + out.writeByte((store >>> 16) & 255); + out.writeByte((store >>> 8) & 255); + out.writeByte(store & 255); + } else { + out.writeByte(255); + out.writeLong(epochSec); + } + } + + /** + * Reads the state from the stream. + * + * @param in the input stream, not null + * @return the epoch seconds, not null + * @throws IOException if an error occurs + */ + static long readEpochSec(DataInput in) throws IOException { + int hiByte = in.readByte() & 255; + if (hiByte == 255) { + return in.readLong(); + } else { + int midByte = in.readByte() & 255; + int loByte = in.readByte() & 255; + long tot = ((hiByte << 16) + (midByte << 8) + loByte); + return (tot * 900) - 4575744000L; + } + } + +} diff --git a/src/share/classes/java/time/zone/TzdbZoneRulesProvider.java b/src/share/classes/java/time/zone/TzdbZoneRulesProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..ed81a06dae269c03e2db339f157cd711daacd9cb --- /dev/null +++ b/src/share/classes/java/time/zone/TzdbZoneRulesProvider.java @@ -0,0 +1,298 @@ +/* + * 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 java.time.zone; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.File; +import java.io.IOException; +import java.io.StreamCorruptedException; +import java.nio.file.FileSystems; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; +import java.time.DateTimeException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentNavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.zip.ZipFile; + +/** + * Loads time-zone rules for 'TZDB'. + *

+ * This class is public for the service loader to access. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +final class TzdbZoneRulesProvider extends ZoneRulesProvider { + // service loader seems to need it to be public + + /** + * All the regions that are available. + */ + private final Set regionIds = new CopyOnWriteArraySet<>(); + /** + * All the versions that are available. + */ + private final ConcurrentNavigableMap versions = new ConcurrentSkipListMap<>(); + + /** + * Creates an instance. + * Created by the {@code ServiceLoader}. + * + * @throws ZoneRulesException if unable to load + */ + public TzdbZoneRulesProvider() { + super(); + if (load(ClassLoader.getSystemClassLoader()) == false) { + throw new ZoneRulesException("No time-zone rules found for 'TZDB'"); + } + } + + //----------------------------------------------------------------------- + @Override + protected Set provideZoneIds() { + return new HashSet<>(regionIds); + } + + @Override + protected ZoneRules provideRules(String zoneId) { + Objects.requireNonNull(zoneId, "zoneId"); + ZoneRules rules = versions.lastEntry().getValue().getRules(zoneId); + if (rules == null) { + throw new ZoneRulesException("Unknown time-zone ID: " + zoneId); + } + return rules; + } + + @Override + protected NavigableMap provideVersions(String zoneId) { + TreeMap map = new TreeMap<>(); + for (Version version : versions.values()) { + ZoneRules rules = version.getRules(zoneId); + if (rules != null) { + map.put(version.versionId, rules); + } + } + return map; + } + + //------------------------------------------------------------------------- + /** + * Loads the rules. + * + * @param classLoader the class loader to use, not null + * @return true if updated + * @throws ZoneRulesException if unable to load + */ + private boolean load(ClassLoader classLoader) { + Object updated = Boolean.FALSE; + try { + updated = AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws IOException, ClassNotFoundException { + File tzdbJar = null; + // TBD: workaround for now, so test/java/time tests can be + // run against Java runtime that does not have tzdb + String tzdbProp = System.getProperty("java.time.zone.tzdbjar"); + if (tzdbProp != null) { + tzdbJar = new File(tzdbProp); + } else { + String libDir = System.getProperty("java.home") + File.separator + "lib"; + try { + libDir = FileSystems.getDefault().getPath(libDir).toRealPath().toString(); + } catch(Exception e) {} + tzdbJar = new File(libDir, "tzdb.jar"); + } + try (ZipFile zf = new ZipFile(tzdbJar); + DataInputStream dis = new DataInputStream( + zf.getInputStream(zf.getEntry("TZDB.dat")))) { + Iterable loadedVersions = load(dis); + for (Version loadedVersion : loadedVersions) { + if (versions.putIfAbsent(loadedVersion.versionId, loadedVersion) != null) { + throw new DateTimeException( + "Data already loaded for TZDB time-zone rules version: " + + loadedVersion.versionId); + } + } + } + return Boolean.TRUE; + } + }); + } catch (Exception ex) { + throw new ZoneRulesException("Unable to load TZDB time-zone rules", ex); + } + return updated == Boolean.TRUE; + } + + /** + * Loads the rules from a DateInputStream, often in a jar file. + * + * @param dis the DateInputStream to load, not null + * @throws Exception if an error occurs + */ + private Iterable load(DataInputStream dis) throws ClassNotFoundException, IOException { + if (dis.readByte() != 1) { + throw new StreamCorruptedException("File format not recognised"); + } + // group + String groupId = dis.readUTF(); + if ("TZDB".equals(groupId) == false) { + throw new StreamCorruptedException("File format not recognised"); + } + // versions + int versionCount = dis.readShort(); + String[] versionArray = new String[versionCount]; + for (int i = 0; i < versionCount; i++) { + versionArray[i] = dis.readUTF(); + } + // regions + int regionCount = dis.readShort(); + String[] regionArray = new String[regionCount]; + for (int i = 0; i < regionCount; i++) { + regionArray[i] = dis.readUTF(); + } + regionIds.addAll(Arrays.asList(regionArray)); + // rules + int ruleCount = dis.readShort(); + Object[] ruleArray = new Object[ruleCount]; + for (int i = 0; i < ruleCount; i++) { + byte[] bytes = new byte[dis.readShort()]; + dis.readFully(bytes); + ruleArray[i] = bytes; + } + AtomicReferenceArray ruleData = new AtomicReferenceArray<>(ruleArray); + // link version-region-rules + Set versionSet = new HashSet(versionCount); + for (int i = 0; i < versionCount; i++) { + int versionRegionCount = dis.readShort(); + String[] versionRegionArray = new String[versionRegionCount]; + short[] versionRulesArray = new short[versionRegionCount]; + for (int j = 0; j < versionRegionCount; j++) { + versionRegionArray[j] = regionArray[dis.readShort()]; + versionRulesArray[j] = dis.readShort(); + } + versionSet.add(new Version(versionArray[i], versionRegionArray, versionRulesArray, ruleData)); + } + return versionSet; + } + + @Override + public String toString() { + return "TZDB"; + } + + //----------------------------------------------------------------------- + /** + * A version of the TZDB rules. + */ + static class Version { + private final String versionId; + private final String[] regionArray; + private final short[] ruleIndices; + private final AtomicReferenceArray ruleData; + + Version(String versionId, String[] regionIds, short[] ruleIndices, AtomicReferenceArray ruleData) { + this.ruleData = ruleData; + this.versionId = versionId; + this.regionArray = regionIds; + this.ruleIndices = ruleIndices; + } + + ZoneRules getRules(String regionId) { + int regionIndex = Arrays.binarySearch(regionArray, regionId); + if (regionIndex < 0) { + return null; + } + try { + return createRule(ruleIndices[regionIndex]); + } catch (Exception ex) { + throw new ZoneRulesException("Invalid binary time-zone data: TZDB:" + regionId + ", version: " + versionId, ex); + } + } + + ZoneRules createRule(short index) throws Exception { + Object obj = ruleData.get(index); + if (obj instanceof byte[]) { + byte[] bytes = (byte[]) obj; + DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes)); + obj = Ser.read(dis); + ruleData.set(index, obj); + } + return (ZoneRules) obj; + } + + @Override + public String toString() { + return versionId; + } + } + +} diff --git a/src/share/classes/java/time/zone/ZoneOffsetTransition.java b/src/share/classes/java/time/zone/ZoneOffsetTransition.java new file mode 100644 index 0000000000000000000000000000000000000000..6d080aba27dd8e1f2be17197f878401924ceba68 --- /dev/null +++ b/src/share/classes/java/time/zone/ZoneOffsetTransition.java @@ -0,0 +1,431 @@ +/* + * 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 java.time.zone; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.Serializable; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +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. + *

+ * A transition between two offsets is normally the result of a daylight savings cutover. + * The discontinuity is normally a gap in spring and an overlap in autumn. + * {@code ZoneOffsetTransition} models the transition between the two offsets. + *

+ * Gaps occur where there are local date-times that simply do not not exist. + * An example would be when the offset changes from {@code +03:00} to {@code +04:00}. + * This might be described as 'the clocks will move forward one hour tonight at 1am'. + *

+ * Overlaps occur where there are local date-times that exist twice. + * An example would be when the offset changes from {@code +04:00} to {@code +03:00}. + * This might be described as 'the clocks will move back one hour tonight at 2am'. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class ZoneOffsetTransition + implements Comparable, Serializable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = -6946044323557704546L; + /** + * 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; + + //----------------------------------------------------------------------- + /** + * Obtains an instance defining a transition between two offsets. + *

+ * Applications should normally obtain an instance from {@link ZoneRules}. + * This factory is only intended for use when creating {@link ZoneRules}. + * + * @param transition the transition date-time at the transition, which never + * actually occurs, expressed local to the before offset, not null + * @param offsetBefore the offset before the transition, not null + * @param offsetAfter the offset at and after the transition, not null + * @return the transition, not null + * @throws IllegalArgumentException if {@code offsetBefore} and {@code offsetAfter} + * are equal, or {@code transition.getNano()} returns non-zero value + */ + public static ZoneOffsetTransition of(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"); + } + if (transition.getNano() != 0) { + throw new IllegalArgumentException("Nano-of-second must be zero"); + } + return new ZoneOffsetTransition(transition, offsetBefore, 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) { + 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; + } + + //----------------------------------------------------------------------- + /** + * Uses a serialization delegate. + * + * @return the replacing object, not null + */ + private Object writeReplace() { + return new Ser(Ser.ZOT, this); + } + + /** + * Writes the state to the stream. + * + * @param out the output stream, not null + * @throws IOException if an error occurs + */ + void writeExternal(DataOutput out) throws IOException { + Ser.writeEpochSec(toEpochSecond(), out); + Ser.writeOffset(offsetBefore, out); + Ser.writeOffset(offsetAfter, out); + } + + /** + * Reads the state from the stream. + * + * @param in the input stream, not null + * @return the created object, not null + * @throws IOException if an error occurs + */ + static ZoneOffsetTransition readExternal(DataInput in) throws IOException { + long epochSecond = Ser.readEpochSec(in); + ZoneOffset before = Ser.readOffset(in); + ZoneOffset after = Ser.readOffset(in); + if (before.equals(after)) { + throw new IllegalArgumentException("Offsets must not be equal"); + } + return new ZoneOffsetTransition(epochSecond, before, after); + } + + //----------------------------------------------------------------------- + /** + * Gets the transition instant. + *

+ * This is the instant of the discontinuity, which is defined as the first + * instant that the 'after' offset applies. + *

+ * The methods {@link #getInstant()}, {@link #getDateTimeBefore()} and {@link #getDateTimeAfter()} + * all represent the same instant. + * + * @return the transition instant, not null + */ + public Instant getInstant() { + return transition.toInstant(offsetBefore); + } + + /** + * 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. + *

+ * 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. + *

+ * 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. + *

+ * This is the first date-time after the discontinuity, when the new offset applies. + *

+ * 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. + *

+ * 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. + *

+ * 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 most cases, the transition duration is one hour, however this is not always the case. + * The duration will be positive for a gap and negative for an overlap. + * Time-zones are second-based, so the nanosecond part of the duration will be zero. + * + * @return the duration of the transition, positive for gaps, negative for overlaps + */ + public Duration getDuration() { + return Duration.ofSeconds(getDurationSeconds()); + } + + /** + * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * A gap will return an empty list, while an overlap will return both offsets. + * + * @return the list of valid offsets + */ + List getValidOffsets() { + if (isGap()) { + return Collections.emptyList(); + } + return Arrays.asList(getOffsetBefore(), getOffsetAfter()); + } + + //----------------------------------------------------------------------- + /** + * Compares this transition to another based on the transition instant. + *

+ * 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 this.getInstant().compareTo(transition.getInstant()); + } + + //----------------------------------------------------------------------- + /** + * Checks if this object equals another. + *

+ * 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); + } + + //----------------------------------------------------------------------- + /** + * Returns a string describing this object. + * + * @return a string for debugging, not null + */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("Transition[") + .append(isGap() ? "Gap" : "Overlap") + .append(" at ") + .append(transition) + .append(offsetBefore) + .append(" to ") + .append(offsetAfter) + .append(']'); + return buf.toString(); + } + +} diff --git a/src/share/classes/java/time/zone/ZoneOffsetTransitionRule.java b/src/share/classes/java/time/zone/ZoneOffsetTransitionRule.java new file mode 100644 index 0000000000000000000000000000000000000000..4b6f7e90722fe9331f5f25a518c784ec351fac28 --- /dev/null +++ b/src/share/classes/java/time/zone/ZoneOffsetTransitionRule.java @@ -0,0 +1,575 @@ +/* + * 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 java.time.zone; + +import static java.time.temporal.Adjusters.nextOrSame; +import static java.time.temporal.Adjusters.previousOrSame; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.Serializable; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneOffset; +import java.time.temporal.ISOChrono; +import java.util.Objects; + +/** + * A rule expressing how to create a transition. + *

+ * This class allows rules for identifying future transitions to be expressed. + * A rule might be written in many forms: + *

    + *
  • the 16th March + *
  • the Sunday on or after the 16th March + *
  • the Sunday on or before the 16th March + *
  • the last Sunday in February + *

+ * These different rule types can be expressed and queried. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class ZoneOffsetTransitionRule implements Serializable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = 6889046316657758795L; + + /** + * The month of the month-day of the first day of the cutover week. + * The actual date will be adjusted by the dowChange field. + */ + private final Month 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. + */ + private final byte dom; + /** + * The cutover day-of-week, null to retain the day-of-month. + */ + private final DayOfWeek dow; + /** + * The cutover time in the 'before' offset. + */ + private final LocalTime time; + /** + * Whether the cutover time is midnight at the end of day. + */ + private final boolean timeEndOfDay; + /** + * The definition of how the local time should be interpreted. + */ + private final TimeDefinition timeDefinition; + /** + * The standard offset at the cutover. + */ + private final ZoneOffset standardOffset; + /** + * The offset before the cutover. + */ + private final ZoneOffset offsetBefore; + /** + * The offset after the cutover. + */ + private final ZoneOffset offsetAfter; + + /** + * Obtains an instance defining the yearly rule to create transitions between two offsets. + *

+ * Applications should normally obtain an instance from {@link ZoneRules}. + * This factory is only intended for use when creating {@link ZoneRules}. + * + * @param month the month of the month-day of the first day of the cutover week, not null + * @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, null 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 + * @return the rule, 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 + */ + public static ZoneOffsetTransitionRule of( + Month month, + int dayOfMonthIndicator, + DayOfWeek dayOfWeek, + LocalTime time, + boolean timeEndOfDay, + TimeDefinition timeDefnition, + ZoneOffset standardOffset, + ZoneOffset offsetBefore, + ZoneOffset offsetAfter) { + Objects.requireNonNull(month, "month"); + Objects.requireNonNull(time, "time"); + Objects.requireNonNull(timeDefnition, "timeDefnition"); + Objects.requireNonNull(standardOffset, "standardOffset"); + Objects.requireNonNull(offsetBefore, "offsetBefore"); + Objects.requireNonNull(offsetAfter, "offsetAfter"); + 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"); + } + return new ZoneOffsetTransitionRule(month, dayOfMonthIndicator, dayOfWeek, time, timeEndOfDay, timeDefnition, standardOffset, offsetBefore, 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, not null + * @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, null 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( + Month month, + int dayOfMonthIndicator, + DayOfWeek dayOfWeek, + LocalTime time, + boolean timeEndOfDay, + TimeDefinition timeDefnition, + ZoneOffset standardOffset, + ZoneOffset offsetBefore, + ZoneOffset offsetAfter) { + 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; + } + + //----------------------------------------------------------------------- + /** + * Uses a serialization delegate. + * + * @return the replacing object, not null + */ + private Object writeReplace() { + return new Ser(Ser.ZOTRULE, this); + } + + /** + * Writes the state to the stream. + * + * @param out the output stream, not null + * @throws IOException if an error occurs + */ + void writeExternal(DataOutput out) throws IOException { + final int timeSecs = (timeEndOfDay ? 86400 : time.toSecondOfDay()); + final int stdOffset = standardOffset.getTotalSeconds(); + final int beforeDiff = offsetBefore.getTotalSeconds() - stdOffset; + final int afterDiff = offsetAfter.getTotalSeconds() - stdOffset; + final int timeByte = (timeSecs % 3600 == 0 ? (timeEndOfDay ? 24 : time.getHour()) : 31); + final int stdOffsetByte = (stdOffset % 900 == 0 ? stdOffset / 900 + 128 : 255); + final int beforeByte = (beforeDiff == 0 || beforeDiff == 1800 || beforeDiff == 3600 ? beforeDiff / 1800 : 3); + final int afterByte = (afterDiff == 0 || afterDiff == 1800 || afterDiff == 3600 ? afterDiff / 1800 : 3); + final int dowByte = (dow == null ? 0 : dow.getValue()); + int b = (month.getValue() << 28) + // 4 bits + ((dom + 32) << 22) + // 6 bits + (dowByte << 19) + // 3 bits + (timeByte << 14) + // 5 bits + (timeDefinition.ordinal() << 12) + // 2 bits + (stdOffsetByte << 4) + // 8 bits + (beforeByte << 2) + // 2 bits + afterByte; // 2 bits + out.writeInt(b); + if (timeByte == 31) { + out.writeInt(timeSecs); + } + if (stdOffsetByte == 255) { + out.writeInt(stdOffset); + } + if (beforeByte == 3) { + out.writeInt(offsetBefore.getTotalSeconds()); + } + if (afterByte == 3) { + out.writeInt(offsetAfter.getTotalSeconds()); + } + } + + /** + * Reads the state from the stream. + * + * @param in the input stream, not null + * @return the created object, not null + * @throws IOException if an error occurs + */ + static ZoneOffsetTransitionRule readExternal(DataInput in) throws IOException { + int data = in.readInt(); + Month month = Month.of(data >>> 28); + int dom = ((data & (63 << 22)) >>> 22) - 32; + int dowByte = (data & (7 << 19)) >>> 19; + DayOfWeek dow = dowByte == 0 ? null : DayOfWeek.of(dowByte); + int timeByte = (data & (31 << 14)) >>> 14; + TimeDefinition defn = TimeDefinition.values()[(data & (3 << 12)) >>> 12]; + int stdByte = (data & (255 << 4)) >>> 4; + int beforeByte = (data & (3 << 2)) >>> 2; + int afterByte = (data & 3); + LocalTime time = (timeByte == 31 ? LocalTime.ofSecondOfDay(in.readInt()) : LocalTime.of(timeByte % 24, 0)); + ZoneOffset std = (stdByte == 255 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds((stdByte - 128) * 900)); + ZoneOffset before = (beforeByte == 3 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds(std.getTotalSeconds() + beforeByte * 1800)); + ZoneOffset after = (afterByte == 3 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds(std.getTotalSeconds() + afterByte * 1800)); + return ZoneOffsetTransitionRule.of(month, dom, dow, time, timeByte == 24, defn, std, before, after); + } + + //----------------------------------------------------------------------- + /** + * Gets the month of the transition. + *

+ * If the rule defines an exact date then the month is the month of that date. + *

+ * If the rule defines a week where the transition might occur, then the month + * if the month of either the earliest or latest possible date of the cutover. + * + * @return the month of the transition, not null + */ + public Month getMonth() { + return month; + } + + /** + * Gets the indicator of the day-of-month of the transition. + *

+ * If the rule defines an exact date then the day is the month of that date. + *

+ * If the rule defines a week where the transition might occur, then the day + * defines either the start of the end of the transition week. + *

+ * If the value is positive, then it represents a normal day-of-month, and is the + * earliest possible date that the transition can be. + * The date may refer to 29th February which should be treated as 1st March in non-leap years. + *

+ * If the value is negative, then it represents the number of days back from the + * end of the month where {@code -1} is the last day of the month. + * In this case, the day identified is the latest possible date that the transition can be. + * + * @return the day-of-month indicator, from -28 to 31 excluding 0 + */ + public int getDayOfMonthIndicator() { + return dom; + } + + /** + * Gets the day-of-week of the transition. + *

+ * If the rule defines an exact date then this returns null. + *

+ * If the rule defines a week where the cutover might occur, then this method + * returns the day-of-week that the month-day will be adjusted to. + * If the day is positive then the adjustment is later. + * If the day is negative then the adjustment is earlier. + * + * @return the day-of-week that the transition occurs, null if the rule defines an exact date + */ + public DayOfWeek getDayOfWeek() { + return dow; + } + + /** + * Gets the local time of day of the transition which must be checked with + * {@link #isMidnightEndOfDay()}. + *

+ * The time is converted into an instant using the time definition. + * + * @return the local time of day of the transition, not null + */ + public LocalTime getLocalTime() { + return time; + } + + /** + * Is the transition local time midnight at the end of day. + *

+ * The transition may be represented as occurring at 24:00. + * + * @return whether a local time of midnight is at the start or end of the day + */ + public boolean isMidnightEndOfDay() { + return timeEndOfDay; + } + + /** + * Gets the time definition, specifying how to convert the time to an instant. + *

+ * The local time can be converted to an instant using the standard offset, + * the wall offset or UTC. + * + * @return the time definition, not null + */ + public TimeDefinition getTimeDefinition() { + return timeDefinition; + } + + /** + * Gets the standard offset in force at the transition. + * + * @return the standard offset, not null + */ + public ZoneOffset getStandardOffset() { + return standardOffset; + } + + /** + * Gets the offset before the transition. + * + * @return the offset before, not null + */ + public ZoneOffset getOffsetBefore() { + return offsetBefore; + } + + /** + * Gets the offset after the transition. + * + * @return the offset after, not null + */ + public ZoneOffset getOffsetAfter() { + return offsetAfter; + } + + //----------------------------------------------------------------------- + /** + * Creates a transition instance for the specified year. + *

+ * Calculations are performed using the ISO-8601 chronology. + * + * @param year the year to create a transition for, not null + * @return the transition instance, not null + */ + public ZoneOffsetTransition createTransition(int year) { + LocalDate date; + if (dom < 0) { + date = LocalDate.of(year, month, month.length(ISOChrono.INSTANCE.isLeapYear(year)) + 1 + dom); + if (dow != null) { + date = date.with(previousOrSame(dow)); + } + } else { + date = LocalDate.of(year, month, dom); + if (dow != null) { + date = date.with(nextOrSame(dow)); + } + } + if (timeEndOfDay) { + date = date.plusDays(1); + } + LocalDateTime localDT = LocalDateTime.of(date, time); + LocalDateTime transition = timeDefinition.createDateTime(localDT, standardOffset, offsetBefore); + return new ZoneOffsetTransition(transition, offsetBefore, offsetAfter); + } + + //----------------------------------------------------------------------- + /** + * Checks if this object equals another. + *

+ * 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.ordinal() << 11) + ((dom + 32) << 5) + + ((dow == null ? 7 : dow.ordinal()) << 2) + (timeDefinition.ordinal()); + return hash ^ standardOffset.hashCode() ^ + offsetBefore.hashCode() ^ offsetAfter.hashCode(); + } + + //----------------------------------------------------------------------- + /** + * Returns a string describing this object. + * + * @return a string for debugging, not null + */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("TransitionRule[") + .append(offsetBefore.compareTo(offsetAfter) > 0 ? "Gap " : "Overlap ") + .append(offsetBefore).append(" to ").append(offsetAfter).append(", "); + if (dow != null) { + if (dom == -1) { + buf.append(dow.name()).append(" on or before last day of ").append(month.name()); + } else if (dom < 0) { + buf.append(dow.name()).append(" on or before last day minus ").append(-dom - 1).append(" of ").append(month.name()); + } else { + buf.append(dow.name()).append(" on or after ").append(month.name()).append(' ').append(dom); + } + } else { + buf.append(month.name()).append(' ').append(dom); + } + buf.append(" at ").append(timeEndOfDay ? "24:00" : time.toString()) + .append(" ").append(timeDefinition) + .append(", standard offset ").append(standardOffset) + .append(']'); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * A definition of the way a local time can be converted to the actual + * transition date-time. + *

+ * Time zone rules are expressed in one of three ways: + *

    + *
  • Relative to UTC
  • + *
  • Relative to the standard offset in force
  • + *
  • Relative to the wall offset (what you would see on a clock on the wall)
  • + *

+ */ + public static 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. + *

+ * This method converts using the type of this enum. + * The output is defined relative to the 'before' offset of the transition. + *

+ * 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; + } + } + } + +} diff --git a/src/share/classes/java/time/zone/ZoneRules.java b/src/share/classes/java/time/zone/ZoneRules.java new file mode 100644 index 0000000000000000000000000000000000000000..159d6e0d36aceefa519ab3973ed78d74e979bef1 --- /dev/null +++ b/src/share/classes/java/time/zone/ZoneRules.java @@ -0,0 +1,959 @@ +/* + * 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 java.time.zone; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.Serializable; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.Year; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * The rules defining how the zone offset varies for a single time-zone. + *

+ * The rules model all the historic and future transitions for a time-zone. + * {@link ZoneOffsetTransition} is used for known transitions, typically historic. + * {@link ZoneOffsetTransitionRule} is used for future transitions that are based + * on the result of an algorithm. + *

+ * The rules are loaded via {@link ZoneRulesProvider} using a {@link ZoneId}. + * The same rules may be shared internally between multiple zone IDs. + *

+ * Serializing an instance of {@code ZoneRules} will store the entire set of rules. + * It does not store the zone ID as it is not part of the state of this object. + *

+ * A rule implementation may or may not store full information about historic + * and future transitions, and the information stored is only as accurate as + * that supplied to the implementation by the rules provider. + * Applications should treat the data provided as representing the best information + * available to the implementation of this rule. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + * + * @since 1.8 + */ +public final class ZoneRules implements Serializable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = 3044319355680032515L; + /** + * The last year to have its transitions cached. + */ + private static final int LAST_CACHED_YEAR = 2100; + + /** + * The transitions between standard offsets (epoch seconds), sorted. + */ + private final long[] standardTransitions; + /** + * The standard offsets. + */ + private final ZoneOffset[] standardOffsets; + /** + * The transitions between instants (epoch seconds), sorted. + */ + private final long[] savingsInstantTransitions; + /** + * The transitions between local date-times, sorted. + * This is a paired array, where the first entry is the start of the transition + * and the second entry is the end of the transition. + */ + private final LocalDateTime[] savingsLocalTransitions; + /** + * The wall offsets. + */ + private final ZoneOffset[] wallOffsets; + /** + * The last rule. + */ + private final ZoneOffsetTransitionRule[] lastRules; + /** + * The map of recent transitions. + */ + private final ConcurrentMap lastRulesCache = + new ConcurrentHashMap(); + /** + * The zero-length long array. + */ + private static final long[] EMPTY_LONG_ARRAY = new long[0]; + /** + * The zero-length lastrules array. + */ + private static final ZoneOffsetTransitionRule[] EMPTY_LASTRULES = + new ZoneOffsetTransitionRule[0]; + /** + * The zero-length ldt array. + */ + private static final LocalDateTime[] EMPTY_LDT_ARRAY = new LocalDateTime[0]; + + /** + * Obtains an instance of a ZoneRules. + * + * @param baseStandardOffset the standard offset to use before legal rules were set, not null + * @param baseWallOffset the wall offset to use before legal rules were set, not null + * @param standardOffsetTransitionList the list of changes to the standard offset, not null + * @param transitionList the list of transitions, not null + * @param lastRules the recurring last rules, size 16 or less, not null + * @return the zone rules, not null + */ + public static ZoneRules of(ZoneOffset baseStandardOffset, + ZoneOffset baseWallOffset, + List standardOffsetTransitionList, + List transitionList, + List lastRules) { + Objects.requireNonNull(baseStandardOffset, "baseStandardOffset"); + Objects.requireNonNull(baseWallOffset, "baseWallOffset"); + Objects.requireNonNull(standardOffsetTransitionList, "standardOffsetTransitionList"); + Objects.requireNonNull(transitionList, "transitionList"); + Objects.requireNonNull(lastRules, "lastRules"); + return new ZoneRules(baseStandardOffset, baseWallOffset, + standardOffsetTransitionList, transitionList, lastRules); + } + + /** + * Obtains an instance of ZoneRules that has fixed zone rules. + * + * @param offset the offset this fixed zone rules is based on, not null + * @return the zone rules, not null + * @see #isFixedOffset() + */ + public static ZoneRules of(ZoneOffset offset) { + Objects.requireNonNull(offset, "offset"); + return new ZoneRules(offset); + } + + /** + * Creates an instance. + * + * @param baseStandardOffset the standard offset to use before legal rules were set, not null + * @param baseWallOffset the wall offset to use before legal rules were set, not null + * @param standardOffsetTransitionList the list of changes to the standard offset, not null + * @param transitionList the list of transitions, not null + * @param lastRules the recurring last rules, size 16 or less, not null + */ + ZoneRules(ZoneOffset baseStandardOffset, + ZoneOffset baseWallOffset, + List standardOffsetTransitionList, + List transitionList, + List lastRules) { + super(); + + // convert standard transitions + + this.standardTransitions = new long[standardOffsetTransitionList.size()]; + + this.standardOffsets = new ZoneOffset[standardOffsetTransitionList.size() + 1]; + this.standardOffsets[0] = baseStandardOffset; + for (int i = 0; i < standardOffsetTransitionList.size(); i++) { + this.standardTransitions[i] = standardOffsetTransitionList.get(i).toEpochSecond(); + this.standardOffsets[i + 1] = standardOffsetTransitionList.get(i).getOffsetAfter(); + } + + // convert savings transitions to locals + List localTransitionList = new ArrayList<>(); + List localTransitionOffsetList = new ArrayList<>(); + localTransitionOffsetList.add(baseWallOffset); + for (ZoneOffsetTransition trans : transitionList) { + if (trans.isGap()) { + localTransitionList.add(trans.getDateTimeBefore()); + localTransitionList.add(trans.getDateTimeAfter()); + } else { + localTransitionList.add(trans.getDateTimeAfter()); + localTransitionList.add(trans.getDateTimeBefore()); + } + localTransitionOffsetList.add(trans.getOffsetAfter()); + } + this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]); + this.wallOffsets = localTransitionOffsetList.toArray(new ZoneOffset[localTransitionOffsetList.size()]); + + // convert savings transitions to instants + this.savingsInstantTransitions = new long[transitionList.size()]; + for (int i = 0; i < transitionList.size(); i++) { + this.savingsInstantTransitions[i] = transitionList.get(i).toEpochSecond(); + } + + // last rules + if (lastRules.size() > 16) { + throw new IllegalArgumentException("Too many transition rules"); + } + this.lastRules = lastRules.toArray(new ZoneOffsetTransitionRule[lastRules.size()]); + } + + /** + * Constructor. + * + * @param standardTransitions the standard transitions, not null + * @param standardOffsets the standard offsets, not null + * @param savingsInstantTransitions the standard transitions, not null + * @param wallOffsets the wall offsets, not null + * @param lastRules the recurring last rules, size 15 or less, not null + */ + private ZoneRules(long[] standardTransitions, + ZoneOffset[] standardOffsets, + long[] savingsInstantTransitions, + ZoneOffset[] wallOffsets, + ZoneOffsetTransitionRule[] lastRules) { + super(); + + this.standardTransitions = standardTransitions; + this.standardOffsets = standardOffsets; + this.savingsInstantTransitions = savingsInstantTransitions; + this.wallOffsets = wallOffsets; + this.lastRules = lastRules; + + if (savingsInstantTransitions.length == 0) { + this.savingsLocalTransitions = EMPTY_LDT_ARRAY; + } else { + // convert savings transitions to locals + List localTransitionList = new ArrayList<>(); + for (int i = 0; i < savingsInstantTransitions.length; i++) { + ZoneOffset before = wallOffsets[i]; + ZoneOffset after = wallOffsets[i + 1]; + ZoneOffsetTransition trans = new ZoneOffsetTransition(savingsInstantTransitions[i], before, after); + if (trans.isGap()) { + localTransitionList.add(trans.getDateTimeBefore()); + localTransitionList.add(trans.getDateTimeAfter()); + } else { + localTransitionList.add(trans.getDateTimeAfter()); + localTransitionList.add(trans.getDateTimeBefore()); + } + } + this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]); + } + } + + /** + * Creates an instance of ZoneRules that has fixed zone rules. + * + * @param offset the offset this fixed zone rules is based on, not null + * @return the zone rules, not null + * @see #isFixedOffset() + */ + private ZoneRules(ZoneOffset offset) { + this.standardOffsets = new ZoneOffset[1]; + this.standardOffsets[0] = offset; + this.standardTransitions = EMPTY_LONG_ARRAY; + this.savingsInstantTransitions = EMPTY_LONG_ARRAY; + this.savingsLocalTransitions = EMPTY_LDT_ARRAY; + this.wallOffsets = standardOffsets; + this.lastRules = EMPTY_LASTRULES; + } + + /** + * Uses a serialization delegate. + * + * @return the replacing object, not null + */ + private Object writeReplace() { + return new Ser(Ser.ZRULES, this); + } + + /** + * Writes the state to the stream. + * + * @param out the output stream, not null + * @throws IOException if an error occurs + */ + void writeExternal(DataOutput out) throws IOException { + out.writeInt(standardTransitions.length); + for (long trans : standardTransitions) { + Ser.writeEpochSec(trans, out); + } + for (ZoneOffset offset : standardOffsets) { + Ser.writeOffset(offset, out); + } + out.writeInt(savingsInstantTransitions.length); + for (long trans : savingsInstantTransitions) { + Ser.writeEpochSec(trans, out); + } + for (ZoneOffset offset : wallOffsets) { + Ser.writeOffset(offset, out); + } + out.writeByte(lastRules.length); + for (ZoneOffsetTransitionRule rule : lastRules) { + rule.writeExternal(out); + } + } + + /** + * Reads the state from the stream. + * + * @param in the input stream, not null + * @return the created object, not null + * @throws IOException if an error occurs + */ + static ZoneRules readExternal(DataInput in) throws IOException, ClassNotFoundException { + int stdSize = in.readInt(); + long[] stdTrans = (stdSize == 0) ? EMPTY_LONG_ARRAY + : new long[stdSize]; + for (int i = 0; i < stdSize; i++) { + stdTrans[i] = Ser.readEpochSec(in); + } + ZoneOffset[] stdOffsets = new ZoneOffset[stdSize + 1]; + for (int i = 0; i < stdOffsets.length; i++) { + stdOffsets[i] = Ser.readOffset(in); + } + int savSize = in.readInt(); + long[] savTrans = (savSize == 0) ? EMPTY_LONG_ARRAY + : new long[savSize]; + for (int i = 0; i < savSize; i++) { + savTrans[i] = Ser.readEpochSec(in); + } + ZoneOffset[] savOffsets = new ZoneOffset[savSize + 1]; + for (int i = 0; i < savOffsets.length; i++) { + savOffsets[i] = Ser.readOffset(in); + } + int ruleSize = in.readByte(); + ZoneOffsetTransitionRule[] rules = (ruleSize == 0) ? + EMPTY_LASTRULES : new ZoneOffsetTransitionRule[ruleSize]; + for (int i = 0; i < ruleSize; i++) { + rules[i] = ZoneOffsetTransitionRule.readExternal(in); + } + return new ZoneRules(stdTrans, stdOffsets, savTrans, savOffsets, rules); + } + + /** + * Checks of the zone rules are fixed, such that the offset never varies. + * + * @return true if the time-zone is fixed and the offset never changes + */ + public boolean isFixedOffset() { + return savingsInstantTransitions.length == 0; + } + + /** + * Gets the offset applicable at the specified instant in these rules. + *

+ * The mapping from an instant to an offset is simple, there is only + * one valid offset for each instant. + * This method returns that offset. + * + * @param instant the instant to find the offset for, not null, but null + * may be ignored if the rules have a single offset for all instants + * @return the offset, not null + */ + public ZoneOffset getOffset(Instant instant) { + if (savingsInstantTransitions.length == 0) { + return standardOffsets[0]; + } + long epochSec = instant.getEpochSecond(); + // check if using last rules + if (lastRules.length > 0 && + epochSec > savingsInstantTransitions[savingsInstantTransitions.length - 1]) { + int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]); + ZoneOffsetTransition[] transArray = findTransitionArray(year); + ZoneOffsetTransition trans = null; + for (int i = 0; i < transArray.length; i++) { + trans = transArray[i]; + if (epochSec < trans.toEpochSecond()) { + return trans.getOffsetBefore(); + } + } + return trans.getOffsetAfter(); + } + + // using historic rules + int index = Arrays.binarySearch(savingsInstantTransitions, epochSec); + if (index < 0) { + // switch negative insert position to start of matched range + index = -index - 2; + } + return wallOffsets[index + 1]; + } + + /** + * Gets a suitable offset for the specified local date-time in these rules. + *

+ * The mapping from a local date-time to an offset is not straightforward. + * There are three cases: + *

    + *
  • Normal, with one valid offset. For the vast majority of the year, the normal + * case applies, where there is a single valid offset for the local date-time.
  • + *
  • Gap, with zero valid offsets. This is when clocks jump forward typically + * due to the spring daylight savings change from "winter" to "summer". + * In a gap there are local date-time values with no valid offset.
  • + *
  • Overlap, with two valid offsets. This is when clocks are set back typically + * due to the autumn daylight savings change from "summer" to "winter". + * In an overlap there are local date-time values with two valid offsets.
  • + *

+ * Thus, for any given local date-time there can be zero, one or two valid offsets. + * This method returns the single offset in the Normal case, and in the Gap or Overlap + * case it returns the offset before the transition. + *

+ * Since, in the case of Gap and Overlap, the offset returned is a "best" value, rather + * than the "correct" value, it should be treated with care. Applications that care + * about the correct offset should use a combination of this method, + * {@link #getValidOffsets(LocalDateTime)} and {@link #getTransition(LocalDateTime)}. + * + * @param localDateTime the local date-time to query, not null, but null + * may be ignored if the rules have a single offset for all instants + * @return the best available offset for the local date-time, not null + */ + public ZoneOffset getOffset(LocalDateTime localDateTime) { + Object info = getOffsetInfo(localDateTime); + if (info instanceof ZoneOffsetTransition) { + return ((ZoneOffsetTransition) info).getOffsetBefore(); + } + return (ZoneOffset) info; + } + + /** + * Gets the offset applicable at the specified local date-time in these rules. + *

+ * The mapping from a local date-time to an offset is not straightforward. + * There are three cases: + *

    + *
  • Normal, with one valid offset. For the vast majority of the year, the normal + * case applies, where there is a single valid offset for the local date-time.
  • + *
  • Gap, with zero valid offsets. This is when clocks jump forward typically + * due to the spring daylight savings change from "winter" to "summer". + * In a gap there are local date-time values with no valid offset.
  • + *
  • Overlap, with two valid offsets. This is when clocks are set back typically + * due to the autumn daylight savings change from "summer" to "winter". + * In an overlap there are local date-time values with two valid offsets.
  • + *

+ * Thus, for any given local date-time there can be zero, one or two valid offsets. + * This method returns that list of valid offsets, which is a list of size 0, 1 or 2. + * In the case where there are two offsets, the earlier offset is returned at index 0 + * and the later offset at index 1. + *

+ * There are various ways to handle the conversion from a {@code LocalDateTime}. + * One technique, using this method, would be: + *

+     *  List<ZoneOffset> validOffsets = rules.getOffset(localDT);
+     *  if (validOffsets.size() == 1) {
+     *    // Normal case: only one valid offset
+     *    zoneOffset = validOffsets.get(0);
+     *  } else {
+     *    // Gap or Overlap: determine what to do from transition (which will be non-null)
+     *    ZoneOffsetTransition trans = rules.getTransition(localDT);
+     *  }
+     * 
+ *

+ * In theory, it is possible for there to be more than two valid offsets. + * This would happen if clocks to be put back more than once in quick succession. + * This has never happened in the history of time-zones and thus has no special handling. + * However, if it were to happen, then the list would return more than 2 entries. + * + * @param localDateTime the local date-time to query for valid offsets, not null, but null + * may be ignored if the rules have a single offset for all instants + * @return the list of valid offsets, may be immutable, not null + */ + public List getValidOffsets(LocalDateTime localDateTime) { + // should probably be optimized + Object info = getOffsetInfo(localDateTime); + if (info instanceof ZoneOffsetTransition) { + return ((ZoneOffsetTransition) info).getValidOffsets(); + } + return Collections.singletonList((ZoneOffset) info); + } + + /** + * Gets the offset transition applicable at the specified local date-time in these rules. + *

+ * The mapping from a local date-time to an offset is not straightforward. + * There are three cases: + *

    + *
  • Normal, with one valid offset. For the vast majority of the year, the normal + * case applies, where there is a single valid offset for the local date-time.
  • + *
  • Gap, with zero valid offsets. This is when clocks jump forward typically + * due to the spring daylight savings change from "winter" to "summer". + * In a gap there are local date-time values with no valid offset.
  • + *
  • Overlap, with two valid offsets. This is when clocks are set back typically + * due to the autumn daylight savings change from "summer" to "winter". + * In an overlap there are local date-time values with two valid offsets.
  • + *

+ * A transition is used to model the cases of a Gap or Overlap. + * The Normal case will return null. + *

+ * There are various ways to handle the conversion from a {@code LocalDateTime}. + * One technique, using this method, would be: + *

+     *  ZoneOffsetTransition trans = rules.getTransition(localDT);
+     *  if (trans == null) {
+     *    // Gap or Overlap: determine what to do from transition
+     *  } else {
+     *    // Normal case: only one valid offset
+     *    zoneOffset = rule.getOffset(localDT);
+     *  }
+     * 
+ * + * @param localDateTime the local date-time to query for offset transition, not null, but null + * may be ignored if the rules have a single offset for all instants + * @return the offset transition, null if the local date-time is not in transition + */ + public ZoneOffsetTransition getTransition(LocalDateTime localDateTime) { + Object info = getOffsetInfo(localDateTime); + return (info instanceof ZoneOffsetTransition ? (ZoneOffsetTransition) info : null); + } + + private Object getOffsetInfo(LocalDateTime dt) { + if (savingsInstantTransitions.length == 0) { + return standardOffsets[0]; + } + // check if using last rules + if (lastRules.length > 0 && + dt.isAfter(savingsLocalTransitions[savingsLocalTransitions.length - 1])) { + ZoneOffsetTransition[] transArray = findTransitionArray(dt.getYear()); + Object info = null; + for (ZoneOffsetTransition trans : transArray) { + info = findOffsetInfo(dt, trans); + if (info instanceof ZoneOffsetTransition || info.equals(trans.getOffsetBefore())) { + return info; + } + } + return info; + } + + // using historic rules + int index = Arrays.binarySearch(savingsLocalTransitions, dt); + if (index == -1) { + // before first transition + return wallOffsets[0]; + } + if (index < 0) { + // switch negative insert position to start of matched range + index = -index - 2; + } else if (index < savingsLocalTransitions.length - 1 && + savingsLocalTransitions[index].equals(savingsLocalTransitions[index + 1])) { + // handle overlap immediately following gap + index++; + } + if ((index & 1) == 0) { + // gap or overlap + LocalDateTime dtBefore = savingsLocalTransitions[index]; + LocalDateTime dtAfter = savingsLocalTransitions[index + 1]; + ZoneOffset offsetBefore = wallOffsets[index / 2]; + ZoneOffset offsetAfter = wallOffsets[index / 2 + 1]; + if (offsetAfter.getTotalSeconds() > offsetBefore.getTotalSeconds()) { + // gap + return new ZoneOffsetTransition(dtBefore, offsetBefore, offsetAfter); + } else { + // overlap + return new ZoneOffsetTransition(dtAfter, offsetBefore, offsetAfter); + } + } else { + // normal (neither gap or overlap) + return wallOffsets[index / 2 + 1]; + } + } + + /** + * Finds the offset info for a local date-time and transition. + * + * @param dt the date-time, not null + * @param trans the transition, not null + * @return the offset info, not null + */ + private Object findOffsetInfo(LocalDateTime dt, ZoneOffsetTransition trans) { + LocalDateTime localTransition = trans.getDateTimeBefore(); + if (trans.isGap()) { + if (dt.isBefore(localTransition)) { + return trans.getOffsetBefore(); + } + if (dt.isBefore(trans.getDateTimeAfter())) { + return trans; + } else { + return trans.getOffsetAfter(); + } + } else { + if (dt.isBefore(localTransition) == false) { + return trans.getOffsetAfter(); + } + if (dt.isBefore(trans.getDateTimeAfter())) { + return trans.getOffsetBefore(); + } else { + return trans; + } + } + } + + /** + * Finds the appropriate transition array for the given year. + * + * @param year the year, not null + * @return the transition array, not null + */ + private ZoneOffsetTransition[] findTransitionArray(int year) { + Integer yearObj = year; // should use Year class, but this saves a class load + ZoneOffsetTransition[] transArray = lastRulesCache.get(yearObj); + if (transArray != null) { + return transArray; + } + ZoneOffsetTransitionRule[] ruleArray = lastRules; + transArray = new ZoneOffsetTransition[ruleArray.length]; + for (int i = 0; i < ruleArray.length; i++) { + transArray[i] = ruleArray[i].createTransition(year); + } + if (year < LAST_CACHED_YEAR) { + lastRulesCache.putIfAbsent(yearObj, transArray); + } + return transArray; + } + + /** + * Gets the standard offset for the specified instant in this zone. + *

+ * This provides access to historic information on how the standard offset + * has changed over time. + * The standard offset is the offset before any daylight saving time is applied. + * This is typically the offset applicable during winter. + * + * @param instant the instant to find the offset information for, not null, but null + * may be ignored if the rules have a single offset for all instants + * @return the standard offset, not null + */ + public ZoneOffset getStandardOffset(Instant instant) { + if (savingsInstantTransitions.length == 0) { + return standardOffsets[0]; + } + long epochSec = instant.getEpochSecond(); + int index = Arrays.binarySearch(standardTransitions, epochSec); + if (index < 0) { + // switch negative insert position to start of matched range + index = -index - 2; + } + return standardOffsets[index + 1]; + } + + /** + * Gets the amount of daylight savings in use for the specified instant in this zone. + *

+ * This provides access to historic information on how the amount of daylight + * savings has changed over time. + * This is the difference between the standard offset and the actual offset. + * Typically the amount is zero during winter and one hour during summer. + * Time-zones are second-based, so the nanosecond part of the duration will be zero. + *

+ * This default implementation calculates the duration from the + * {@link #getOffset(java.time.Instant) actual} and + * {@link #getStandardOffset(java.time.Instant) standard} offsets. + * + * @param instant the instant to find the daylight savings for, not null, but null + * may be ignored if the rules have a single offset for all instants + * @return the difference between the standard and actual offset, not null + */ + public Duration getDaylightSavings(Instant instant) { + if (savingsInstantTransitions.length == 0) { + return Duration.ZERO; + } + ZoneOffset standardOffset = getStandardOffset(instant); + ZoneOffset actualOffset = getOffset(instant); + return Duration.ofSeconds(actualOffset.getTotalSeconds() - standardOffset.getTotalSeconds()); + } + + /** + * Checks if the specified instant is in daylight savings. + *

+ * This checks if the standard offset and the actual offset are the same + * for the specified instant. + * If they are not, it is assumed that daylight savings is in operation. + *

+ * This default implementation compares the {@link #getOffset(java.time.Instant) actual} + * and {@link #getStandardOffset(java.time.Instant) standard} offsets. + * + * @param instant the instant to find the offset information for, not null, but null + * may be ignored if the rules have a single offset for all instants + * @return the standard offset, not null + */ + public boolean isDaylightSavings(Instant instant) { + return (getStandardOffset(instant).equals(getOffset(instant)) == false); + } + + /** + * Checks if the offset date-time is valid for these rules. + *

+ * To be valid, the local date-time must not be in a gap and the offset + * must match one of the valid offsets. + *

+ * This default implementation checks if {@link #getValidOffsets(java.time.LocalDateTime)} + * contains the specified offset. + * + * @param localDateTime the date-time to check, not null, but null + * may be ignored if the rules have a single offset for all instants + * @param offset the offset to check, null returns false + * @return true if the offset date-time is valid for these rules + */ + public boolean isValidOffset(LocalDateTime localDateTime, ZoneOffset offset) { + return getValidOffsets(localDateTime).contains(offset); + } + + /** + * Gets the next transition after the specified instant. + *

+ * This returns details of the next transition after the specified instant. + * For example, if the instant represents a point where "Summer" daylight savings time + * applies, then the method will return the transition to the next "Winter" time. + * + * @param instant the instant to get the next transition after, not null, but null + * may be ignored if the rules have a single offset for all instants + * @return the next transition after the specified instant, null if this is after the last transition + */ + public ZoneOffsetTransition nextTransition(Instant instant) { + if (savingsInstantTransitions.length == 0) { + return null; + } + long epochSec = instant.getEpochSecond(); + // check if using last rules + if (epochSec >= savingsInstantTransitions[savingsInstantTransitions.length - 1]) { + if (lastRules.length == 0) { + return null; + } + // search year the instant is in + int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]); + ZoneOffsetTransition[] transArray = findTransitionArray(year); + for (ZoneOffsetTransition trans : transArray) { + if (epochSec < trans.toEpochSecond()) { + return trans; + } + } + // use first from following year + if (year < Year.MAX_VALUE) { + transArray = findTransitionArray(year + 1); + return transArray[0]; + } + return null; + } + + // using historic rules + int index = Arrays.binarySearch(savingsInstantTransitions, epochSec); + if (index < 0) { + index = -index - 1; // switched value is the next transition + } else { + index += 1; // exact match, so need to add one to get the next + } + return new ZoneOffsetTransition(savingsInstantTransitions[index], wallOffsets[index], wallOffsets[index + 1]); + } + + /** + * Gets the previous transition before the specified instant. + *

+ * This returns details of the previous transition after the specified instant. + * For example, if the instant represents a point where "summer" daylight saving time + * applies, then the method will return the transition from the previous "winter" time. + * + * @param instant the instant to get the previous transition after, not null, but null + * may be ignored if the rules have a single offset for all instants + * @return the previous transition after the specified instant, null if this is before the first transition + */ + public ZoneOffsetTransition previousTransition(Instant instant) { + if (savingsInstantTransitions.length == 0) { + return null; + } + long epochSec = instant.getEpochSecond(); + if (instant.getNano() > 0 && epochSec < Long.MAX_VALUE) { + epochSec += 1; // allow rest of method to only use seconds + } + + // check if using last rules + long lastHistoric = savingsInstantTransitions[savingsInstantTransitions.length - 1]; + if (lastRules.length > 0 && epochSec > lastHistoric) { + // search year the instant is in + ZoneOffset lastHistoricOffset = wallOffsets[wallOffsets.length - 1]; + int year = findYear(epochSec, lastHistoricOffset); + ZoneOffsetTransition[] transArray = findTransitionArray(year); + for (int i = transArray.length - 1; i >= 0; i--) { + if (epochSec > transArray[i].toEpochSecond()) { + return transArray[i]; + } + } + // use last from preceeding year + int lastHistoricYear = findYear(lastHistoric, lastHistoricOffset); + if (--year > lastHistoricYear) { + transArray = findTransitionArray(year); + return transArray[transArray.length - 1]; + } + // drop through + } + + // using historic rules + int index = Arrays.binarySearch(savingsInstantTransitions, epochSec); + if (index < 0) { + index = -index - 1; + } + if (index <= 0) { + return null; + } + return new ZoneOffsetTransition(savingsInstantTransitions[index - 1], wallOffsets[index - 1], wallOffsets[index]); + } + + private int findYear(long epochSecond, ZoneOffset offset) { + // inline for performance + long localSecond = epochSecond + offset.getTotalSeconds(); + long localEpochDay = Math.floorDiv(localSecond, 86400); + return LocalDate.ofEpochDay(localEpochDay).getYear(); + } + + /** + * Gets the complete list of fully defined transitions. + *

+ * The complete set of transitions for this rules instance is defined by this method + * and {@link #getTransitionRules()}. This method returns those transitions that have + * been fully defined. These are typically historical, but may be in the future. + *

+ * The list will be empty for fixed offset rules and for any time-zone where there has + * only ever been a single offset. The list will also be empty if the transition rules are unknown. + * + * @return an immutable list of fully defined transitions, not null + */ + public List getTransitions() { + List list = new ArrayList<>(); + for (int i = 0; i < savingsInstantTransitions.length; i++) { + list.add(new ZoneOffsetTransition(savingsInstantTransitions[i], wallOffsets[i], wallOffsets[i + 1])); + } + return Collections.unmodifiableList(list); + } + + /** + * Gets the list of transition rules for years beyond those defined in the transition list. + *

+ * The complete set of transitions for this rules instance is defined by this method + * and {@link #getTransitions()}. This method returns instances of {@link ZoneOffsetTransitionRule} + * that define an algorithm for when transitions will occur. + *

+ * For any given {@code ZoneRules}, this list contains the transition rules for years + * beyond those years that have been fully defined. These rules typically refer to future + * daylight saving time rule changes. + *

+ * If the zone defines daylight savings into the future, then the list will normally + * be of size two and hold information about entering and exiting daylight savings. + * If the zone does not have daylight savings, or information about future changes + * is uncertain, then the list will be empty. + *

+ * The list will be empty for fixed offset rules and for any time-zone where there is no + * daylight saving time. The list will also be empty if the transition rules are unknown. + * + * @return an immutable list of transition rules, not null + */ + public List getTransitionRules() { + return Collections.unmodifiableList(Arrays.asList(lastRules)); + } + + /** + * Checks if this set of rules equals another. + *

+ * Two rule sets are equal if they will always result in the same output + * for any given input instant or local date-time. + * Rules from two different groups may return false even if they are in fact the same. + *

+ * This definition should result in implementations comparing their entire state. + * + * @param otherRules the other rules, null returns false + * @return true if this rules is the same as that specified + */ + @Override + public boolean equals(Object otherRules) { + if (this == otherRules) { + return true; + } + if (otherRules instanceof ZoneRules) { + ZoneRules other = (ZoneRules) otherRules; + return Arrays.equals(standardTransitions, other.standardTransitions) && + Arrays.equals(standardOffsets, other.standardOffsets) && + Arrays.equals(savingsInstantTransitions, other.savingsInstantTransitions) && + Arrays.equals(wallOffsets, other.wallOffsets) && + Arrays.equals(lastRules, other.lastRules); + } + return false; + } + + /** + * Returns a suitable hash code given the definition of {@code #equals}. + * + * @return the hash code + */ + @Override + public int hashCode() { + return Arrays.hashCode(standardTransitions) ^ + Arrays.hashCode(standardOffsets) ^ + Arrays.hashCode(savingsInstantTransitions) ^ + Arrays.hashCode(wallOffsets) ^ + Arrays.hashCode(lastRules); + } + + /** + * Returns a string describing this object. + * + * @return a string for debugging, not null + */ + @Override + public String toString() { + return "ZoneRules[currentStandardOffset=" + standardOffsets[standardOffsets.length - 1] + "]"; + } + +} diff --git a/src/share/classes/java/time/zone/ZoneRulesException.java b/src/share/classes/java/time/zone/ZoneRulesException.java new file mode 100644 index 0000000000000000000000000000000000000000..e965af0dc348d7d0616f39670e88572fab8e3458 --- /dev/null +++ b/src/share/classes/java/time/zone/ZoneRulesException.java @@ -0,0 +1,98 @@ +/* + * 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) 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 java.time.zone; + +import java.time.DateTimeException; + +/** + * Thrown to indicate a problem with time-zone configuration. + *

+ * This exception is used to indicate a problems with the configured + * time-zone rules. + * + *

Specification for implementors

+ * This class is intended for use in a single thread. + * + * @since 1.8 + */ +public class ZoneRulesException extends DateTimeException { + + /** + * 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 ZoneRulesException(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 ZoneRulesException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/share/classes/java/time/zone/ZoneRulesProvider.java b/src/share/classes/java/time/zone/ZoneRulesProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..2c187a7e32ebf12ea9278cf80b376badbfb0a09e --- /dev/null +++ b/src/share/classes/java/time/zone/ZoneRulesProvider.java @@ -0,0 +1,412 @@ +/* + * 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 java.time.zone; + +import java.time.DateTimeException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Provider of time-zone rules to the system. + *

+ * This class manages the configuration of time-zone rules. + * The static methods provide the public API that can be used to manage the providers. + * The abstract methods provide the SPI that allows rules to be provided. + *

+ * Rules are looked up primarily by zone ID, as used by {@link ZoneId}. + * Only zone region IDs may be used, zone offset IDs are not used here. + *

+ * Time-zone rules are political, thus the data can change at any time. + * Each provider will provide the latest rules for each zone ID, but they + * may also provide the history of how the rules changed. + * + *

Specification for implementors

+ * This interface is a service provider that can be called by multiple threads. + * Implementations must be immutable and thread-safe. + *

+ * Providers must ensure that once a rule has been seen by the application, the + * rule must continue to be available. + *

+ * Many systems would like to update time-zone rules dynamically without stopping the JVM. + * When examined in detail, this is a complex problem. + * Providers may choose to handle dynamic updates, however the default provider does not. + * + * @since 1.8 + */ +public abstract class ZoneRulesProvider { + + /** + * The set of loaded providers. + */ + private static final CopyOnWriteArrayList PROVIDERS = new CopyOnWriteArrayList<>(); + /** + * The lookup from zone region ID to provider. + */ + private static final ConcurrentMap ZONES = new ConcurrentHashMap<>(512, 0.75f, 2); + static { + registerProvider(new TzdbZoneRulesProvider()); + ServiceLoader sl = ServiceLoader.load(ZoneRulesProvider.class, ClassLoader.getSystemClassLoader()); + List loaded = new ArrayList<>(); + Iterator it = sl.iterator(); + while (it.hasNext()) { + ZoneRulesProvider provider; + try { + provider = it.next(); + } catch (ServiceConfigurationError ex) { + if (ex.getCause() instanceof SecurityException) { + continue; // ignore the security exception, try the next provider + } + throw ex; + } + registerProvider0(provider); + } + // CopyOnWriteList could be slow if lots of providers and each added individually + PROVIDERS.addAll(loaded); + } + + //------------------------------------------------------------------------- + /** + * Gets the set of available zone IDs. + *

+ * These zone IDs are loaded and available for use by {@code ZoneId}. + * + * @return a modifiable copy of the set of zone IDs, not null + */ + public static Set getAvailableZoneIds() { + return new HashSet<>(ZONES.keySet()); + } + + /** + * Gets the rules for the zone ID. + *

+ * This returns the latest available rules for the zone ID. + *

+ * This method relies on time-zone data provider files that are configured. + * These are loaded using a {@code ServiceLoader}. + * + * @param zoneId the zone region ID as used by {@code ZoneId}, not null + * @return the rules for the ID, not null + * @throws ZoneRulesException if the zone ID is unknown + */ + public static ZoneRules getRules(String zoneId) { + Objects.requireNonNull(zoneId, "zoneId"); + return getProvider(zoneId).provideRules(zoneId); + } + + /** + * Gets the history of rules for the zone ID. + *

+ * Time-zones are defined by governments and change frequently. + * This method allows applications to find the history of changes to the + * rules for a single zone ID. The map is keyed by a string, which is the + * version string associated with the rules. + *

+ * The exact meaning and format of the version is provider specific. + * The version must follow lexicographical order, thus the returned map will + * be order from the oldest known rules to the newest available rules. + * The default 'TZDB' group uses version numbering consisting of the year + * followed by a letter, such as '2009e' or '2012f'. + *

+ * Implementations must provide a result for each valid zone ID, however + * they do not have to provide a history of rules. + * Thus the map will always contain one element, and will only contain more + * than one element if historical rule information is available. + * + * @param zoneId the zone region ID as used by {@code ZoneId}, not null + * @return a modifiable copy of the history of the rules for the ID, sorted + * from oldest to newest, not null + * @throws ZoneRulesException if the zone ID is unknown + */ + public static NavigableMap getVersions(String zoneId) { + Objects.requireNonNull(zoneId, "zoneId"); + return getProvider(zoneId).provideVersions(zoneId); + } + + /** + * Gets the provider for the zone ID. + * + * @param zoneId the zone region ID as used by {@code ZoneId}, not null + * @return the provider, not null + * @throws ZoneRulesException if the zone ID is unknown + */ + private static ZoneRulesProvider getProvider(String zoneId) { + ZoneRulesProvider provider = ZONES.get(zoneId); + if (provider == null) { + if (ZONES.isEmpty()) { + throw new ZoneRulesException("No time-zone data files registered"); + } + throw new ZoneRulesException("Unknown time-zone ID: " + zoneId); + } + return provider; + } + + //------------------------------------------------------------------------- + /** + * Registers a zone rules provider. + *

+ * This adds a new provider to those currently available. + * A provider supplies rules for one or more zone IDs. + * A provider cannot be registered if it supplies a zone ID that has already been + * registered. See the notes on time-zone IDs in {@link ZoneId}, especially + * the section on using the concept of a "group" to make IDs unique. + *

+ * To ensure the integrity of time-zones already created, there is no way + * to deregister providers. + * + * @param provider the provider to register, not null + * @throws ZoneRulesException if a region is already registered + */ + public static void registerProvider(ZoneRulesProvider provider) { + Objects.requireNonNull(provider, "provider"); + registerProvider0(provider); + PROVIDERS.add(provider); + } + + /** + * Registers the provider. + * + * @param provider the provider to register, not null + * @throws ZoneRulesException if unable to complete the registration + */ + private static void registerProvider0(ZoneRulesProvider provider) { + for (String zoneId : provider.provideZoneIds()) { + Objects.requireNonNull(zoneId, "zoneId"); + ZoneRulesProvider old = ZONES.putIfAbsent(zoneId, provider.provideBind(zoneId)); + if (old != null) { + throw new ZoneRulesException( + "Unable to register zone as one already registered with that ID: " + zoneId + + ", currently loading from provider: " + provider); + } + } + } + + //------------------------------------------------------------------------- + /** + * Refreshes the rules from the underlying data provider. + *

+ * This method is an extension point that allows providers to refresh their + * rules dynamically at a time of the applications choosing. + * After calling this method, the offset stored in any {@link ZonedDateTime} + * may be invalid for the zone ID. + *

+ * Dynamic behavior is entirely optional and most providers, including the + * default provider, do not support it. + * + * @return true if the rules were updated + * @throws ZoneRulesException if an error occurs during the refresh + */ + public static boolean refresh() { + boolean changed = false; + for (ZoneRulesProvider provider : PROVIDERS) { + changed |= provider.provideRefresh(); + } + return changed; + } + + //----------------------------------------------------------------------- + /** + * Constructor. + */ + protected ZoneRulesProvider() { + } + + //----------------------------------------------------------------------- + /** + * SPI method to get the available zone IDs. + *

+ * This obtains the IDs that this {@code ZoneRulesProvider} provides. + * A provider should provide data for at least one region. + *

+ * The returned regions remain available and valid for the lifetime of the application. + * A dynamic provider may increase the set of regions as more data becomes available. + * + * @return the unmodifiable set of region IDs being provided, not null + */ + protected abstract Set provideZoneIds(); + + /** + * SPI method to bind to the specified zone ID. + *

+ * {@code ZoneRulesProvider} has a lookup from zone ID to provider. + * This method is used when building that lookup, allowing providers + * to insert a derived provider that is precisely tuned to the zone ID. + * This replaces two hash map lookups by one, enhancing performance. + *

+ * This optimization is optional. Returning {@code this} is acceptable. + *

+ * This implementation creates a bound provider that caches the + * rules from the underlying provider. The request to version history + * is forward on to the underlying. This is suitable for providers that + * cannot change their contents during the lifetime of the JVM. + * + * @param zoneId the zone region ID as used by {@code ZoneId}, not null + * @return the resolved provider for the ID, not null + * @throws DateTimeException if there is no provider for the specified group + */ + protected ZoneRulesProvider provideBind(String zoneId) { + return new BoundProvider(this, zoneId); + } + + /** + * SPI method to get the rules for the zone ID. + *

+ * This loads the rules for the region and version specified. + * The version may be null to indicate the "latest" version. + * + * @param regionId the time-zone region ID, not null + * @return the rules, not null + * @throws DateTimeException if rules cannot be obtained + */ + protected abstract ZoneRules provideRules(String regionId); + + /** + * SPI method to get the history of rules for the zone ID. + *

+ * This returns a map of historical rules keyed by a version string. + * The exact meaning and format of the version is provider specific. + * The version must follow lexicographical order, thus the returned map will + * be order from the oldest known rules to the newest available rules. + * The default 'TZDB' group uses version numbering consisting of the year + * followed by a letter, such as '2009e' or '2012f'. + *

+ * Implementations must provide a result for each valid zone ID, however + * they do not have to provide a history of rules. + * Thus the map will always contain one element, and will only contain more + * than one element if historical rule information is available. + *

+ * The returned versions remain available and valid for the lifetime of the application. + * A dynamic provider may increase the set of versions as more data becomes available. + * + * @param zoneId the zone region ID as used by {@code ZoneId}, not null + * @return a modifiable copy of the history of the rules for the ID, sorted + * from oldest to newest, not null + * @throws ZoneRulesException if the zone ID is unknown + */ + protected abstract NavigableMap provideVersions(String zoneId); + + /** + * SPI method to refresh the rules from the underlying data provider. + *

+ * This method provides the opportunity for a provider to dynamically + * recheck the underlying data provider to find the latest rules. + * This could be used to load new rules without stopping the JVM. + * Dynamic behavior is entirely optional and most providers do not support it. + *

+ * This implementation returns false. + * + * @return true if the rules were updated + * @throws DateTimeException if an error occurs during the refresh + */ + protected boolean provideRefresh() { + return false; + } + + //------------------------------------------------------------------------- + /** + * A provider bound to a single zone ID. + */ + private static class BoundProvider extends ZoneRulesProvider { + private final ZoneRulesProvider provider; + private final String zoneId; + private final ZoneRules rules; + + private BoundProvider(ZoneRulesProvider provider, String zoneId) { + this.provider = provider; + this.zoneId = zoneId; + this.rules = provider.provideRules(zoneId); + } + + @Override + protected Set provideZoneIds() { + return new HashSet<>(Collections.singleton(zoneId)); + } + + @Override + protected ZoneRules provideRules(String regionId) { + return rules; + } + + @Override + protected NavigableMap provideVersions(String zoneId) { + return provider.provideVersions(zoneId); + } + + @Override + public String toString() { + return zoneId; + } + } + +} diff --git a/src/share/classes/java/time/zone/package-info.java b/src/share/classes/java/time/zone/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..f50b3de2f5dbd9f81c6e7f89399dc2dc50e9697c --- /dev/null +++ b/src/share/classes/java/time/zone/package-info.java @@ -0,0 +1,86 @@ +/* + * 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) 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. + */ + +/** + *

+ * Support for time-zones and their rules. + *

+ *

+ * Daylight Saving Time and Time-Zones are concepts used by Governments to alter local time. + * This package provides support for time-zones, their rules and the resulting + * gaps and overlaps in the local time-line typically caused by Daylight Saving Time. + *

+ * + *

Package specification

+ *

+ * Unless otherwise noted, passing a null argument to a constructor or method in any class or interface + * in this package will cause a {@link java.lang.NullPointerException NullPointerException} to be thrown. + * The Javadoc "@param" definition is used to summarise the null-behavior. + * The "@throws {@link java.lang.NullPointerException}" is not explicitly documented in each method. + *

+ *

+ * All calculations should check for numeric overflow and throw either an {@link java.lang.ArithmeticException} + * or a {@link java.time.DateTimeException}. + *

+ * @since JDK1.8 + */ +package java.time.zone; diff --git a/src/share/classes/java/util/Formatter.java b/src/share/classes/java/util/Formatter.java index c13e4ea3ed570c340642e885e137f16635cf7ea9..f5f479588c1175d75f249d22597d1aaa2334c216 100644 --- a/src/share/classes/java/util/Formatter.java +++ b/src/share/classes/java/util/Formatter.java @@ -50,6 +50,21 @@ import java.text.NumberFormat; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.time.Clock; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.Queries; +import java.time.temporal.OffsetDate; +import java.time.temporal.OffsetDateTime; +import java.time.temporal.OffsetTime; +import java.time.temporal.ChronoZonedDateTime; +import java.time.format.TextStyle; +import java.time.zone.ZoneRules; + import sun.misc.DoubleConsts; import sun.misc.FormattedFloatingDecimal; @@ -254,8 +269,8 @@ import sun.misc.FormattedFloatingDecimal; * * *
  • Date/Time - may be applied to Java types which are capable of - * encoding a date or time: {@code long}, {@link Long}, {@link Calendar}, and - * {@link Date}. + * encoding a date or time: {@code long}, {@link Long}, {@link Calendar}, + * {@link Date} and {@link TemporalAccessor TemporalAccessor} * *
  • Percent - produces a literal {@code '%'} * ('\u0025') @@ -1488,7 +1503,7 @@ import sun.misc.FormattedFloatingDecimal; *

    Date/Time

    * *

    This conversion may be applied to {@code long}, {@link Long}, {@link - * Calendar}, and {@link Date}. + * Calendar}, {@link Date} and {@link TemporalAccessor TemporalAccessor} * * * @@ -2782,6 +2797,9 @@ public final class Formatter implements Closeable, Flushable { } else if (arg instanceof Calendar) { cal = (Calendar) ((Calendar)arg).clone(); cal.setLenient(true); + } else if (arg instanceof TemporalAccessor) { + print((TemporalAccessor)arg, c, l); + return; } else { failConversion(c, arg); } @@ -4032,6 +4050,242 @@ public final class Formatter implements Closeable, Flushable { return sb; } + private void print(TemporalAccessor t, char c, Locale l) throws IOException { + StringBuilder sb = new StringBuilder(); + print(sb, t, c, l); + // justify based on width + String s = justify(sb.toString()); + if (f.contains(Flags.UPPERCASE)) + s = s.toUpperCase(); + a.append(s); + } + + private Appendable print(StringBuilder sb, TemporalAccessor t, char c, + Locale l) throws IOException { + if (sb == null) + sb = new StringBuilder(); + try { + switch (c) { + case DateTime.HOUR_OF_DAY_0: { // 'H' (00 - 23) + int i = t.get(ChronoField.HOUR_OF_DAY); + sb.append(localizedMagnitude(null, i, Flags.ZERO_PAD, 2, l)); + break; + } + case DateTime.HOUR_OF_DAY: { // 'k' (0 - 23) -- like H + int i = t.get(ChronoField.HOUR_OF_DAY); + sb.append(localizedMagnitude(null, i, Flags.NONE, 2, l)); + break; + } + case DateTime.HOUR_0: { // 'I' (01 - 12) + int i = t.get(ChronoField.CLOCK_HOUR_OF_AMPM); + sb.append(localizedMagnitude(null, i, Flags.ZERO_PAD, 2, l)); + break; + } + case DateTime.HOUR: { // 'l' (1 - 12) -- like I + int i = t.get(ChronoField.CLOCK_HOUR_OF_AMPM); + sb.append(localizedMagnitude(null, i, Flags.NONE, 2, l)); + break; + } + case DateTime.MINUTE: { // 'M' (00 - 59) + int i = t.get(ChronoField.MINUTE_OF_HOUR); + Flags flags = Flags.ZERO_PAD; + sb.append(localizedMagnitude(null, i, flags, 2, l)); + break; + } + case DateTime.NANOSECOND: { // 'N' (000000000 - 999999999) + int i = t.get(ChronoField.MILLI_OF_SECOND) * 1000000; + Flags flags = Flags.ZERO_PAD; + sb.append(localizedMagnitude(null, i, flags, 9, l)); + break; + } + case DateTime.MILLISECOND: { // 'L' (000 - 999) + int i = t.get(ChronoField.MILLI_OF_SECOND); + Flags flags = Flags.ZERO_PAD; + sb.append(localizedMagnitude(null, i, flags, 3, l)); + break; + } + case DateTime.MILLISECOND_SINCE_EPOCH: { // 'Q' (0 - 99...?) + long i = t.getLong(ChronoField.INSTANT_SECONDS) * 1000L + + t.getLong(ChronoField.MILLI_OF_SECOND); + Flags flags = Flags.NONE; + sb.append(localizedMagnitude(null, i, flags, width, l)); + break; + } + case DateTime.AM_PM: { // 'p' (am or pm) + // Calendar.AM = 0, Calendar.PM = 1, LocaleElements defines upper + String[] ampm = { "AM", "PM" }; + if (l != null && l != Locale.US) { + DateFormatSymbols dfs = DateFormatSymbols.getInstance(l); + ampm = dfs.getAmPmStrings(); + } + String s = ampm[t.get(ChronoField.AMPM_OF_DAY)]; + sb.append(s.toLowerCase(l != null ? l : Locale.US)); + break; + } + case DateTime.SECONDS_SINCE_EPOCH: { // 's' (0 - 99...?) + long i = t.getLong(ChronoField.INSTANT_SECONDS); + Flags flags = Flags.NONE; + sb.append(localizedMagnitude(null, i, flags, width, l)); + break; + } + case DateTime.SECOND: { // 'S' (00 - 60 - leap second) + int i = t.get(ChronoField.SECOND_OF_MINUTE); + Flags flags = Flags.ZERO_PAD; + sb.append(localizedMagnitude(null, i, flags, 2, l)); + break; + } + case DateTime.ZONE_NUMERIC: { // 'z' ({-|+}####) - ls minus? + int i = t.get(ChronoField.OFFSET_SECONDS); + boolean neg = i < 0; + sb.append(neg ? '-' : '+'); + if (neg) + i = -i; + int min = i / 60; + // combine minute and hour into a single integer + int offset = (min / 60) * 100 + (min % 60); + Flags flags = Flags.ZERO_PAD; + sb.append(localizedMagnitude(null, offset, flags, 4, l)); + break; + } + case DateTime.ZONE: { // 'Z' (symbol) + ZoneId zid = t.query(Queries.zone()); + if (zid == null) { + throw new IllegalFormatConversionException(c, t.getClass()); + } + if (!(zid instanceof ZoneOffset) && + t.isSupported(ChronoField.INSTANT_SECONDS)) { + Instant instant = Instant.from(t); + sb.append(TimeZone.getTimeZone(zid.getId()) + .getDisplayName(zid.getRules().isDaylightSavings(instant), + TimeZone.SHORT, + (l == null) ? Locale.US : l)); + break; + } + sb.append(zid.getId()); + break; + } + // Date + case DateTime.NAME_OF_DAY_ABBREV: // 'a' + case DateTime.NAME_OF_DAY: { // 'A' + int i = t.get(ChronoField.DAY_OF_WEEK) % 7 + 1; + Locale lt = ((l == null) ? Locale.US : l); + DateFormatSymbols dfs = DateFormatSymbols.getInstance(lt); + if (c == DateTime.NAME_OF_DAY) + sb.append(dfs.getWeekdays()[i]); + else + sb.append(dfs.getShortWeekdays()[i]); + break; + } + case DateTime.NAME_OF_MONTH_ABBREV: // 'b' + case DateTime.NAME_OF_MONTH_ABBREV_X: // 'h' -- same b + case DateTime.NAME_OF_MONTH: { // 'B' + int i = t.get(ChronoField.MONTH_OF_YEAR) - 1; + Locale lt = ((l == null) ? Locale.US : l); + DateFormatSymbols dfs = DateFormatSymbols.getInstance(lt); + if (c == DateTime.NAME_OF_MONTH) + sb.append(dfs.getMonths()[i]); + else + sb.append(dfs.getShortMonths()[i]); + break; + } + case DateTime.CENTURY: // 'C' (00 - 99) + case DateTime.YEAR_2: // 'y' (00 - 99) + case DateTime.YEAR_4: { // 'Y' (0000 - 9999) + int i = t.get(ChronoField.YEAR); + int size = 2; + switch (c) { + case DateTime.CENTURY: + i /= 100; + break; + case DateTime.YEAR_2: + i %= 100; + break; + case DateTime.YEAR_4: + size = 4; + break; + } + Flags flags = Flags.ZERO_PAD; + sb.append(localizedMagnitude(null, i, flags, size, l)); + break; + } + case DateTime.DAY_OF_MONTH_0: // 'd' (01 - 31) + case DateTime.DAY_OF_MONTH: { // 'e' (1 - 31) -- like d + int i = t.get(ChronoField.DAY_OF_MONTH); + Flags flags = (c == DateTime.DAY_OF_MONTH_0 + ? Flags.ZERO_PAD + : Flags.NONE); + sb.append(localizedMagnitude(null, i, flags, 2, l)); + break; + } + case DateTime.DAY_OF_YEAR: { // 'j' (001 - 366) + int i = t.get(ChronoField.DAY_OF_YEAR); + Flags flags = Flags.ZERO_PAD; + sb.append(localizedMagnitude(null, i, flags, 3, l)); + break; + } + case DateTime.MONTH: { // 'm' (01 - 12) + int i = t.get(ChronoField.MONTH_OF_YEAR); + Flags flags = Flags.ZERO_PAD; + sb.append(localizedMagnitude(null, i, flags, 2, l)); + break; + } + + // Composites + case DateTime.TIME: // 'T' (24 hour hh:mm:ss - %tH:%tM:%tS) + case DateTime.TIME_24_HOUR: { // 'R' (hh:mm same as %H:%M) + char sep = ':'; + print(sb, t, DateTime.HOUR_OF_DAY_0, l).append(sep); + print(sb, t, DateTime.MINUTE, l); + if (c == DateTime.TIME) { + sb.append(sep); + print(sb, t, DateTime.SECOND, l); + } + break; + } + case DateTime.TIME_12_HOUR: { // 'r' (hh:mm:ss [AP]M) + char sep = ':'; + print(sb, t, DateTime.HOUR_0, l).append(sep); + print(sb, t, DateTime.MINUTE, l).append(sep); + print(sb, t, DateTime.SECOND, l).append(' '); + // this may be in wrong place for some locales + StringBuilder tsb = new StringBuilder(); + print(tsb, t, DateTime.AM_PM, l); + sb.append(tsb.toString().toUpperCase(l != null ? l : Locale.US)); + break; + } + case DateTime.DATE_TIME: { // 'c' (Sat Nov 04 12:02:33 EST 1999) + char sep = ' '; + print(sb, t, DateTime.NAME_OF_DAY_ABBREV, l).append(sep); + print(sb, t, DateTime.NAME_OF_MONTH_ABBREV, l).append(sep); + print(sb, t, DateTime.DAY_OF_MONTH_0, l).append(sep); + print(sb, t, DateTime.TIME, l).append(sep); + print(sb, t, DateTime.ZONE, l).append(sep); + print(sb, t, DateTime.YEAR_4, l); + break; + } + case DateTime.DATE: { // 'D' (mm/dd/yy) + char sep = '/'; + print(sb, t, DateTime.MONTH, l).append(sep); + print(sb, t, DateTime.DAY_OF_MONTH_0, l).append(sep); + print(sb, t, DateTime.YEAR_2, l); + break; + } + case DateTime.ISO_STANDARD_DATE: { // 'F' (%Y-%m-%d) + char sep = '-'; + print(sb, t, DateTime.YEAR_4, l).append(sep); + print(sb, t, DateTime.MONTH, l).append(sep); + print(sb, t, DateTime.DAY_OF_MONTH_0, l); + break; + } + default: + assert false; + } + } catch (DateTimeException x) { + throw new IllegalFormatConversionException(c, t.getClass()); + } + return sb; + } + // -- Methods to support throwing exceptions -- private void failMismatch(Flags f, char c) { diff --git a/test/Makefile b/test/Makefile index 871cc54dc2c75cb94692fdecd912e52fc870e857..408aaaeed251c5bd967a56e61ae30a8acdd77896 100644 --- a/test/Makefile +++ b/test/Makefile @@ -498,6 +498,11 @@ JDK_DEFAULT_TARGETS += jdk_math jdk_math: $(call TestDirs, java/math) $(call RunAgentvmBatch) +# Stable agentvm testruns (TestNG) +JDK_DEFAULT_TARGETS += jdk_time +jdk_time: $(call TestDirs, java/time) + $(call RunOthervmBatch) + # Stable agentvm testruns (minus items from PROBLEM_LIST) JDK_ALL_TARGETS += jdk_other JDK_DEFAULT_TARGETS += jdk_other diff --git a/test/java/time/META-INF/services/java.time.temporal.Chrono b/test/java/time/META-INF/services/java.time.temporal.Chrono new file mode 100644 index 0000000000000000000000000000000000000000..918ad84d621b40ea6c6971b90715684124ad9a02 --- /dev/null +++ b/test/java/time/META-INF/services/java.time.temporal.Chrono @@ -0,0 +1 @@ +tck.java.time.calendar.CopticChrono diff --git a/test/java/time/TEST.properties b/test/java/time/TEST.properties new file mode 100644 index 0000000000000000000000000000000000000000..ccf8ed60635771da96338702eb333df176f5a44f --- /dev/null +++ b/test/java/time/TEST.properties @@ -0,0 +1,3 @@ +# Threeten test uses TestNG +TestNG.dirs = . + diff --git a/test/java/time/tck/java/time/AbstractDateTimeTest.java b/test/java/time/tck/java/time/AbstractDateTimeTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d44d2410f283f3f2bb44adad5634f19c5569c6ec --- /dev/null +++ b/test/java/time/tck/java/time/AbstractDateTimeTest.java @@ -0,0 +1,278 @@ +/* + * 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. + * + * 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) 2011-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 tck.java.time; + +import java.time.*; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +import java.util.List; + +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQuery; +import java.time.temporal.TemporalField; + +import org.testng.annotations.Test; +import test.java.time.AbstractTest; +import test.java.time.temporal.MockFieldNoValue; + +/** + * Base test class for {@code Temporal}. + */ +public abstract class AbstractDateTimeTest extends AbstractTCKTest { + + /** + * Sample {@code Temporal} objects. + * @return the objects, not null + */ + protected abstract List samples(); + + /** + * List of valid supported fields. + * @return the fields, not null + */ + protected abstract List validFields(); + + /** + * List of invalid unsupported fields. + * @return the fields, not null + */ + protected abstract List invalidFields(); + + //----------------------------------------------------------------------- + // isSupported(TemporalField) + //----------------------------------------------------------------------- + @Test(groups = "tck") + public void basicTest_isSupported_TemporalField_supported() { + for (TemporalAccessor sample : samples()) { + for (TemporalField field : validFields()) { + assertEquals(sample.isSupported(field), true, "Failed on " + sample + " " + field); + } + } + } + + @Test(groups = "tck") + public void basicTest_isSupported_TemporalField_unsupported() { + for (TemporalAccessor sample : samples()) { + for (TemporalField field : invalidFields()) { + assertEquals(sample.isSupported(field), false, "Failed on " + sample + " " + field); + } + } + } + + @Test(groups = "tck") + public void basicTest_isSupported_TemporalField_null() { + for (TemporalAccessor sample : samples()) { + assertEquals(sample.isSupported(null), false, "Failed on " + sample); + } + } + + //----------------------------------------------------------------------- + // range(TemporalField) + //----------------------------------------------------------------------- + @Test(groups = "tck") + public void basicTest_range_TemporalField_supported() { + for (TemporalAccessor sample : samples()) { + for (TemporalField field : validFields()) { + sample.range(field); // no exception + } + } + } + + @Test(groups = "tck") + public void basicTest_range_TemporalField_unsupported() { + for (TemporalAccessor sample : samples()) { + for (TemporalField field : invalidFields()) { + try { + sample.range(field); + fail("Failed on " + sample + " " + field); + } catch (DateTimeException ex) { + // expected + } + } + } + } + + @Test(groups = "tck") + public void basicTest_range_TemporalField_null() { + for (TemporalAccessor sample : samples()) { + try { + sample.range(null); + fail("Failed on " + sample); + } catch (NullPointerException ex) { + // expected + } + } + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @Test(groups = "tck") + public void basicTest_get_TemporalField_supported() { + for (TemporalAccessor sample : samples()) { + for (TemporalField field : validFields()) { + if (sample.range(field).isIntValue()) { + sample.get(field); // no exception + } else { + try { + sample.get(field); + fail("Failed on " + sample + " " + field); + } catch (DateTimeException ex) { + // expected + } + } + } + } + } + + @Test(groups = "tck") + public void basicTest_get_TemporalField_unsupported() { + for (TemporalAccessor sample : samples()) { + for (TemporalField field : invalidFields()) { + try { + sample.get(field); + fail("Failed on " + sample + " " + field); + } catch (DateTimeException ex) { + // expected + } + } + } + } + + @Test(expectedExceptions=DateTimeException.class) + public void test_get_TemporalField_invalidField() { + for (TemporalAccessor sample : samples()) { + sample.get(MockFieldNoValue.INSTANCE); + } + } + + @Test(groups = "tck") + public void basicTest_get_TemporalField_null() { + for (TemporalAccessor sample : samples()) { + try { + sample.get(null); + fail("Failed on " + sample); + } catch (NullPointerException ex) { + // expected + } + } + } + + //----------------------------------------------------------------------- + // getLong(TemporalField) + //----------------------------------------------------------------------- + @Test(groups = "tck") + public void basicTest_getLong_TemporalField_supported() { + for (TemporalAccessor sample : samples()) { + for (TemporalField field : validFields()) { + sample.getLong(field); // no exception + } + } + } + + @Test(groups = "tck") + public void basicTest_getLong_TemporalField_unsupported() { + for (TemporalAccessor sample : samples()) { + for (TemporalField field : invalidFields()) { + try { + sample.getLong(field); + fail("Failed on " + sample + " " + field); + } catch (DateTimeException ex) { + // expected + } + } + } + } + + @Test(expectedExceptions=DateTimeException.class) + public void test_getLong_TemporalField_invalidField() { + for (TemporalAccessor sample : samples()) { + sample.getLong(MockFieldNoValue.INSTANCE); + } + } + + @Test(groups = "tck") + public void basicTest_getLong_TemporalField_null() { + for (TemporalAccessor sample : samples()) { + try { + sample.getLong(null); + fail("Failed on " + sample); + } catch (NullPointerException ex) { + // expected + } + } + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void basicTest_query() { + for (TemporalAccessor sample : samples()) { + assertEquals(sample.query(new TemporalQuery() { + @Override + public String queryFrom(TemporalAccessor temporal) { + return "foo"; + } + }), "foo"); + } + } + +} diff --git a/test/java/time/tck/java/time/AbstractTCKTest.java b/test/java/time/tck/java/time/AbstractTCKTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ce20d6b9386bd988db9c7af512821dbb29fa4007 --- /dev/null +++ b/test/java/time/tck/java/time/AbstractTCKTest.java @@ -0,0 +1,158 @@ +/* + * 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) 2011-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 tck.java.time; + +import static org.testng.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamConstants; +import java.io.Serializable; +import java.lang.reflect.Field; + +/** + * Base test class. + */ +public abstract class AbstractTCKTest { + + protected static boolean isIsoLeap(long year) { + if (year % 4 != 0) { + return false; + } + if (year % 100 == 0 && year % 400 != 0) { + return false; + } + return true; + } + + protected static void assertSerializable(Object object) throws IOException, ClassNotFoundException { + assertEquals(object instanceof Serializable, true); + Object deserializedObject = writeThenRead(object); + assertEquals(deserializedObject, object); + } + + private static Object writeThenRead(Object object) throws IOException, ClassNotFoundException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (ObjectOutputStream oos = new ObjectOutputStream(baos) ) { + oos.writeObject(object); + } + try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()))) { + return ois.readObject(); + } + } + + protected static void assertSerializedBySer(Object object, byte[] expectedBytes, byte[]... matches) throws Exception { + String serClass = object.getClass().getPackage().getName() + ".Ser"; + Class serCls = Class.forName(serClass); + Field field = serCls.getDeclaredField("serialVersionUID"); + field.setAccessible(true); + long serVer = (Long) field.get(null); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (ObjectOutputStream oos = new ObjectOutputStream(baos) ) { + oos.writeObject(object); + } + byte[] bytes = baos.toByteArray(); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + try (DataInputStream dis = new DataInputStream(bais)) { + assertEquals(dis.readShort(), ObjectStreamConstants.STREAM_MAGIC); + assertEquals(dis.readShort(), ObjectStreamConstants.STREAM_VERSION); + assertEquals(dis.readByte(), ObjectStreamConstants.TC_OBJECT); + assertEquals(dis.readByte(), ObjectStreamConstants.TC_CLASSDESC); + assertEquals(dis.readUTF(), serClass); + assertEquals(dis.readLong(), serVer); + assertEquals(dis.readByte(), ObjectStreamConstants.SC_EXTERNALIZABLE | ObjectStreamConstants.SC_BLOCK_DATA); + assertEquals(dis.readShort(), 0); // number of fields + assertEquals(dis.readByte(), ObjectStreamConstants.TC_ENDBLOCKDATA); // end of classdesc + assertEquals(dis.readByte(), ObjectStreamConstants.TC_NULL); // no superclasses + if (expectedBytes.length < 256) { + assertEquals(dis.readByte(), ObjectStreamConstants.TC_BLOCKDATA); + assertEquals(dis.readUnsignedByte(), expectedBytes.length); + } else { + assertEquals(dis.readByte(), ObjectStreamConstants.TC_BLOCKDATALONG); + assertEquals(dis.readInt(), expectedBytes.length); + } + byte[] input = new byte[expectedBytes.length]; + dis.readFully(input); + assertEquals(input, expectedBytes); + if (matches.length > 0) { + for (byte[] match : matches) { + boolean matched = false; + while (matched == false) { + try { + dis.mark(1000); + byte[] possible = new byte[match.length]; + dis.readFully(possible); + assertEquals(possible, match); + matched = true; + } catch (AssertionError ex) { + dis.reset(); + dis.readByte(); // ignore + } + } + } + } else { + assertEquals(dis.readByte(), ObjectStreamConstants.TC_ENDBLOCKDATA); // end of blockdata + assertEquals(dis.read(), -1); + } + } + } + +} diff --git a/test/java/time/tck/java/time/TCKClock.java b/test/java/time/tck/java/time/TCKClock.java new file mode 100644 index 0000000000000000000000000000000000000000..e4a870e379649d9d349fbb3ac81519b372db8060 --- /dev/null +++ b/test/java/time/tck/java/time/TCKClock.java @@ -0,0 +1,128 @@ +/* + * 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. + * + * 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 tck.java.time; + +import java.time.*; + +import static org.testng.Assert.assertEquals; + +import org.testng.annotations.Test; + +/** + * Test Clock. + */ +@Test +public class TCKClock { + + static class MockInstantClock extends Clock { + final long millis; + final ZoneId zone; + MockInstantClock(long millis, ZoneId zone) { + this.millis = millis; + this.zone = zone; + } + @Override + public long millis() { + return millis; + } + @Override + public ZoneId getZone() { + return zone; + } + @Override + public Clock withZone(ZoneId timeZone) { + return new MockInstantClock(millis, timeZone); + } + @Override + public boolean equals(Object obj) { + return false; + } + @Override + public int hashCode() { + return 0; + } + @Override + public String toString() { + return "Mock"; + } + } + + private static final Instant INSTANT = Instant.ofEpochSecond(1873687, 357000000); + private static final ZoneId ZONE = ZoneId.of("Europe/Paris"); + private static final Clock MOCK_INSTANT = new MockInstantClock(INSTANT.toEpochMilli(), ZONE); + + //----------------------------------------------------------------------- + @Test + public void test_mockInstantClock_get() { + assertEquals(MOCK_INSTANT.instant(), INSTANT); + assertEquals(MOCK_INSTANT.millis(), INSTANT.toEpochMilli()); + assertEquals(MOCK_INSTANT.getZone(), ZONE); + } + + @Test + public void test_mockInstantClock_withZone() { + ZoneId london = ZoneId.of("Europe/London"); + Clock changed = MOCK_INSTANT.withZone(london); + assertEquals(MOCK_INSTANT.instant(), INSTANT); + assertEquals(MOCK_INSTANT.millis(), INSTANT.toEpochMilli()); + assertEquals(changed.getZone(), london); + } + +} diff --git a/test/java/time/tck/java/time/TCKClock_Fixed.java b/test/java/time/tck/java/time/TCKClock_Fixed.java new file mode 100644 index 0000000000000000000000000000000000000000..20e66a2ed9658b5a45ddf5b14c6f9f35f1b13587 --- /dev/null +++ b/test/java/time/tck/java/time/TCKClock_Fixed.java @@ -0,0 +1,166 @@ +/* + * 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. + * + * 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 tck.java.time; + +import static org.testng.Assert.assertEquals; + +import java.io.IOException; +import java.time.Clock; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; + +import org.testng.annotations.Test; + +/** + * Test fixed clock. + */ +@Test +public class TCKClock_Fixed extends AbstractTCKTest { + + private static final ZoneId MOSCOW = ZoneId.of("Europe/Moscow"); + private static final ZoneId PARIS = ZoneId.of("Europe/Paris"); + private static final Instant INSTANT = LocalDateTime.of(2008, 6, 30, 11, 30, 10, 500).atZone(ZoneOffset.ofHours(2)).toInstant(); + + //----------------------------------------------------------------------- + public void test_isSerializable() throws IOException, ClassNotFoundException { + assertSerializable(Clock.fixed(INSTANT, ZoneOffset.UTC)); + assertSerializable(Clock.fixed(INSTANT, PARIS)); + } + + //------------------------------------------------------------------------- + public void test_fixed_InstantZoneId() { + Clock test = Clock.fixed(INSTANT, PARIS); + assertEquals(test.instant(), INSTANT); + assertEquals(test.getZone(), PARIS); + } + + @Test(expectedExceptions = NullPointerException.class) + public void test_fixed_InstantZoneId_nullInstant() { + Clock.fixed(null, PARIS); + } + + @Test(expectedExceptions = NullPointerException.class) + public void test_fixed_InstantZoneId_nullZoneId() { + Clock.fixed(INSTANT, null); + } + + //------------------------------------------------------------------------- + public void test_withZone() { + Clock test = Clock.fixed(INSTANT, PARIS); + Clock changed = test.withZone(MOSCOW); + assertEquals(test.getZone(), PARIS); + assertEquals(changed.getZone(), MOSCOW); + } + + public void test_withZone_equal() { + Clock test = Clock.fixed(INSTANT, PARIS); + Clock changed = test.withZone(PARIS); + assertEquals(changed.getZone(), PARIS); + } + + @Test(expectedExceptions = NullPointerException.class) + public void test_withZone_null() { + Clock.fixed(INSTANT, PARIS).withZone(null); + } + + //----------------------------------------------------------------------- + public void test_equals() { + Clock a = Clock.fixed(INSTANT, ZoneOffset.UTC); + Clock b = Clock.fixed(INSTANT, ZoneOffset.UTC); + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), true); + assertEquals(b.equals(a), true); + assertEquals(b.equals(b), true); + + Clock c = Clock.fixed(INSTANT, PARIS); + assertEquals(a.equals(c), false); + + Clock d = Clock.fixed(INSTANT.minusNanos(1), ZoneOffset.UTC); + assertEquals(a.equals(d), false); + + assertEquals(a.equals(null), false); + assertEquals(a.equals("other type"), false); + assertEquals(a.equals(Clock.systemUTC()), false); + } + + public void test_hashCode() { + Clock a = Clock.fixed(INSTANT, ZoneOffset.UTC); + Clock b = Clock.fixed(INSTANT, ZoneOffset.UTC); + assertEquals(a.hashCode(), a.hashCode()); + assertEquals(a.hashCode(), b.hashCode()); + + Clock c = Clock.fixed(INSTANT, PARIS); + assertEquals(a.hashCode() == c.hashCode(), false); + + Clock d = Clock.fixed(INSTANT.minusNanos(1), ZoneOffset.UTC); + assertEquals(a.hashCode() == d.hashCode(), false); + } + + //----------------------------------------------------------------------- + public void test_toString() { + // spec requires "full state" in toString() + Clock test = Clock.fixed(INSTANT, PARIS); + assertEquals(test.toString().contains("Europe/Paris"), true); + assertEquals(test.toString().contains("2008-06-30T09:30:10.000000500Z"), true); + } + +} diff --git a/test/java/time/tck/java/time/TCKClock_Offset.java b/test/java/time/tck/java/time/TCKClock_Offset.java new file mode 100644 index 0000000000000000000000000000000000000000..b628c0ce4ebe886075fa9457852fafdc46546e9e --- /dev/null +++ b/test/java/time/tck/java/time/TCKClock_Offset.java @@ -0,0 +1,176 @@ +/* + * 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. + * + * 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 tck.java.time; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; + +import java.io.IOException; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; + +import org.testng.annotations.Test; + +/** + * Test offset clock. + */ +@Test +public class TCKClock_Offset extends AbstractTCKTest { + + private static final ZoneId MOSCOW = ZoneId.of("Europe/Moscow"); + private static final ZoneId PARIS = ZoneId.of("Europe/Paris"); + private static final Instant INSTANT = LocalDateTime.of(2008, 6, 30, 11, 30, 10, 500).atZone(ZoneOffset.ofHours(2)).toInstant(); + private static final Duration OFFSET = Duration.ofSeconds(2); + + //----------------------------------------------------------------------- + public void test_isSerializable() throws IOException, ClassNotFoundException { + assertSerializable(Clock.offset(Clock.system(PARIS), OFFSET)); + } + + //----------------------------------------------------------------------- + public void test_offset_ClockDuration() { + Clock test = Clock.offset(Clock.fixed(INSTANT, PARIS), OFFSET); + System.out.println(test.instant()); + System.out.println(INSTANT.plus(OFFSET)); + assertEquals(test.instant(), INSTANT.plus(OFFSET)); + assertEquals(test.getZone(), PARIS); + } + + public void test_offset_ClockDuration_zeroDuration() { + Clock underlying = Clock.system(PARIS); + Clock test = Clock.offset(underlying, Duration.ZERO); + assertSame(test, underlying); // spec says same + } + + @Test(expectedExceptions = NullPointerException.class) + public void test_offset_ClockDuration_nullClock() { + Clock.offset(null, Duration.ZERO); + } + + @Test(expectedExceptions = NullPointerException.class) + public void test_offset_ClockDuration_nullDuration() { + Clock.offset(Clock.systemUTC(), null); + } + + //------------------------------------------------------------------------- + public void test_withZone() { + Clock test = Clock.offset(Clock.system(PARIS), OFFSET); + Clock changed = test.withZone(MOSCOW); + assertEquals(test.getZone(), PARIS); + assertEquals(changed.getZone(), MOSCOW); + } + + public void test_withZone_equal() { + Clock test = Clock.offset(Clock.system(PARIS), OFFSET); + Clock changed = test.withZone(PARIS); + assertEquals(test, changed); + } + + @Test(expectedExceptions = NullPointerException.class) + public void test_withZone_null() { + Clock.offset(Clock.system(PARIS), OFFSET).withZone(null); + } + + //----------------------------------------------------------------------- + public void test_equals() { + Clock a = Clock.offset(Clock.system(PARIS), OFFSET); + Clock b = Clock.offset(Clock.system(PARIS), OFFSET); + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), true); + assertEquals(b.equals(a), true); + assertEquals(b.equals(b), true); + + Clock c = Clock.offset(Clock.system(MOSCOW), OFFSET); + assertEquals(a.equals(c), false); + + Clock d = Clock.offset(Clock.system(PARIS), OFFSET.minusNanos(1)); + assertEquals(a.equals(d), false); + + assertEquals(a.equals(null), false); + assertEquals(a.equals("other type"), false); + assertEquals(a.equals(Clock.systemUTC()), false); + } + + public void test_hashCode() { + Clock a = Clock.offset(Clock.system(PARIS), OFFSET); + Clock b = Clock.offset(Clock.system(PARIS), OFFSET); + assertEquals(a.hashCode(), a.hashCode()); + assertEquals(a.hashCode(), b.hashCode()); + + Clock c = Clock.offset(Clock.system(MOSCOW), OFFSET); + assertEquals(a.hashCode() == c.hashCode(), false); + + Clock d = Clock.offset(Clock.system(PARIS), OFFSET.minusNanos(1)); + assertEquals(a.hashCode() == d.hashCode(), false); + } + + //----------------------------------------------------------------------- + public void test_toString() { + // spec requires "full state" in toString() + Clock test = Clock.offset(Clock.system(PARIS), OFFSET); + assertEquals(test.toString().contains("Europe/Paris"), true); + assertEquals(test.toString().contains("PT2S"), true); + } + +} diff --git a/test/java/time/tck/java/time/TCKClock_System.java b/test/java/time/tck/java/time/TCKClock_System.java new file mode 100644 index 0000000000000000000000000000000000000000..8152a502ffa314d1b2c2fb710d174744e11b5fcf --- /dev/null +++ b/test/java/time/tck/java/time/TCKClock_System.java @@ -0,0 +1,207 @@ +/* + * 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. + * + * 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 tck.java.time; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +import java.io.IOException; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; + +import org.testng.annotations.Test; + +/** + * Test system clock. + */ +@Test +public class TCKClock_System extends AbstractTCKTest { + + private static final ZoneId MOSCOW = ZoneId.of("Europe/Moscow"); + private static final ZoneId PARIS = ZoneId.of("Europe/Paris"); + + //----------------------------------------------------------------------- + public void test_isSerializable() throws IOException, ClassNotFoundException { + assertSerializable(Clock.systemUTC()); + assertSerializable(Clock.systemDefaultZone()); + assertSerializable(Clock.system(PARIS)); + } + + //----------------------------------------------------------------------- + public void test_instant() { + Clock system = Clock.systemUTC(); + assertEquals(system.getZone(), ZoneOffset.UTC); + for (int i = 0; i < 10000; i++) { + // assume can eventually get these within 10 milliseconds + Instant instant = system.instant(); + long systemMillis = System.currentTimeMillis(); + if (systemMillis - instant.toEpochMilli() < 10) { + return; // success + } + } + fail(); + } + + public void test_millis() { + Clock system = Clock.systemUTC(); + assertEquals(system.getZone(), ZoneOffset.UTC); + for (int i = 0; i < 10000; i++) { + // assume can eventually get these within 10 milliseconds + long instant = system.millis(); + long systemMillis = System.currentTimeMillis(); + if (systemMillis - instant < 10) { + return; // success + } + } + fail(); + } + + //------------------------------------------------------------------------- + public void test_systemUTC() { + Clock test = Clock.systemUTC(); + assertEquals(test.getZone(), ZoneOffset.UTC); + assertEquals(test, Clock.system(ZoneOffset.UTC)); + } + + public void test_systemDefaultZone() { + Clock test = Clock.systemDefaultZone(); + assertEquals(test.getZone(), ZoneId.systemDefault()); + assertEquals(test, Clock.system(ZoneId.systemDefault())); + } + + public void test_system_ZoneId() { + Clock test = Clock.system(PARIS); + assertEquals(test.getZone(), PARIS); + } + + @Test(expectedExceptions = NullPointerException.class) + public void test_zoneId_nullZoneId() { + Clock.system(null); + } + + //------------------------------------------------------------------------- + public void test_withZone() { + Clock test = Clock.system(PARIS); + Clock changed = test.withZone(MOSCOW); + assertEquals(test.getZone(), PARIS); + assertEquals(changed.getZone(), MOSCOW); + } + + public void test_withZone_equal() { + Clock test = Clock.system(PARIS); + Clock changed = test.withZone(PARIS); + assertEquals(changed.getZone(), PARIS); + } + + public void test_withZone_fromUTC() { + Clock test = Clock.systemUTC(); + Clock changed = test.withZone(PARIS); + assertEquals(changed.getZone(), PARIS); + } + + @Test(expectedExceptions = NullPointerException.class) + public void test_withZone_null() { + Clock.systemUTC().withZone(null); + } + + //----------------------------------------------------------------------- + public void test_equals() { + Clock a = Clock.systemUTC(); + Clock b = Clock.systemUTC(); + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), true); + assertEquals(b.equals(a), true); + assertEquals(b.equals(b), true); + + Clock c = Clock.system(PARIS); + Clock d = Clock.system(PARIS); + assertEquals(c.equals(c), true); + assertEquals(c.equals(d), true); + assertEquals(d.equals(c), true); + assertEquals(d.equals(d), true); + + assertEquals(a.equals(c), false); + assertEquals(c.equals(a), false); + + assertEquals(a.equals(null), false); + assertEquals(a.equals("other type"), false); + assertEquals(a.equals(Clock.fixed(Instant.now(), ZoneOffset.UTC)), false); + } + + public void test_hashCode() { + Clock a = Clock.system(ZoneOffset.UTC); + Clock b = Clock.system(ZoneOffset.UTC); + assertEquals(a.hashCode(), a.hashCode()); + assertEquals(a.hashCode(), b.hashCode()); + + Clock c = Clock.system(PARIS); + assertEquals(a.hashCode() == c.hashCode(), false); + } + + //----------------------------------------------------------------------- + public void test_toString() { + // spec requires "full state" in toString() + Clock test = Clock.system(PARIS); + assertEquals(test.toString().contains("Europe/Paris"), true); + } + +} diff --git a/test/java/time/tck/java/time/TCKClock_Tick.java b/test/java/time/tck/java/time/TCKClock_Tick.java new file mode 100644 index 0000000000000000000000000000000000000000..3022d77b747de58cb6e8c5f44cef1ba592eafea6 --- /dev/null +++ b/test/java/time/tck/java/time/TCKClock_Tick.java @@ -0,0 +1,260 @@ +/* + * 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. + * + * 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 tck.java.time; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; + +import java.io.IOException; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +import org.testng.annotations.Test; + +/** + * Test tick clock. + */ +@Test +public class TCKClock_Tick extends AbstractTCKTest { + + private static final ZoneId MOSCOW = ZoneId.of("Europe/Moscow"); + private static final ZoneId PARIS = ZoneId.of("Europe/Paris"); + private static final Duration AMOUNT = Duration.ofSeconds(2); + private static final ZonedDateTime ZDT = LocalDateTime.of(2008, 6, 30, 11, 30, 10, 500).atZone(ZoneOffset.ofHours(2)); + private static final Instant INSTANT = ZDT.toInstant(); + + //----------------------------------------------------------------------- + public void test_isSerializable() throws IOException, ClassNotFoundException { + assertSerializable(Clock.tickSeconds(PARIS)); + assertSerializable(Clock.tickMinutes(MOSCOW)); + assertSerializable(Clock.tick(Clock.fixed(INSTANT, PARIS), AMOUNT)); + } + + //----------------------------------------------------------------------- + public void test_tick_ClockDuration_250millis() { + for (int i = 0; i < 1000; i++) { + Clock test = Clock.tick(Clock.fixed(ZDT.withNano(i * 1000_000).toInstant(), PARIS), Duration.ofMillis(250)); + assertEquals(test.instant(), ZDT.withNano((i / 250) * 250_000_000).toInstant()); + assertEquals(test.getZone(), PARIS); + } + } + + public void test_tick_ClockDuration_250micros() { + for (int i = 0; i < 1000; i++) { + Clock test = Clock.tick(Clock.fixed(ZDT.withNano(i * 1000).toInstant(), PARIS), Duration.ofNanos(250_000)); + assertEquals(test.instant(), ZDT.withNano((i / 250) * 250_000).toInstant()); + assertEquals(test.getZone(), PARIS); + } + } + + public void test_tick_ClockDuration_20nanos() { + for (int i = 0; i < 1000; i++) { + Clock test = Clock.tick(Clock.fixed(ZDT.withNano(i).toInstant(), PARIS), Duration.ofNanos(20)); + assertEquals(test.instant(), ZDT.withNano((i / 20) * 20).toInstant()); + assertEquals(test.getZone(), PARIS); + } + } + + public void test_tick_ClockDuration_zeroDuration() { + Clock underlying = Clock.system(PARIS); + Clock test = Clock.tick(underlying, Duration.ZERO); + assertSame(test, underlying); // spec says same + } + + public void test_tick_ClockDuration_1nsDuration() { + Clock underlying = Clock.system(PARIS); + Clock test = Clock.tick(underlying, Duration.ofNanos(1)); + assertSame(test, underlying); // spec says same + } + + @Test(expectedExceptions = ArithmeticException.class) + public void test_tick_ClockDuration_maxDuration() { + Clock.tick(Clock.systemUTC(), Duration.ofSeconds(Long.MAX_VALUE)); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void test_tick_ClockDuration_subMilliNotDivisible_123ns() { + Clock.tick(Clock.systemUTC(), Duration.ofSeconds(0, 123)); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void test_tick_ClockDuration_subMilliNotDivisible_999ns() { + Clock.tick(Clock.systemUTC(), Duration.ofSeconds(0, 999)); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void test_tick_ClockDuration_subMilliNotDivisible_999_999_999ns() { + Clock.tick(Clock.systemUTC(), Duration.ofSeconds(0, 999_999_999)); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void test_tick_ClockDuration_negative1ns() { + Clock.tick(Clock.systemUTC(), Duration.ofSeconds(0, -1)); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void test_tick_ClockDuration_negative1s() { + Clock.tick(Clock.systemUTC(), Duration.ofSeconds(-1)); + } + + @Test(expectedExceptions = NullPointerException.class) + public void test_tick_ClockDuration_nullClock() { + Clock.tick(null, Duration.ZERO); + } + + @Test(expectedExceptions = NullPointerException.class) + public void test_tick_ClockDuration_nullDuration() { + Clock.tick(Clock.systemUTC(), null); + } + + //----------------------------------------------------------------------- + public void test_tickSeconds_ZoneId() throws Exception { + Clock test = Clock.tickSeconds(PARIS); + assertEquals(test.getZone(), PARIS); + assertEquals(test.instant().getNano(), 0); + Thread.sleep(100); + assertEquals(test.instant().getNano(), 0); + } + + @Test(expectedExceptions = NullPointerException.class) + public void test_tickSeconds_ZoneId_nullZoneId() { + Clock.tickSeconds(null); + } + + //----------------------------------------------------------------------- + public void test_tickMinutes_ZoneId() { + Clock test = Clock.tickMinutes(PARIS); + assertEquals(test.getZone(), PARIS); + Instant instant = test.instant(); + assertEquals(instant.getEpochSecond() % 60, 0); + assertEquals(instant.getNano(), 0); + } + + @Test(expectedExceptions = NullPointerException.class) + public void test_tickMinutes_ZoneId_nullZoneId() { + Clock.tickMinutes(null); + } + + //------------------------------------------------------------------------- + public void test_withZone() { + Clock test = Clock.tick(Clock.system(PARIS), Duration.ofMillis(500)); + Clock changed = test.withZone(MOSCOW); + assertEquals(test.getZone(), PARIS); + assertEquals(changed.getZone(), MOSCOW); + } + + public void test_withZone_equal() { + Clock test = Clock.tick(Clock.system(PARIS), Duration.ofMillis(500)); + Clock changed = test.withZone(PARIS); + assertEquals(test, changed); + } + + @Test(expectedExceptions = NullPointerException.class) + public void test_withZone_null() { + Clock.tick(Clock.system(PARIS), Duration.ofMillis(500)).withZone(null); + } + + //----------------------------------------------------------------------- + public void test__equals() { + Clock a = Clock.tick(Clock.system(PARIS), Duration.ofMillis(500)); + Clock b = Clock.tick(Clock.system(PARIS), Duration.ofMillis(500)); + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), true); + assertEquals(b.equals(a), true); + assertEquals(b.equals(b), true); + + Clock c = Clock.tick(Clock.system(MOSCOW), Duration.ofMillis(500)); + assertEquals(a.equals(c), false); + + Clock d = Clock.tick(Clock.system(PARIS), Duration.ofMillis(499)); + assertEquals(a.equals(d), false); + + assertEquals(a.equals(null), false); + assertEquals(a.equals("other type"), false); + assertEquals(a.equals(Clock.systemUTC()), false); + } + + public void test_hashCode() { + Clock a = Clock.tick(Clock.system(PARIS), Duration.ofMillis(500)); + Clock b = Clock.tick(Clock.system(PARIS), Duration.ofMillis(500)); + assertEquals(a.hashCode(), a.hashCode()); + assertEquals(a.hashCode(), b.hashCode()); + + Clock c = Clock.tick(Clock.system(MOSCOW), Duration.ofMillis(500)); + assertEquals(a.hashCode() == c.hashCode(), false); + + Clock d = Clock.tick(Clock.system(PARIS), Duration.ofMillis(499)); + assertEquals(a.hashCode() == d.hashCode(), false); + } + + //----------------------------------------------------------------------- + public void test_toString() { + // spec requires "full state" in toString() + Clock test = Clock.tick(Clock.system(PARIS), AMOUNT); + assertEquals(test.toString().contains("Europe/Paris"), true); + assertEquals(test.toString().contains("PT2S"), true); + } + +} diff --git a/test/java/time/tck/java/time/TCKDayOfWeek.java b/test/java/time/tck/java/time/TCKDayOfWeek.java new file mode 100644 index 0000000000000000000000000000000000000000..a2460898039d7048cbe35c6a6e4f75531d4186f1 --- /dev/null +++ b/test/java/time/tck/java/time/TCKDayOfWeek.java @@ -0,0 +1,351 @@ +/* + * 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. + * + * 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 tck.java.time; + +import static java.time.DayOfWeek.MONDAY; +import static java.time.DayOfWeek.SUNDAY; +import static java.time.DayOfWeek.WEDNESDAY; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +import java.time.DateTimeException; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; +import java.time.format.TextStyle; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.ISOChrono; +import java.time.temporal.JulianFields; +import java.time.temporal.Queries; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test DayOfWeek. + */ +@Test +public class TCKDayOfWeek extends AbstractDateTimeTest { + + @BeforeMethod + public void setUp() { + } + + //----------------------------------------------------------------------- + @Override + protected List samples() { + TemporalAccessor[] array = {MONDAY, WEDNESDAY, SUNDAY, }; + return Arrays.asList(array); + } + + @Override + protected List validFields() { + TemporalField[] array = { + DAY_OF_WEEK, + }; + return Arrays.asList(array); + } + + @Override + protected List invalidFields() { + List list = new ArrayList<>(Arrays.asList(ChronoField.values())); + list.removeAll(validFields()); + list.add(JulianFields.JULIAN_DAY); + list.add(JulianFields.MODIFIED_JULIAN_DAY); + list.add(JulianFields.RATA_DIE); + return list; + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_int_singleton() { + for (int i = 1; i <= 7; i++) { + DayOfWeek test = DayOfWeek.of(i); + assertEquals(test.getValue(), i); + assertSame(DayOfWeek.of(i), test); + } + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_valueTooLow() { + DayOfWeek.of(0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_valueTooHigh() { + DayOfWeek.of(8); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_CalendricalObject() { + assertEquals(DayOfWeek.from(LocalDate.of(2011, 6, 6)), DayOfWeek.MONDAY); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_CalendricalObject_invalid_noDerive() { + DayOfWeek.from(LocalTime.of(12, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_CalendricalObject_null() { + DayOfWeek.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @Test + public void test_get_TemporalField() { + assertEquals(DayOfWeek.WEDNESDAY.getLong(ChronoField.DAY_OF_WEEK), 3); + } + + @Test + public void test_getLong_TemporalField() { + assertEquals(DayOfWeek.WEDNESDAY.getLong(ChronoField.DAY_OF_WEEK), 3); + } + + //----------------------------------------------------------------------- + // query(TemporalQuery) + //----------------------------------------------------------------------- + @Test + public void test_query_chrono() { + assertEquals(DayOfWeek.FRIDAY.query(Queries.chrono()), null); + assertEquals(Queries.chrono().queryFrom(DayOfWeek.FRIDAY), null); + } + + @Test + public void test_query_zoneId() { + assertEquals(DayOfWeek.FRIDAY.query(Queries.zoneId()), null); + assertEquals(Queries.zoneId().queryFrom(DayOfWeek.FRIDAY), null); + } + + @Test + public void test_query_precision() { + assertEquals(DayOfWeek.FRIDAY.query(Queries.precision()), ChronoUnit.DAYS); + assertEquals(Queries.precision().queryFrom(DayOfWeek.FRIDAY), ChronoUnit.DAYS); + } + + @Test + public void test_query_offset() { + assertEquals(DayOfWeek.FRIDAY.query(Queries.offset()), null); + assertEquals(Queries.offset().queryFrom(DayOfWeek.FRIDAY), null); + } + + @Test + public void test_query_zone() { + assertEquals(DayOfWeek.FRIDAY.query(Queries.zone()), null); + assertEquals(Queries.zone().queryFrom(DayOfWeek.FRIDAY), null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_query_null() { + DayOfWeek.FRIDAY.query(null); + } + + //----------------------------------------------------------------------- + // getText() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_getText() { + assertEquals(DayOfWeek.MONDAY.getText(TextStyle.SHORT, Locale.US), "Mon"); + } + + @Test(expectedExceptions = NullPointerException.class, groups={"tck"}) + public void test_getText_nullStyle() { + DayOfWeek.MONDAY.getText(null, Locale.US); + } + + @Test(expectedExceptions = NullPointerException.class, groups={"tck"}) + public void test_getText_nullLocale() { + DayOfWeek.MONDAY.getText(TextStyle.FULL, null); + } + + //----------------------------------------------------------------------- + // plus(long), plus(long,unit) + //----------------------------------------------------------------------- + @DataProvider(name="plus") + Object[][] data_plus() { + return new Object[][] { + {1, -8, 7}, + {1, -7, 1}, + {1, -6, 2}, + {1, -5, 3}, + {1, -4, 4}, + {1, -3, 5}, + {1, -2, 6}, + {1, -1, 7}, + {1, 0, 1}, + {1, 1, 2}, + {1, 2, 3}, + {1, 3, 4}, + {1, 4, 5}, + {1, 5, 6}, + {1, 6, 7}, + {1, 7, 1}, + {1, 8, 2}, + + {1, 1, 2}, + {2, 1, 3}, + {3, 1, 4}, + {4, 1, 5}, + {5, 1, 6}, + {6, 1, 7}, + {7, 1, 1}, + + {1, -1, 7}, + {2, -1, 1}, + {3, -1, 2}, + {4, -1, 3}, + {5, -1, 4}, + {6, -1, 5}, + {7, -1, 6}, + }; + } + + @Test(dataProvider="plus", groups={"tck"}) + public void test_plus_long(int base, long amount, int expected) { + assertEquals(DayOfWeek.of(base).plus(amount), DayOfWeek.of(expected)); + } + + //----------------------------------------------------------------------- + // minus(long), minus(long,unit) + //----------------------------------------------------------------------- + @DataProvider(name="minus") + Object[][] data_minus() { + return new Object[][] { + {1, -8, 2}, + {1, -7, 1}, + {1, -6, 7}, + {1, -5, 6}, + {1, -4, 5}, + {1, -3, 4}, + {1, -2, 3}, + {1, -1, 2}, + {1, 0, 1}, + {1, 1, 7}, + {1, 2, 6}, + {1, 3, 5}, + {1, 4, 4}, + {1, 5, 3}, + {1, 6, 2}, + {1, 7, 1}, + {1, 8, 7}, + }; + } + + @Test(dataProvider="minus", groups={"tck"}) + public void test_minus_long(int base, long amount, int expected) { + assertEquals(DayOfWeek.of(base).minus(amount), DayOfWeek.of(expected)); + } + + //----------------------------------------------------------------------- + // adjustInto() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjustInto() { + assertEquals(DayOfWeek.MONDAY.adjustInto(LocalDate.of(2012, 9, 2)), LocalDate.of(2012, 8, 27)); + assertEquals(DayOfWeek.MONDAY.adjustInto(LocalDate.of(2012, 9, 3)), LocalDate.of(2012, 9, 3)); + assertEquals(DayOfWeek.MONDAY.adjustInto(LocalDate.of(2012, 9, 4)), LocalDate.of(2012, 9, 3)); + assertEquals(DayOfWeek.MONDAY.adjustInto(LocalDate.of(2012, 9, 10)), LocalDate.of(2012, 9, 10)); + assertEquals(DayOfWeek.MONDAY.adjustInto(LocalDate.of(2012, 9, 11)), LocalDate.of(2012, 9, 10)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_adjustInto_null() { + DayOfWeek.MONDAY.adjustInto((Temporal) null); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString() { + assertEquals(DayOfWeek.MONDAY.toString(), "MONDAY"); + assertEquals(DayOfWeek.TUESDAY.toString(), "TUESDAY"); + assertEquals(DayOfWeek.WEDNESDAY.toString(), "WEDNESDAY"); + assertEquals(DayOfWeek.THURSDAY.toString(), "THURSDAY"); + assertEquals(DayOfWeek.FRIDAY.toString(), "FRIDAY"); + assertEquals(DayOfWeek.SATURDAY.toString(), "SATURDAY"); + assertEquals(DayOfWeek.SUNDAY.toString(), "SUNDAY"); + } + + //----------------------------------------------------------------------- + // generated methods + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_enum() { + assertEquals(DayOfWeek.valueOf("MONDAY"), DayOfWeek.MONDAY); + assertEquals(DayOfWeek.values()[0], DayOfWeek.MONDAY); + } + +} diff --git a/test/java/time/tck/java/time/TCKDuration.java b/test/java/time/tck/java/time/TCKDuration.java new file mode 100644 index 0000000000000000000000000000000000000000..aeba804291da85e478186fb42212b3c33694c3cf --- /dev/null +++ b/test/java/time/tck/java/time/TCKDuration.java @@ -0,0 +1,2135 @@ +/* + * 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. + * + * 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 tck.java.time; + +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.HALF_DAYS; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MICROS; +import static java.time.temporal.ChronoUnit.MILLIS; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static java.time.temporal.ChronoUnit.WEEKS; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import java.time.DateTimeException; +import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalUnit; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test Duration. + */ +@Test +public class TCKDuration extends AbstractTCKTest { + + //----------------------------------------------------------------------- + @Test + public void test_serialization() throws Exception { + assertSerializable(Duration.ofHours(5)); + assertSerializable(Duration.ofHours(0)); + assertSerializable(Duration.ofHours(-5)); + } + + @Test + public void test_serialization_format() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(1); + dos.writeLong(654321); + dos.writeInt(123456789); + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(Duration.ofSeconds(654321, 123456789), bytes); + } + + //----------------------------------------------------------------------- + // constants + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_zero() { + assertEquals(Duration.ZERO.getSeconds(), 0L); + assertEquals(Duration.ZERO.getNano(), 0); + } + + //----------------------------------------------------------------------- + // ofSeconds(long) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_seconds_long() { + for (long i = -2; i <= 2; i++) { + Duration t = Duration.ofSeconds(i); + assertEquals(t.getSeconds(), i); + assertEquals(t.getNano(), 0); + } + } + + //----------------------------------------------------------------------- + // ofSeconds(long,long) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_seconds_long_long() { + for (long i = -2; i <= 2; i++) { + for (int j = 0; j < 10; j++) { + Duration t = Duration.ofSeconds(i, j); + assertEquals(t.getSeconds(), i); + assertEquals(t.getNano(), j); + } + for (int j = -10; j < 0; j++) { + Duration t = Duration.ofSeconds(i, j); + assertEquals(t.getSeconds(), i - 1); + assertEquals(t.getNano(), j + 1000000000); + } + for (int j = 999999990; j < 1000000000; j++) { + Duration t = Duration.ofSeconds(i, j); + assertEquals(t.getSeconds(), i); + assertEquals(t.getNano(), j); + } + } + } + + @Test(groups={"tck"}) + public void factory_seconds_long_long_nanosNegativeAdjusted() { + Duration test = Duration.ofSeconds(2L, -1); + assertEquals(test.getSeconds(), 1); + assertEquals(test.getNano(), 999999999); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void factory_seconds_long_long_tooBig() { + Duration.ofSeconds(Long.MAX_VALUE, 1000000000); + } + + //----------------------------------------------------------------------- + // ofMillis(long) + //----------------------------------------------------------------------- + @DataProvider(name="MillisDurationNoNanos") + Object[][] provider_factory_millis_long() { + return new Object[][] { + {0, 0, 0}, + {1, 0, 1000000}, + {2, 0, 2000000}, + {999, 0, 999000000}, + {1000, 1, 0}, + {1001, 1, 1000000}, + {-1, -1, 999000000}, + {-2, -1, 998000000}, + {-999, -1, 1000000}, + {-1000, -1, 0}, + {-1001, -2, 999000000}, + }; + } + + @Test(dataProvider="MillisDurationNoNanos", groups={"tck"}) + public void factory_millis_long(long millis, long expectedSeconds, int expectedNanoOfSecond) { + Duration test = Duration.ofMillis(millis); + assertEquals(test.getSeconds(), expectedSeconds); + assertEquals(test.getNano(), expectedNanoOfSecond); + } + + //----------------------------------------------------------------------- + // ofNanos(long) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_nanos_nanos() { + Duration test = Duration.ofNanos(1); + assertEquals(test.getSeconds(), 0); + assertEquals(test.getNano(), 1); + } + + @Test(groups={"tck"}) + public void factory_nanos_nanosSecs() { + Duration test = Duration.ofNanos(1000000002); + assertEquals(test.getSeconds(), 1); + assertEquals(test.getNano(), 2); + } + + @Test(groups={"tck"}) + public void factory_nanos_negative() { + Duration test = Duration.ofNanos(-2000000001); + assertEquals(test.getSeconds(), -3); + assertEquals(test.getNano(), 999999999); + } + + @Test(groups={"tck"}) + public void factory_nanos_max() { + Duration test = Duration.ofNanos(Long.MAX_VALUE); + assertEquals(test.getSeconds(), Long.MAX_VALUE / 1000000000); + assertEquals(test.getNano(), Long.MAX_VALUE % 1000000000); + } + + @Test(groups={"tck"}) + public void factory_nanos_min() { + Duration test = Duration.ofNanos(Long.MIN_VALUE); + assertEquals(test.getSeconds(), Long.MIN_VALUE / 1000000000 - 1); + assertEquals(test.getNano(), Long.MIN_VALUE % 1000000000 + 1000000000); + } + + //----------------------------------------------------------------------- + // ofMinutes() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_minutes() { + Duration test = Duration.ofMinutes(2); + assertEquals(test.getSeconds(), 120); + assertEquals(test.getNano(), 0); + } + + @Test(groups={"tck"}) + public void factory_minutes_max() { + Duration test = Duration.ofMinutes(Long.MAX_VALUE / 60); + assertEquals(test.getSeconds(), (Long.MAX_VALUE / 60) * 60); + assertEquals(test.getNano(), 0); + } + + @Test(groups={"tck"}) + public void factory_minutes_min() { + Duration test = Duration.ofMinutes(Long.MIN_VALUE / 60); + assertEquals(test.getSeconds(), (Long.MIN_VALUE / 60) * 60); + assertEquals(test.getNano(), 0); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void factory_minutes_tooBig() { + Duration.ofMinutes(Long.MAX_VALUE / 60 + 1); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void factory_minutes_tooSmall() { + Duration.ofMinutes(Long.MIN_VALUE / 60 - 1); + } + + //----------------------------------------------------------------------- + // ofHours() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_hours() { + Duration test = Duration.ofHours(2); + assertEquals(test.getSeconds(), 2 * 3600); + assertEquals(test.getNano(), 0); + } + + @Test(groups={"tck"}) + public void factory_hours_max() { + Duration test = Duration.ofHours(Long.MAX_VALUE / 3600); + assertEquals(test.getSeconds(), (Long.MAX_VALUE / 3600) * 3600); + assertEquals(test.getNano(), 0); + } + + @Test(groups={"tck"}) + public void factory_hours_min() { + Duration test = Duration.ofHours(Long.MIN_VALUE / 3600); + assertEquals(test.getSeconds(), (Long.MIN_VALUE / 3600) * 3600); + assertEquals(test.getNano(), 0); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void factory_hours_tooBig() { + Duration.ofHours(Long.MAX_VALUE / 3600 + 1); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void factory_hours_tooSmall() { + Duration.ofHours(Long.MIN_VALUE / 3600 - 1); + } + + //----------------------------------------------------------------------- + // ofDays() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_days() { + Duration test = Duration.ofDays(2); + assertEquals(test.getSeconds(), 2 * 86400); + assertEquals(test.getNano(), 0); + } + + @Test(groups={"tck"}) + public void factory_days_max() { + Duration test = Duration.ofDays(Long.MAX_VALUE / 86400); + assertEquals(test.getSeconds(), (Long.MAX_VALUE / 86400) * 86400); + assertEquals(test.getNano(), 0); + } + + @Test(groups={"tck"}) + public void factory_days_min() { + Duration test = Duration.ofDays(Long.MIN_VALUE / 86400); + assertEquals(test.getSeconds(), (Long.MIN_VALUE / 86400) * 86400); + assertEquals(test.getNano(), 0); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void factory_days_tooBig() { + Duration.ofDays(Long.MAX_VALUE / 86400 + 1); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void factory_days_tooSmall() { + Duration.ofDays(Long.MIN_VALUE / 86400 - 1); + } + + //----------------------------------------------------------------------- + // of(long,TemporalUnit) + //----------------------------------------------------------------------- + @DataProvider(name="OfTemporalUnit") + Object[][] provider_factory_of_longTemporalUnit() { + return new Object[][] { + {0, NANOS, 0, 0}, + {0, MICROS, 0, 0}, + {0, MILLIS, 0, 0}, + {0, SECONDS, 0, 0}, + {0, MINUTES, 0, 0}, + {0, HOURS, 0, 0}, + {0, HALF_DAYS, 0, 0}, + {0, DAYS, 0, 0}, + {1, NANOS, 0, 1}, + {1, MICROS, 0, 1000}, + {1, MILLIS, 0, 1000000}, + {1, SECONDS, 1, 0}, + {1, MINUTES, 60, 0}, + {1, HOURS, 3600, 0}, + {1, HALF_DAYS, 43200, 0}, + {1, DAYS, 86400, 0}, + {3, NANOS, 0, 3}, + {3, MICROS, 0, 3000}, + {3, MILLIS, 0, 3000000}, + {3, SECONDS, 3, 0}, + {3, MINUTES, 3 * 60, 0}, + {3, HOURS, 3 * 3600, 0}, + {3, HALF_DAYS, 3 * 43200, 0}, + {3, DAYS, 3 * 86400, 0}, + {-1, NANOS, -1, 999999999}, + {-1, MICROS, -1, 999999000}, + {-1, MILLIS, -1, 999000000}, + {-1, SECONDS, -1, 0}, + {-1, MINUTES, -60, 0}, + {-1, HOURS, -3600, 0}, + {-1, HALF_DAYS, -43200, 0}, + {-1, DAYS, -86400, 0}, + {-3, NANOS, -1, 999999997}, + {-3, MICROS, -1, 999997000}, + {-3, MILLIS, -1, 997000000}, + {-3, SECONDS, -3, 0}, + {-3, MINUTES, -3 * 60, 0}, + {-3, HOURS, -3 * 3600, 0}, + {-3, HALF_DAYS, -3 * 43200, 0}, + {-3, DAYS, -3 * 86400, 0}, + {Long.MAX_VALUE, NANOS, Long.MAX_VALUE / 1000000000, (int) (Long.MAX_VALUE % 1000000000)}, + {Long.MIN_VALUE, NANOS, Long.MIN_VALUE / 1000000000 - 1, (int) (Long.MIN_VALUE % 1000000000 + 1000000000)}, + {Long.MAX_VALUE, MICROS, Long.MAX_VALUE / 1000000, (int) ((Long.MAX_VALUE % 1000000) * 1000)}, + {Long.MIN_VALUE, MICROS, Long.MIN_VALUE / 1000000 - 1, (int) ((Long.MIN_VALUE % 1000000 + 1000000) * 1000)}, + {Long.MAX_VALUE, MILLIS, Long.MAX_VALUE / 1000, (int) ((Long.MAX_VALUE % 1000) * 1000000)}, + {Long.MIN_VALUE, MILLIS, Long.MIN_VALUE / 1000 - 1, (int) ((Long.MIN_VALUE % 1000 + 1000) * 1000000)}, + {Long.MAX_VALUE, SECONDS, Long.MAX_VALUE, 0}, + {Long.MIN_VALUE, SECONDS, Long.MIN_VALUE, 0}, + {Long.MAX_VALUE / 60, MINUTES, (Long.MAX_VALUE / 60) * 60, 0}, + {Long.MIN_VALUE / 60, MINUTES, (Long.MIN_VALUE / 60) * 60, 0}, + {Long.MAX_VALUE / 3600, HOURS, (Long.MAX_VALUE / 3600) * 3600, 0}, + {Long.MIN_VALUE / 3600, HOURS, (Long.MIN_VALUE / 3600) * 3600, 0}, + {Long.MAX_VALUE / 43200, HALF_DAYS, (Long.MAX_VALUE / 43200) * 43200, 0}, + {Long.MIN_VALUE / 43200, HALF_DAYS, (Long.MIN_VALUE / 43200) * 43200, 0}, + }; + } + + @Test(dataProvider="OfTemporalUnit", groups={"tck"}) + public void factory_of_longTemporalUnit(long amount, TemporalUnit unit, long expectedSeconds, int expectedNanoOfSecond) { + Duration t = Duration.of(amount, unit); + assertEquals(t.getSeconds(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @DataProvider(name="OfTemporalUnitOutOfRange") + Object[][] provider_factory_of_longTemporalUnit_outOfRange() { + return new Object[][] { + {Long.MAX_VALUE / 60 + 1, MINUTES}, + {Long.MIN_VALUE / 60 - 1, MINUTES}, + {Long.MAX_VALUE / 3600 + 1, HOURS}, + {Long.MIN_VALUE / 3600 - 1, HOURS}, + {Long.MAX_VALUE / 43200 + 1, HALF_DAYS}, + {Long.MIN_VALUE / 43200 - 1, HALF_DAYS}, + }; + } + + @Test(dataProvider="OfTemporalUnitOutOfRange", expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void factory_of_longTemporalUnit_outOfRange(long amount, TemporalUnit unit) { + Duration.of(amount, unit); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_longTemporalUnit_estimatedUnit() { + Duration.of(2, WEEKS); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_longTemporalUnit_null() { + Duration.of(1, (TemporalUnit) null); + } + + //----------------------------------------------------------------------- + // between() + //----------------------------------------------------------------------- + @DataProvider(name="DurationBetween") + Object[][] provider_factory_between_Instant_Instant() { + return new Object[][] { + {0, 0, 0, 0, 0, 0}, + {3, 0, 7, 0, 4, 0}, + {3, 20, 7, 50, 4, 30}, + {3, 80, 7, 50, 3, 999999970}, + {7, 0, 3, 0, -4, 0}, + }; + } + + @Test(dataProvider="DurationBetween", groups={"tck"}) + public void factory_between_Instant_Instant(long secs1, int nanos1, long secs2, int nanos2, long expectedSeconds, int expectedNanoOfSecond) { + Instant start = Instant.ofEpochSecond(secs1, nanos1); + Instant end = Instant.ofEpochSecond(secs2, nanos2); + Duration t = Duration.between(start, end); + assertEquals(t.getSeconds(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_between_Instant_Instant_startNull() { + Instant end = Instant.ofEpochSecond(1); + Duration.between(null, end); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_between_Instant_Instant_endNull() { + Instant start = Instant.ofEpochSecond(1); + Duration.between(start, null); + } + + //----------------------------------------------------------------------- + // parse(String) + //----------------------------------------------------------------------- + @DataProvider(name="Parse") + Object[][] provider_factory_parse() { + return new Object[][] { + {"PT0S", 0, 0}, + {"pT0S", 0, 0}, + {"Pt0S", 0, 0}, + {"PT0s", 0, 0}, + + {"PT1S", 1, 0}, + {"PT12S", 12, 0}, + {"PT123456789S", 123456789, 0}, + {"PT" + Long.MAX_VALUE + "S", Long.MAX_VALUE, 0}, + + {"PT-1S", -1, 0}, + {"PT-12S", -12, 0}, + {"PT-123456789S", -123456789, 0}, + {"PT" + Long.MIN_VALUE + "S", Long.MIN_VALUE, 0}, + + {"PT1.1S", 1, 100000000}, + {"PT1.12S", 1, 120000000}, + {"PT1.123S", 1, 123000000}, + {"PT1.1234S", 1, 123400000}, + {"PT1.12345S", 1, 123450000}, + {"PT1.123456S", 1, 123456000}, + {"PT1.1234567S", 1, 123456700}, + {"PT1.12345678S", 1, 123456780}, + {"PT1.123456789S", 1, 123456789}, + + {"PT-1.1S", -2, 1000000000 - 100000000}, + {"PT-1.12S", -2, 1000000000 - 120000000}, + {"PT-1.123S", -2, 1000000000 - 123000000}, + {"PT-1.1234S", -2, 1000000000 - 123400000}, + {"PT-1.12345S", -2, 1000000000 - 123450000}, + {"PT-1.123456S", -2, 1000000000 - 123456000}, + {"PT-1.1234567S", -2, 1000000000 - 123456700}, + {"PT-1.12345678S", -2, 1000000000 - 123456780}, + {"PT-1.123456789S", -2, 1000000000 - 123456789}, + + {"PT" + Long.MAX_VALUE + ".123456789S", Long.MAX_VALUE, 123456789}, + {"PT" + Long.MIN_VALUE + ".000000000S", Long.MIN_VALUE, 0}, + }; + } + + @Test(dataProvider="Parse", groups={"tck"}) + public void factory_parse(String text, long expectedSeconds, int expectedNanoOfSecond) { + Duration t = Duration.parse(text); + assertEquals(t.getSeconds(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @Test(dataProvider="Parse", groups={"tck"}) + public void factory_parse_comma(String text, long expectedSeconds, int expectedNanoOfSecond) { + text = text.replace('.', ','); + Duration t = Duration.parse(text); + assertEquals(t.getSeconds(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @DataProvider(name="ParseFailures") + Object[][] provider_factory_parseFailures() { + return new Object[][] { + {""}, + {"PTS"}, + {"AT0S"}, + {"PA0S"}, + {"PT0A"}, + + {"PT+S"}, + {"PT-S"}, + {"PT.S"}, + {"PTAS"}, + + {"PT+0S"}, + {"PT+00S"}, + {"PT+000S"}, + {"PT-0S"}, + {"PT-00S"}, + {"PT-000S"}, + {"PT+1S"}, + {"PT-.S"}, + {"PT+.S"}, + + {"PT1ABC2S"}, + {"PT1.1ABC2S"}, + + {"PT123456789123456789123456789S"}, + {"PT0.1234567891S"}, + {"PT1.S"}, + {"PT.1S"}, + + {"PT2.-3"}, + {"PT-2.-3"}, + {"PT2.+3"}, + {"PT-2.+3"}, + }; + } + + @Test(dataProvider="ParseFailures", expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parseFailures(String text) { + Duration.parse(text); + } + + @Test(dataProvider="ParseFailures", expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parseFailures_comma(String text) { + text = text.replace('.', ','); + Duration.parse(text); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_tooBig() { + Duration.parse("PT" + Long.MAX_VALUE + "1S"); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_tooBig_decimal() { + Duration.parse("PT" + Long.MAX_VALUE + "1.1S"); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_tooSmall() { + Duration.parse("PT" + Long.MIN_VALUE + "1S"); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_tooSmall_decimal() { + Duration.parse("PT" + Long.MIN_VALUE + ".1S"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_nullText() { + Duration.parse((String) null); + } + + @Test(groups={"tck"}) + public void test_deserialization() throws Exception { + Duration orginal = Duration.ofSeconds(2); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(orginal); + out.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + Duration ser = (Duration) in.readObject(); + assertEquals(Duration.ofSeconds(2), ser); + } + + //----------------------------------------------------------------------- + // isZero(), isPositive(), isPositiveOrZero(), isNegative(), isNegativeOrZero() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_isZero() { + assertEquals(Duration.ofNanos(0).isZero(), true); + assertEquals(Duration.ofSeconds(0).isZero(), true); + assertEquals(Duration.ofNanos(1).isZero(), false); + assertEquals(Duration.ofSeconds(1).isZero(), false); + assertEquals(Duration.ofSeconds(1, 1).isZero(), false); + assertEquals(Duration.ofNanos(-1).isZero(), false); + assertEquals(Duration.ofSeconds(-1).isZero(), false); + assertEquals(Duration.ofSeconds(-1, -1).isZero(), false); + } + + @Test(groups={"tck"}) + public void test_isPositive() { + assertEquals(Duration.ofNanos(0).isPositive(), false); + assertEquals(Duration.ofSeconds(0).isPositive(), false); + assertEquals(Duration.ofNanos(1).isPositive(), true); + assertEquals(Duration.ofSeconds(1).isPositive(), true); + assertEquals(Duration.ofSeconds(1, 1).isPositive(), true); + assertEquals(Duration.ofNanos(-1).isPositive(), false); + assertEquals(Duration.ofSeconds(-1).isPositive(), false); + assertEquals(Duration.ofSeconds(-1, -1).isPositive(), false); + } + + @Test(groups={"tck"}) + public void test_isNegative() { + assertEquals(Duration.ofNanos(0).isNegative(), false); + assertEquals(Duration.ofSeconds(0).isNegative(), false); + assertEquals(Duration.ofNanos(1).isNegative(), false); + assertEquals(Duration.ofSeconds(1).isNegative(), false); + assertEquals(Duration.ofSeconds(1, 1).isNegative(), false); + assertEquals(Duration.ofNanos(-1).isNegative(), true); + assertEquals(Duration.ofSeconds(-1).isNegative(), true); + assertEquals(Duration.ofSeconds(-1, -1).isNegative(), true); + } + + //----------------------------------------------------------------------- + // plus() + //----------------------------------------------------------------------- + @DataProvider(name="Plus") + Object[][] provider_plus() { + return new Object[][] { + {Long.MIN_VALUE, 0, Long.MAX_VALUE, 0, -1, 0}, + + {-4, 666666667, -4, 666666667, -7, 333333334}, + {-4, 666666667, -3, 0, -7, 666666667}, + {-4, 666666667, -2, 0, -6, 666666667}, + {-4, 666666667, -1, 0, -5, 666666667}, + {-4, 666666667, -1, 333333334, -4, 1}, + {-4, 666666667, -1, 666666667, -4, 333333334}, + {-4, 666666667, -1, 999999999, -4, 666666666}, + {-4, 666666667, 0, 0, -4, 666666667}, + {-4, 666666667, 0, 1, -4, 666666668}, + {-4, 666666667, 0, 333333333, -3, 0}, + {-4, 666666667, 0, 666666666, -3, 333333333}, + {-4, 666666667, 1, 0, -3, 666666667}, + {-4, 666666667, 2, 0, -2, 666666667}, + {-4, 666666667, 3, 0, -1, 666666667}, + {-4, 666666667, 3, 333333333, 0, 0}, + + {-3, 0, -4, 666666667, -7, 666666667}, + {-3, 0, -3, 0, -6, 0}, + {-3, 0, -2, 0, -5, 0}, + {-3, 0, -1, 0, -4, 0}, + {-3, 0, -1, 333333334, -4, 333333334}, + {-3, 0, -1, 666666667, -4, 666666667}, + {-3, 0, -1, 999999999, -4, 999999999}, + {-3, 0, 0, 0, -3, 0}, + {-3, 0, 0, 1, -3, 1}, + {-3, 0, 0, 333333333, -3, 333333333}, + {-3, 0, 0, 666666666, -3, 666666666}, + {-3, 0, 1, 0, -2, 0}, + {-3, 0, 2, 0, -1, 0}, + {-3, 0, 3, 0, 0, 0}, + {-3, 0, 3, 333333333, 0, 333333333}, + + {-2, 0, -4, 666666667, -6, 666666667}, + {-2, 0, -3, 0, -5, 0}, + {-2, 0, -2, 0, -4, 0}, + {-2, 0, -1, 0, -3, 0}, + {-2, 0, -1, 333333334, -3, 333333334}, + {-2, 0, -1, 666666667, -3, 666666667}, + {-2, 0, -1, 999999999, -3, 999999999}, + {-2, 0, 0, 0, -2, 0}, + {-2, 0, 0, 1, -2, 1}, + {-2, 0, 0, 333333333, -2, 333333333}, + {-2, 0, 0, 666666666, -2, 666666666}, + {-2, 0, 1, 0, -1, 0}, + {-2, 0, 2, 0, 0, 0}, + {-2, 0, 3, 0, 1, 0}, + {-2, 0, 3, 333333333, 1, 333333333}, + + {-1, 0, -4, 666666667, -5, 666666667}, + {-1, 0, -3, 0, -4, 0}, + {-1, 0, -2, 0, -3, 0}, + {-1, 0, -1, 0, -2, 0}, + {-1, 0, -1, 333333334, -2, 333333334}, + {-1, 0, -1, 666666667, -2, 666666667}, + {-1, 0, -1, 999999999, -2, 999999999}, + {-1, 0, 0, 0, -1, 0}, + {-1, 0, 0, 1, -1, 1}, + {-1, 0, 0, 333333333, -1, 333333333}, + {-1, 0, 0, 666666666, -1, 666666666}, + {-1, 0, 1, 0, 0, 0}, + {-1, 0, 2, 0, 1, 0}, + {-1, 0, 3, 0, 2, 0}, + {-1, 0, 3, 333333333, 2, 333333333}, + + {-1, 666666667, -4, 666666667, -4, 333333334}, + {-1, 666666667, -3, 0, -4, 666666667}, + {-1, 666666667, -2, 0, -3, 666666667}, + {-1, 666666667, -1, 0, -2, 666666667}, + {-1, 666666667, -1, 333333334, -1, 1}, + {-1, 666666667, -1, 666666667, -1, 333333334}, + {-1, 666666667, -1, 999999999, -1, 666666666}, + {-1, 666666667, 0, 0, -1, 666666667}, + {-1, 666666667, 0, 1, -1, 666666668}, + {-1, 666666667, 0, 333333333, 0, 0}, + {-1, 666666667, 0, 666666666, 0, 333333333}, + {-1, 666666667, 1, 0, 0, 666666667}, + {-1, 666666667, 2, 0, 1, 666666667}, + {-1, 666666667, 3, 0, 2, 666666667}, + {-1, 666666667, 3, 333333333, 3, 0}, + + {0, 0, -4, 666666667, -4, 666666667}, + {0, 0, -3, 0, -3, 0}, + {0, 0, -2, 0, -2, 0}, + {0, 0, -1, 0, -1, 0}, + {0, 0, -1, 333333334, -1, 333333334}, + {0, 0, -1, 666666667, -1, 666666667}, + {0, 0, -1, 999999999, -1, 999999999}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, 0, 1}, + {0, 0, 0, 333333333, 0, 333333333}, + {0, 0, 0, 666666666, 0, 666666666}, + {0, 0, 1, 0, 1, 0}, + {0, 0, 2, 0, 2, 0}, + {0, 0, 3, 0, 3, 0}, + {0, 0, 3, 333333333, 3, 333333333}, + + {0, 333333333, -4, 666666667, -3, 0}, + {0, 333333333, -3, 0, -3, 333333333}, + {0, 333333333, -2, 0, -2, 333333333}, + {0, 333333333, -1, 0, -1, 333333333}, + {0, 333333333, -1, 333333334, -1, 666666667}, + {0, 333333333, -1, 666666667, 0, 0}, + {0, 333333333, -1, 999999999, 0, 333333332}, + {0, 333333333, 0, 0, 0, 333333333}, + {0, 333333333, 0, 1, 0, 333333334}, + {0, 333333333, 0, 333333333, 0, 666666666}, + {0, 333333333, 0, 666666666, 0, 999999999}, + {0, 333333333, 1, 0, 1, 333333333}, + {0, 333333333, 2, 0, 2, 333333333}, + {0, 333333333, 3, 0, 3, 333333333}, + {0, 333333333, 3, 333333333, 3, 666666666}, + + {1, 0, -4, 666666667, -3, 666666667}, + {1, 0, -3, 0, -2, 0}, + {1, 0, -2, 0, -1, 0}, + {1, 0, -1, 0, 0, 0}, + {1, 0, -1, 333333334, 0, 333333334}, + {1, 0, -1, 666666667, 0, 666666667}, + {1, 0, -1, 999999999, 0, 999999999}, + {1, 0, 0, 0, 1, 0}, + {1, 0, 0, 1, 1, 1}, + {1, 0, 0, 333333333, 1, 333333333}, + {1, 0, 0, 666666666, 1, 666666666}, + {1, 0, 1, 0, 2, 0}, + {1, 0, 2, 0, 3, 0}, + {1, 0, 3, 0, 4, 0}, + {1, 0, 3, 333333333, 4, 333333333}, + + {2, 0, -4, 666666667, -2, 666666667}, + {2, 0, -3, 0, -1, 0}, + {2, 0, -2, 0, 0, 0}, + {2, 0, -1, 0, 1, 0}, + {2, 0, -1, 333333334, 1, 333333334}, + {2, 0, -1, 666666667, 1, 666666667}, + {2, 0, -1, 999999999, 1, 999999999}, + {2, 0, 0, 0, 2, 0}, + {2, 0, 0, 1, 2, 1}, + {2, 0, 0, 333333333, 2, 333333333}, + {2, 0, 0, 666666666, 2, 666666666}, + {2, 0, 1, 0, 3, 0}, + {2, 0, 2, 0, 4, 0}, + {2, 0, 3, 0, 5, 0}, + {2, 0, 3, 333333333, 5, 333333333}, + + {3, 0, -4, 666666667, -1, 666666667}, + {3, 0, -3, 0, 0, 0}, + {3, 0, -2, 0, 1, 0}, + {3, 0, -1, 0, 2, 0}, + {3, 0, -1, 333333334, 2, 333333334}, + {3, 0, -1, 666666667, 2, 666666667}, + {3, 0, -1, 999999999, 2, 999999999}, + {3, 0, 0, 0, 3, 0}, + {3, 0, 0, 1, 3, 1}, + {3, 0, 0, 333333333, 3, 333333333}, + {3, 0, 0, 666666666, 3, 666666666}, + {3, 0, 1, 0, 4, 0}, + {3, 0, 2, 0, 5, 0}, + {3, 0, 3, 0, 6, 0}, + {3, 0, 3, 333333333, 6, 333333333}, + + {3, 333333333, -4, 666666667, 0, 0}, + {3, 333333333, -3, 0, 0, 333333333}, + {3, 333333333, -2, 0, 1, 333333333}, + {3, 333333333, -1, 0, 2, 333333333}, + {3, 333333333, -1, 333333334, 2, 666666667}, + {3, 333333333, -1, 666666667, 3, 0}, + {3, 333333333, -1, 999999999, 3, 333333332}, + {3, 333333333, 0, 0, 3, 333333333}, + {3, 333333333, 0, 1, 3, 333333334}, + {3, 333333333, 0, 333333333, 3, 666666666}, + {3, 333333333, 0, 666666666, 3, 999999999}, + {3, 333333333, 1, 0, 4, 333333333}, + {3, 333333333, 2, 0, 5, 333333333}, + {3, 333333333, 3, 0, 6, 333333333}, + {3, 333333333, 3, 333333333, 6, 666666666}, + + {Long.MAX_VALUE, 0, Long.MIN_VALUE, 0, -1, 0}, + }; + } + + @Test(dataProvider="Plus", groups={"tck"}) + public void plus(long seconds, int nanos, long otherSeconds, int otherNanos, long expectedSeconds, int expectedNanoOfSecond) { + Duration t = Duration.ofSeconds(seconds, nanos).plus(Duration.ofSeconds(otherSeconds, otherNanos)); + assertEquals(t.getSeconds(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void plusOverflowTooBig() { + Duration t = Duration.ofSeconds(Long.MAX_VALUE, 999999999); + t.plus(Duration.ofSeconds(0, 1)); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void plusOverflowTooSmall() { + Duration t = Duration.ofSeconds(Long.MIN_VALUE); + t.plus(Duration.ofSeconds(-1, 999999999)); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void plus_longTemporalUnit_seconds() { + Duration t = Duration.ofSeconds(1); + t = t.plus(1, SECONDS); + assertEquals(2, t.getSeconds()); + assertEquals(0, t.getNano()); + } + + @Test(groups={"tck"}) + public void plus_longTemporalUnit_millis() { + Duration t = Duration.ofSeconds(1); + t = t.plus(1, MILLIS); + assertEquals(1, t.getSeconds()); + assertEquals(1000000, t.getNano()); + } + + @Test(groups={"tck"}) + public void plus_longTemporalUnit_micros() { + Duration t = Duration.ofSeconds(1); + t = t.plus(1, MICROS); + assertEquals(1, t.getSeconds()); + assertEquals(1000, t.getNano()); + } + + @Test(groups={"tck"}) + public void plus_longTemporalUnit_nanos() { + Duration t = Duration.ofSeconds(1); + t = t.plus(1, NANOS); + assertEquals(1, t.getSeconds()); + assertEquals(1, t.getNano()); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void plus_longTemporalUnit_null() { + Duration t = Duration.ofSeconds(1); + t.plus(1, (TemporalUnit) null); + } + + //----------------------------------------------------------------------- + @DataProvider(name="PlusSeconds") + Object[][] provider_plusSeconds_long() { + return new Object[][] { + {0, 0, 0, 0, 0}, + {0, 0, 1, 1, 0}, + {0, 0, -1, -1, 0}, + {0, 0, Long.MAX_VALUE, Long.MAX_VALUE, 0}, + {0, 0, Long.MIN_VALUE, Long.MIN_VALUE, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 1, 2, 0}, + {1, 0, -1, 0, 0}, + {1, 0, Long.MAX_VALUE - 1, Long.MAX_VALUE, 0}, + {1, 0, Long.MIN_VALUE, Long.MIN_VALUE + 1, 0}, + {1, 1, 0, 1, 1}, + {1, 1, 1, 2, 1}, + {1, 1, -1, 0, 1}, + {1, 1, Long.MAX_VALUE - 1, Long.MAX_VALUE, 1}, + {1, 1, Long.MIN_VALUE, Long.MIN_VALUE + 1, 1}, + {-1, 1, 0, -1, 1}, + {-1, 1, 1, 0, 1}, + {-1, 1, -1, -2, 1}, + {-1, 1, Long.MAX_VALUE, Long.MAX_VALUE - 1, 1}, + {-1, 1, Long.MIN_VALUE + 1, Long.MIN_VALUE, 1}, + }; + } + + @Test(dataProvider="PlusSeconds", groups={"tck"}) + public void plusSeconds_long(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Duration t = Duration.ofSeconds(seconds, nanos); + t = t.plusSeconds(amount); + assertEquals(t.getSeconds(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions = {ArithmeticException.class}, groups={"tck"}) + public void plusSeconds_long_overflowTooBig() { + Duration t = Duration.ofSeconds(1, 0); + t.plusSeconds(Long.MAX_VALUE); + } + + @Test(expectedExceptions = {ArithmeticException.class}, groups={"tck"}) + public void plusSeconds_long_overflowTooSmall() { + Duration t = Duration.ofSeconds(-1, 0); + t.plusSeconds(Long.MIN_VALUE); + } + + //----------------------------------------------------------------------- + @DataProvider(name="PlusMillis") + Object[][] provider_plusMillis_long() { + return new Object[][] { + {0, 0, 0, 0, 0}, + {0, 0, 1, 0, 1000000}, + {0, 0, 999, 0, 999000000}, + {0, 0, 1000, 1, 0}, + {0, 0, 1001, 1, 1000000}, + {0, 0, 1999, 1, 999000000}, + {0, 0, 2000, 2, 0}, + {0, 0, -1, -1, 999000000}, + {0, 0, -999, -1, 1000000}, + {0, 0, -1000, -1, 0}, + {0, 0, -1001, -2, 999000000}, + {0, 0, -1999, -2, 1000000}, + + {0, 1, 0, 0, 1}, + {0, 1, 1, 0, 1000001}, + {0, 1, 998, 0, 998000001}, + {0, 1, 999, 0, 999000001}, + {0, 1, 1000, 1, 1}, + {0, 1, 1998, 1, 998000001}, + {0, 1, 1999, 1, 999000001}, + {0, 1, 2000, 2, 1}, + {0, 1, -1, -1, 999000001}, + {0, 1, -2, -1, 998000001}, + {0, 1, -1000, -1, 1}, + {0, 1, -1001, -2, 999000001}, + + {0, 1000000, 0, 0, 1000000}, + {0, 1000000, 1, 0, 2000000}, + {0, 1000000, 998, 0, 999000000}, + {0, 1000000, 999, 1, 0}, + {0, 1000000, 1000, 1, 1000000}, + {0, 1000000, 1998, 1, 999000000}, + {0, 1000000, 1999, 2, 0}, + {0, 1000000, 2000, 2, 1000000}, + {0, 1000000, -1, 0, 0}, + {0, 1000000, -2, -1, 999000000}, + {0, 1000000, -999, -1, 2000000}, + {0, 1000000, -1000, -1, 1000000}, + {0, 1000000, -1001, -1, 0}, + {0, 1000000, -1002, -2, 999000000}, + + {0, 999999999, 0, 0, 999999999}, + {0, 999999999, 1, 1, 999999}, + {0, 999999999, 999, 1, 998999999}, + {0, 999999999, 1000, 1, 999999999}, + {0, 999999999, 1001, 2, 999999}, + {0, 999999999, -1, 0, 998999999}, + {0, 999999999, -1000, -1, 999999999}, + {0, 999999999, -1001, -1, 998999999}, + }; + } + + @Test(dataProvider="PlusMillis", groups={"tck"}) + public void plusMillis_long(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Duration t = Duration.ofSeconds(seconds, nanos); + t = t.plusMillis(amount); + assertEquals(t.getSeconds(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + @Test(dataProvider="PlusMillis", groups={"tck"}) + public void plusMillis_long_oneMore(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Duration t = Duration.ofSeconds(seconds + 1, nanos); + t = t.plusMillis(amount); + assertEquals(t.getSeconds(), expectedSeconds + 1); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + @Test(dataProvider="PlusMillis", groups={"tck"}) + public void plusMillis_long_minusOneLess(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Duration t = Duration.ofSeconds(seconds - 1, nanos); + t = t.plusMillis(amount); + assertEquals(t.getSeconds(), expectedSeconds - 1); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @Test(groups={"tck"}) + public void plusMillis_long_max() { + Duration t = Duration.ofSeconds(Long.MAX_VALUE, 998999999); + t = t.plusMillis(1); + assertEquals(t.getSeconds(), Long.MAX_VALUE); + assertEquals(t.getNano(), 999999999); + } + + @Test(expectedExceptions = {ArithmeticException.class}, groups={"tck"}) + public void plusMillis_long_overflowTooBig() { + Duration t = Duration.ofSeconds(Long.MAX_VALUE, 999000000); + t.plusMillis(1); + } + + @Test(groups={"tck"}) + public void plusMillis_long_min() { + Duration t = Duration.ofSeconds(Long.MIN_VALUE, 1000000); + t = t.plusMillis(-1); + assertEquals(t.getSeconds(), Long.MIN_VALUE); + assertEquals(t.getNano(), 0); + } + + @Test(expectedExceptions = {ArithmeticException.class}, groups={"tck"}) + public void plusMillis_long_overflowTooSmall() { + Duration t = Duration.ofSeconds(Long.MIN_VALUE, 0); + t.plusMillis(-1); + } + + //----------------------------------------------------------------------- + @DataProvider(name="PlusNanos") + Object[][] provider_plusNanos_long() { + return new Object[][] { + {0, 0, 0, 0, 0}, + {0, 0, 1, 0, 1}, + {0, 0, 999999999, 0, 999999999}, + {0, 0, 1000000000, 1, 0}, + {0, 0, 1000000001, 1, 1}, + {0, 0, 1999999999, 1, 999999999}, + {0, 0, 2000000000, 2, 0}, + {0, 0, -1, -1, 999999999}, + {0, 0, -999999999, -1, 1}, + {0, 0, -1000000000, -1, 0}, + {0, 0, -1000000001, -2, 999999999}, + {0, 0, -1999999999, -2, 1}, + + {1, 0, 0, 1, 0}, + {1, 0, 1, 1, 1}, + {1, 0, 999999999, 1, 999999999}, + {1, 0, 1000000000, 2, 0}, + {1, 0, 1000000001, 2, 1}, + {1, 0, 1999999999, 2, 999999999}, + {1, 0, 2000000000, 3, 0}, + {1, 0, -1, 0, 999999999}, + {1, 0, -999999999, 0, 1}, + {1, 0, -1000000000, 0, 0}, + {1, 0, -1000000001, -1, 999999999}, + {1, 0, -1999999999, -1, 1}, + + {-1, 0, 0, -1, 0}, + {-1, 0, 1, -1, 1}, + {-1, 0, 999999999, -1, 999999999}, + {-1, 0, 1000000000, 0, 0}, + {-1, 0, 1000000001, 0, 1}, + {-1, 0, 1999999999, 0, 999999999}, + {-1, 0, 2000000000, 1, 0}, + {-1, 0, -1, -2, 999999999}, + {-1, 0, -999999999, -2, 1}, + {-1, 0, -1000000000, -2, 0}, + {-1, 0, -1000000001, -3, 999999999}, + {-1, 0, -1999999999, -3, 1}, + + {1, 1, 0, 1, 1}, + {1, 1, 1, 1, 2}, + {1, 1, 999999998, 1, 999999999}, + {1, 1, 999999999, 2, 0}, + {1, 1, 1000000000, 2, 1}, + {1, 1, 1999999998, 2, 999999999}, + {1, 1, 1999999999, 3, 0}, + {1, 1, 2000000000, 3, 1}, + {1, 1, -1, 1, 0}, + {1, 1, -2, 0, 999999999}, + {1, 1, -1000000000, 0, 1}, + {1, 1, -1000000001, 0, 0}, + {1, 1, -1000000002, -1, 999999999}, + {1, 1, -2000000000, -1, 1}, + + {1, 999999999, 0, 1, 999999999}, + {1, 999999999, 1, 2, 0}, + {1, 999999999, 999999999, 2, 999999998}, + {1, 999999999, 1000000000, 2, 999999999}, + {1, 999999999, 1000000001, 3, 0}, + {1, 999999999, -1, 1, 999999998}, + {1, 999999999, -1000000000, 0, 999999999}, + {1, 999999999, -1000000001, 0, 999999998}, + {1, 999999999, -1999999999, 0, 0}, + {1, 999999999, -2000000000, -1, 999999999}, + + {Long.MAX_VALUE, 0, 999999999, Long.MAX_VALUE, 999999999}, + {Long.MAX_VALUE - 1, 0, 1999999999, Long.MAX_VALUE, 999999999}, + {Long.MIN_VALUE, 1, -1, Long.MIN_VALUE, 0}, + {Long.MIN_VALUE + 1, 1, -1000000001, Long.MIN_VALUE, 0}, + }; + } + + @Test(dataProvider="PlusNanos", groups={"tck"}) + public void plusNanos_long(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Duration t = Duration.ofSeconds(seconds, nanos); + t = t.plusNanos(amount); + assertEquals(t.getSeconds(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions = {ArithmeticException.class}, groups={"tck"}) + public void plusNanos_long_overflowTooBig() { + Duration t = Duration.ofSeconds(Long.MAX_VALUE, 999999999); + t.plusNanos(1); + } + + @Test(expectedExceptions = {ArithmeticException.class}, groups={"tck"}) + public void plusNanos_long_overflowTooSmall() { + Duration t = Duration.ofSeconds(Long.MIN_VALUE, 0); + t.plusNanos(-1); + } + + //----------------------------------------------------------------------- + @DataProvider(name="Minus") + Object[][] provider_minus() { + return new Object[][] { + {Long.MIN_VALUE, 0, Long.MIN_VALUE + 1, 0, -1, 0}, + + {-4, 666666667, -4, 666666667, 0, 0}, + {-4, 666666667, -3, 0, -1, 666666667}, + {-4, 666666667, -2, 0, -2, 666666667}, + {-4, 666666667, -1, 0, -3, 666666667}, + {-4, 666666667, -1, 333333334, -3, 333333333}, + {-4, 666666667, -1, 666666667, -3, 0}, + {-4, 666666667, -1, 999999999, -4, 666666668}, + {-4, 666666667, 0, 0, -4, 666666667}, + {-4, 666666667, 0, 1, -4, 666666666}, + {-4, 666666667, 0, 333333333, -4, 333333334}, + {-4, 666666667, 0, 666666666, -4, 1}, + {-4, 666666667, 1, 0, -5, 666666667}, + {-4, 666666667, 2, 0, -6, 666666667}, + {-4, 666666667, 3, 0, -7, 666666667}, + {-4, 666666667, 3, 333333333, -7, 333333334}, + + {-3, 0, -4, 666666667, 0, 333333333}, + {-3, 0, -3, 0, 0, 0}, + {-3, 0, -2, 0, -1, 0}, + {-3, 0, -1, 0, -2, 0}, + {-3, 0, -1, 333333334, -3, 666666666}, + {-3, 0, -1, 666666667, -3, 333333333}, + {-3, 0, -1, 999999999, -3, 1}, + {-3, 0, 0, 0, -3, 0}, + {-3, 0, 0, 1, -4, 999999999}, + {-3, 0, 0, 333333333, -4, 666666667}, + {-3, 0, 0, 666666666, -4, 333333334}, + {-3, 0, 1, 0, -4, 0}, + {-3, 0, 2, 0, -5, 0}, + {-3, 0, 3, 0, -6, 0}, + {-3, 0, 3, 333333333, -7, 666666667}, + + {-2, 0, -4, 666666667, 1, 333333333}, + {-2, 0, -3, 0, 1, 0}, + {-2, 0, -2, 0, 0, 0}, + {-2, 0, -1, 0, -1, 0}, + {-2, 0, -1, 333333334, -2, 666666666}, + {-2, 0, -1, 666666667, -2, 333333333}, + {-2, 0, -1, 999999999, -2, 1}, + {-2, 0, 0, 0, -2, 0}, + {-2, 0, 0, 1, -3, 999999999}, + {-2, 0, 0, 333333333, -3, 666666667}, + {-2, 0, 0, 666666666, -3, 333333334}, + {-2, 0, 1, 0, -3, 0}, + {-2, 0, 2, 0, -4, 0}, + {-2, 0, 3, 0, -5, 0}, + {-2, 0, 3, 333333333, -6, 666666667}, + + {-1, 0, -4, 666666667, 2, 333333333}, + {-1, 0, -3, 0, 2, 0}, + {-1, 0, -2, 0, 1, 0}, + {-1, 0, -1, 0, 0, 0}, + {-1, 0, -1, 333333334, -1, 666666666}, + {-1, 0, -1, 666666667, -1, 333333333}, + {-1, 0, -1, 999999999, -1, 1}, + {-1, 0, 0, 0, -1, 0}, + {-1, 0, 0, 1, -2, 999999999}, + {-1, 0, 0, 333333333, -2, 666666667}, + {-1, 0, 0, 666666666, -2, 333333334}, + {-1, 0, 1, 0, -2, 0}, + {-1, 0, 2, 0, -3, 0}, + {-1, 0, 3, 0, -4, 0}, + {-1, 0, 3, 333333333, -5, 666666667}, + + {-1, 666666667, -4, 666666667, 3, 0}, + {-1, 666666667, -3, 0, 2, 666666667}, + {-1, 666666667, -2, 0, 1, 666666667}, + {-1, 666666667, -1, 0, 0, 666666667}, + {-1, 666666667, -1, 333333334, 0, 333333333}, + {-1, 666666667, -1, 666666667, 0, 0}, + {-1, 666666667, -1, 999999999, -1, 666666668}, + {-1, 666666667, 0, 0, -1, 666666667}, + {-1, 666666667, 0, 1, -1, 666666666}, + {-1, 666666667, 0, 333333333, -1, 333333334}, + {-1, 666666667, 0, 666666666, -1, 1}, + {-1, 666666667, 1, 0, -2, 666666667}, + {-1, 666666667, 2, 0, -3, 666666667}, + {-1, 666666667, 3, 0, -4, 666666667}, + {-1, 666666667, 3, 333333333, -4, 333333334}, + + {0, 0, -4, 666666667, 3, 333333333}, + {0, 0, -3, 0, 3, 0}, + {0, 0, -2, 0, 2, 0}, + {0, 0, -1, 0, 1, 0}, + {0, 0, -1, 333333334, 0, 666666666}, + {0, 0, -1, 666666667, 0, 333333333}, + {0, 0, -1, 999999999, 0, 1}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, -1, 999999999}, + {0, 0, 0, 333333333, -1, 666666667}, + {0, 0, 0, 666666666, -1, 333333334}, + {0, 0, 1, 0, -1, 0}, + {0, 0, 2, 0, -2, 0}, + {0, 0, 3, 0, -3, 0}, + {0, 0, 3, 333333333, -4, 666666667}, + + {0, 333333333, -4, 666666667, 3, 666666666}, + {0, 333333333, -3, 0, 3, 333333333}, + {0, 333333333, -2, 0, 2, 333333333}, + {0, 333333333, -1, 0, 1, 333333333}, + {0, 333333333, -1, 333333334, 0, 999999999}, + {0, 333333333, -1, 666666667, 0, 666666666}, + {0, 333333333, -1, 999999999, 0, 333333334}, + {0, 333333333, 0, 0, 0, 333333333}, + {0, 333333333, 0, 1, 0, 333333332}, + {0, 333333333, 0, 333333333, 0, 0}, + {0, 333333333, 0, 666666666, -1, 666666667}, + {0, 333333333, 1, 0, -1, 333333333}, + {0, 333333333, 2, 0, -2, 333333333}, + {0, 333333333, 3, 0, -3, 333333333}, + {0, 333333333, 3, 333333333, -3, 0}, + + {1, 0, -4, 666666667, 4, 333333333}, + {1, 0, -3, 0, 4, 0}, + {1, 0, -2, 0, 3, 0}, + {1, 0, -1, 0, 2, 0}, + {1, 0, -1, 333333334, 1, 666666666}, + {1, 0, -1, 666666667, 1, 333333333}, + {1, 0, -1, 999999999, 1, 1}, + {1, 0, 0, 0, 1, 0}, + {1, 0, 0, 1, 0, 999999999}, + {1, 0, 0, 333333333, 0, 666666667}, + {1, 0, 0, 666666666, 0, 333333334}, + {1, 0, 1, 0, 0, 0}, + {1, 0, 2, 0, -1, 0}, + {1, 0, 3, 0, -2, 0}, + {1, 0, 3, 333333333, -3, 666666667}, + + {2, 0, -4, 666666667, 5, 333333333}, + {2, 0, -3, 0, 5, 0}, + {2, 0, -2, 0, 4, 0}, + {2, 0, -1, 0, 3, 0}, + {2, 0, -1, 333333334, 2, 666666666}, + {2, 0, -1, 666666667, 2, 333333333}, + {2, 0, -1, 999999999, 2, 1}, + {2, 0, 0, 0, 2, 0}, + {2, 0, 0, 1, 1, 999999999}, + {2, 0, 0, 333333333, 1, 666666667}, + {2, 0, 0, 666666666, 1, 333333334}, + {2, 0, 1, 0, 1, 0}, + {2, 0, 2, 0, 0, 0}, + {2, 0, 3, 0, -1, 0}, + {2, 0, 3, 333333333, -2, 666666667}, + + {3, 0, -4, 666666667, 6, 333333333}, + {3, 0, -3, 0, 6, 0}, + {3, 0, -2, 0, 5, 0}, + {3, 0, -1, 0, 4, 0}, + {3, 0, -1, 333333334, 3, 666666666}, + {3, 0, -1, 666666667, 3, 333333333}, + {3, 0, -1, 999999999, 3, 1}, + {3, 0, 0, 0, 3, 0}, + {3, 0, 0, 1, 2, 999999999}, + {3, 0, 0, 333333333, 2, 666666667}, + {3, 0, 0, 666666666, 2, 333333334}, + {3, 0, 1, 0, 2, 0}, + {3, 0, 2, 0, 1, 0}, + {3, 0, 3, 0, 0, 0}, + {3, 0, 3, 333333333, -1, 666666667}, + + {3, 333333333, -4, 666666667, 6, 666666666}, + {3, 333333333, -3, 0, 6, 333333333}, + {3, 333333333, -2, 0, 5, 333333333}, + {3, 333333333, -1, 0, 4, 333333333}, + {3, 333333333, -1, 333333334, 3, 999999999}, + {3, 333333333, -1, 666666667, 3, 666666666}, + {3, 333333333, -1, 999999999, 3, 333333334}, + {3, 333333333, 0, 0, 3, 333333333}, + {3, 333333333, 0, 1, 3, 333333332}, + {3, 333333333, 0, 333333333, 3, 0}, + {3, 333333333, 0, 666666666, 2, 666666667}, + {3, 333333333, 1, 0, 2, 333333333}, + {3, 333333333, 2, 0, 1, 333333333}, + {3, 333333333, 3, 0, 0, 333333333}, + {3, 333333333, 3, 333333333, 0, 0}, + + {Long.MAX_VALUE, 0, Long.MAX_VALUE, 0, 0, 0}, + }; + } + + @Test(dataProvider="Minus", groups={"tck"}) + public void minus(long seconds, int nanos, long otherSeconds, int otherNanos, long expectedSeconds, int expectedNanoOfSecond) { + Duration t = Duration.ofSeconds(seconds, nanos).minus(Duration.ofSeconds(otherSeconds, otherNanos)); + assertEquals(t.getSeconds(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void minusOverflowTooSmall() { + Duration t = Duration.ofSeconds(Long.MIN_VALUE); + t.minus(Duration.ofSeconds(0, 1)); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void minusOverflowTooBig() { + Duration t = Duration.ofSeconds(Long.MAX_VALUE, 999999999); + t.minus(Duration.ofSeconds(-1, 999999999)); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void minus_longTemporalUnit_seconds() { + Duration t = Duration.ofSeconds(1); + t = t.minus(1, SECONDS); + assertEquals(0, t.getSeconds()); + assertEquals(0, t.getNano()); + } + + @Test(groups={"tck"}) + public void minus_longTemporalUnit_millis() { + Duration t = Duration.ofSeconds(1); + t = t.minus(1, MILLIS); + assertEquals(0, t.getSeconds()); + assertEquals(999000000, t.getNano()); + } + + @Test(groups={"tck"}) + public void minus_longTemporalUnit_micros() { + Duration t = Duration.ofSeconds(1); + t = t.minus(1, MICROS); + assertEquals(0, t.getSeconds()); + assertEquals(999999000, t.getNano()); + } + + @Test(groups={"tck"}) + public void minus_longTemporalUnit_nanos() { + Duration t = Duration.ofSeconds(1); + t = t.minus(1, NANOS); + assertEquals(0, t.getSeconds()); + assertEquals(999999999, t.getNano()); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void minus_longTemporalUnit_null() { + Duration t = Duration.ofSeconds(1); + t.minus(1, (TemporalUnit) null); + } + + //----------------------------------------------------------------------- + @DataProvider(name="MinusSeconds") + Object[][] provider_minusSeconds_long() { + return new Object[][] { + {0, 0, 0, 0, 0}, + {0, 0, 1, -1, 0}, + {0, 0, -1, 1, 0}, + {0, 0, Long.MAX_VALUE, -Long.MAX_VALUE, 0}, + {0, 0, Long.MIN_VALUE + 1, Long.MAX_VALUE, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 1, 0, 0}, + {1, 0, -1, 2, 0}, + {1, 0, Long.MAX_VALUE - 1, -Long.MAX_VALUE + 2, 0}, + {1, 0, Long.MIN_VALUE + 2, Long.MAX_VALUE, 0}, + {1, 1, 0, 1, 1}, + {1, 1, 1, 0, 1}, + {1, 1, -1, 2, 1}, + {1, 1, Long.MAX_VALUE, -Long.MAX_VALUE + 1, 1}, + {1, 1, Long.MIN_VALUE + 2, Long.MAX_VALUE, 1}, + {-1, 1, 0, -1, 1}, + {-1, 1, 1, -2, 1}, + {-1, 1, -1, 0, 1}, + {-1, 1, Long.MAX_VALUE, Long.MIN_VALUE, 1}, + {-1, 1, Long.MIN_VALUE + 1, Long.MAX_VALUE - 1, 1}, + }; + } + + @Test(dataProvider="MinusSeconds", groups={"tck"}) + public void minusSeconds_long(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Duration t = Duration.ofSeconds(seconds, nanos); + t = t.minusSeconds(amount); + assertEquals(t.getSeconds(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions = {ArithmeticException.class}, groups={"tck"}) + public void minusSeconds_long_overflowTooBig() { + Duration t = Duration.ofSeconds(1, 0); + t.minusSeconds(Long.MIN_VALUE + 1); + } + + @Test(expectedExceptions = {ArithmeticException.class}, groups={"tck"}) + public void minusSeconds_long_overflowTooSmall() { + Duration t = Duration.ofSeconds(-2, 0); + t.minusSeconds(Long.MAX_VALUE); + } + + //----------------------------------------------------------------------- + @DataProvider(name="MinusMillis") + Object[][] provider_minusMillis_long() { + return new Object[][] { + {0, 0, 0, 0, 0}, + {0, 0, 1, -1, 999000000}, + {0, 0, 999, -1, 1000000}, + {0, 0, 1000, -1, 0}, + {0, 0, 1001, -2, 999000000}, + {0, 0, 1999, -2, 1000000}, + {0, 0, 2000, -2, 0}, + {0, 0, -1, 0, 1000000}, + {0, 0, -999, 0, 999000000}, + {0, 0, -1000, 1, 0}, + {0, 0, -1001, 1, 1000000}, + {0, 0, -1999, 1, 999000000}, + + {0, 1, 0, 0, 1}, + {0, 1, 1, -1, 999000001}, + {0, 1, 998, -1, 2000001}, + {0, 1, 999, -1, 1000001}, + {0, 1, 1000, -1, 1}, + {0, 1, 1998, -2, 2000001}, + {0, 1, 1999, -2, 1000001}, + {0, 1, 2000, -2, 1}, + {0, 1, -1, 0, 1000001}, + {0, 1, -2, 0, 2000001}, + {0, 1, -1000, 1, 1}, + {0, 1, -1001, 1, 1000001}, + + {0, 1000000, 0, 0, 1000000}, + {0, 1000000, 1, 0, 0}, + {0, 1000000, 998, -1, 3000000}, + {0, 1000000, 999, -1, 2000000}, + {0, 1000000, 1000, -1, 1000000}, + {0, 1000000, 1998, -2, 3000000}, + {0, 1000000, 1999, -2, 2000000}, + {0, 1000000, 2000, -2, 1000000}, + {0, 1000000, -1, 0, 2000000}, + {0, 1000000, -2, 0, 3000000}, + {0, 1000000, -999, 1, 0}, + {0, 1000000, -1000, 1, 1000000}, + {0, 1000000, -1001, 1, 2000000}, + {0, 1000000, -1002, 1, 3000000}, + + {0, 999999999, 0, 0, 999999999}, + {0, 999999999, 1, 0, 998999999}, + {0, 999999999, 999, 0, 999999}, + {0, 999999999, 1000, -1, 999999999}, + {0, 999999999, 1001, -1, 998999999}, + {0, 999999999, -1, 1, 999999}, + {0, 999999999, -1000, 1, 999999999}, + {0, 999999999, -1001, 2, 999999}, + }; + } + + @Test(dataProvider="MinusMillis", groups={"tck"}) + public void minusMillis_long(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Duration t = Duration.ofSeconds(seconds, nanos); + t = t.minusMillis(amount); + assertEquals(t.getSeconds(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + @Test(dataProvider="MinusMillis", groups={"tck"}) + public void minusMillis_long_oneMore(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Duration t = Duration.ofSeconds(seconds + 1, nanos); + t = t.minusMillis(amount); + assertEquals(t.getSeconds(), expectedSeconds + 1); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + @Test(dataProvider="MinusMillis", groups={"tck"}) + public void minusMillis_long_minusOneLess(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Duration t = Duration.ofSeconds(seconds - 1, nanos); + t = t.minusMillis(amount); + assertEquals(t.getSeconds(), expectedSeconds - 1); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @Test(groups={"tck"}) + public void minusMillis_long_max() { + Duration t = Duration.ofSeconds(Long.MAX_VALUE, 998999999); + t = t.minusMillis(-1); + assertEquals(t.getSeconds(), Long.MAX_VALUE); + assertEquals(t.getNano(), 999999999); + } + + @Test(expectedExceptions = {ArithmeticException.class}, groups={"tck"}) + public void minusMillis_long_overflowTooBig() { + Duration t = Duration.ofSeconds(Long.MAX_VALUE, 999000000); + t.minusMillis(-1); + } + + @Test(groups={"tck"}) + public void minusMillis_long_min() { + Duration t = Duration.ofSeconds(Long.MIN_VALUE, 1000000); + t = t.minusMillis(1); + assertEquals(t.getSeconds(), Long.MIN_VALUE); + assertEquals(t.getNano(), 0); + } + + @Test(expectedExceptions = {ArithmeticException.class}, groups={"tck"}) + public void minusMillis_long_overflowTooSmall() { + Duration t = Duration.ofSeconds(Long.MIN_VALUE, 0); + t.minusMillis(1); + } + + //----------------------------------------------------------------------- + @DataProvider(name="MinusNanos") + Object[][] provider_minusNanos_long() { + return new Object[][] { + {0, 0, 0, 0, 0}, + {0, 0, 1, -1, 999999999}, + {0, 0, 999999999, -1, 1}, + {0, 0, 1000000000, -1, 0}, + {0, 0, 1000000001, -2, 999999999}, + {0, 0, 1999999999, -2, 1}, + {0, 0, 2000000000, -2, 0}, + {0, 0, -1, 0, 1}, + {0, 0, -999999999, 0, 999999999}, + {0, 0, -1000000000, 1, 0}, + {0, 0, -1000000001, 1, 1}, + {0, 0, -1999999999, 1, 999999999}, + + {1, 0, 0, 1, 0}, + {1, 0, 1, 0, 999999999}, + {1, 0, 999999999, 0, 1}, + {1, 0, 1000000000, 0, 0}, + {1, 0, 1000000001, -1, 999999999}, + {1, 0, 1999999999, -1, 1}, + {1, 0, 2000000000, -1, 0}, + {1, 0, -1, 1, 1}, + {1, 0, -999999999, 1, 999999999}, + {1, 0, -1000000000, 2, 0}, + {1, 0, -1000000001, 2, 1}, + {1, 0, -1999999999, 2, 999999999}, + + {-1, 0, 0, -1, 0}, + {-1, 0, 1, -2, 999999999}, + {-1, 0, 999999999, -2, 1}, + {-1, 0, 1000000000, -2, 0}, + {-1, 0, 1000000001, -3, 999999999}, + {-1, 0, 1999999999, -3, 1}, + {-1, 0, 2000000000, -3, 0}, + {-1, 0, -1, -1, 1}, + {-1, 0, -999999999, -1, 999999999}, + {-1, 0, -1000000000, 0, 0}, + {-1, 0, -1000000001, 0, 1}, + {-1, 0, -1999999999, 0, 999999999}, + + {1, 1, 0, 1, 1}, + {1, 1, 1, 1, 0}, + {1, 1, 999999998, 0, 3}, + {1, 1, 999999999, 0, 2}, + {1, 1, 1000000000, 0, 1}, + {1, 1, 1999999998, -1, 3}, + {1, 1, 1999999999, -1, 2}, + {1, 1, 2000000000, -1, 1}, + {1, 1, -1, 1, 2}, + {1, 1, -2, 1, 3}, + {1, 1, -1000000000, 2, 1}, + {1, 1, -1000000001, 2, 2}, + {1, 1, -1000000002, 2, 3}, + {1, 1, -2000000000, 3, 1}, + + {1, 999999999, 0, 1, 999999999}, + {1, 999999999, 1, 1, 999999998}, + {1, 999999999, 999999999, 1, 0}, + {1, 999999999, 1000000000, 0, 999999999}, + {1, 999999999, 1000000001, 0, 999999998}, + {1, 999999999, -1, 2, 0}, + {1, 999999999, -1000000000, 2, 999999999}, + {1, 999999999, -1000000001, 3, 0}, + {1, 999999999, -1999999999, 3, 999999998}, + {1, 999999999, -2000000000, 3, 999999999}, + + {Long.MAX_VALUE, 0, -999999999, Long.MAX_VALUE, 999999999}, + {Long.MAX_VALUE - 1, 0, -1999999999, Long.MAX_VALUE, 999999999}, + {Long.MIN_VALUE, 1, 1, Long.MIN_VALUE, 0}, + {Long.MIN_VALUE + 1, 1, 1000000001, Long.MIN_VALUE, 0}, + }; + } + + @Test(dataProvider="MinusNanos", groups={"tck"}) + public void minusNanos_long(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Duration t = Duration.ofSeconds(seconds, nanos); + t = t.minusNanos(amount); + assertEquals(t.getSeconds(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions = {ArithmeticException.class}, groups={"tck"}) + public void minusNanos_long_overflowTooBig() { + Duration t = Duration.ofSeconds(Long.MAX_VALUE, 999999999); + t.minusNanos(-1); + } + + @Test(expectedExceptions = {ArithmeticException.class}, groups={"tck"}) + public void minusNanos_long_overflowTooSmall() { + Duration t = Duration.ofSeconds(Long.MIN_VALUE, 0); + t.minusNanos(1); + } + + //----------------------------------------------------------------------- + // multipliedBy() + //----------------------------------------------------------------------- + @DataProvider(name="MultipliedBy") + Object[][] provider_multipliedBy() { + return new Object[][] { + {-4, 666666667, -3, 9, 999999999}, + {-4, 666666667, -2, 6, 666666666}, + {-4, 666666667, -1, 3, 333333333}, + {-4, 666666667, 0, 0, 0}, + {-4, 666666667, 1, -4, 666666667}, + {-4, 666666667, 2, -7, 333333334}, + {-4, 666666667, 3, -10, 000000001}, + + {-3, 0, -3, 9, 0}, + {-3, 0, -2, 6, 0}, + {-3, 0, -1, 3, 0}, + {-3, 0, 0, 0, 0}, + {-3, 0, 1, -3, 0}, + {-3, 0, 2, -6, 0}, + {-3, 0, 3, -9, 0}, + + {-2, 0, -3, 6, 0}, + {-2, 0, -2, 4, 0}, + {-2, 0, -1, 2, 0}, + {-2, 0, 0, 0, 0}, + {-2, 0, 1, -2, 0}, + {-2, 0, 2, -4, 0}, + {-2, 0, 3, -6, 0}, + + {-1, 0, -3, 3, 0}, + {-1, 0, -2, 2, 0}, + {-1, 0, -1, 1, 0}, + {-1, 0, 0, 0, 0}, + {-1, 0, 1, -1, 0}, + {-1, 0, 2, -2, 0}, + {-1, 0, 3, -3, 0}, + + {-1, 500000000, -3, 1, 500000000}, + {-1, 500000000, -2, 1, 0}, + {-1, 500000000, -1, 0, 500000000}, + {-1, 500000000, 0, 0, 0}, + {-1, 500000000, 1, -1, 500000000}, + {-1, 500000000, 2, -1, 0}, + {-1, 500000000, 3, -2, 500000000}, + + {0, 0, -3, 0, 0}, + {0, 0, -2, 0, 0}, + {0, 0, -1, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 2, 0, 0}, + {0, 0, 3, 0, 0}, + + {0, 500000000, -3, -2, 500000000}, + {0, 500000000, -2, -1, 0}, + {0, 500000000, -1, -1, 500000000}, + {0, 500000000, 0, 0, 0}, + {0, 500000000, 1, 0, 500000000}, + {0, 500000000, 2, 1, 0}, + {0, 500000000, 3, 1, 500000000}, + + {1, 0, -3, -3, 0}, + {1, 0, -2, -2, 0}, + {1, 0, -1, -1, 0}, + {1, 0, 0, 0, 0}, + {1, 0, 1, 1, 0}, + {1, 0, 2, 2, 0}, + {1, 0, 3, 3, 0}, + + {2, 0, -3, -6, 0}, + {2, 0, -2, -4, 0}, + {2, 0, -1, -2, 0}, + {2, 0, 0, 0, 0}, + {2, 0, 1, 2, 0}, + {2, 0, 2, 4, 0}, + {2, 0, 3, 6, 0}, + + {3, 0, -3, -9, 0}, + {3, 0, -2, -6, 0}, + {3, 0, -1, -3, 0}, + {3, 0, 0, 0, 0}, + {3, 0, 1, 3, 0}, + {3, 0, 2, 6, 0}, + {3, 0, 3, 9, 0}, + + {3, 333333333, -3, -10, 000000001}, + {3, 333333333, -2, -7, 333333334}, + {3, 333333333, -1, -4, 666666667}, + {3, 333333333, 0, 0, 0}, + {3, 333333333, 1, 3, 333333333}, + {3, 333333333, 2, 6, 666666666}, + {3, 333333333, 3, 9, 999999999}, + }; + } + + @Test(dataProvider="MultipliedBy", groups={"tck"}) + public void multipliedBy(long seconds, int nanos, int multiplicand, long expectedSeconds, int expectedNanos) { + Duration t = Duration.ofSeconds(seconds, nanos); + t = t.multipliedBy(multiplicand); + assertEquals(t.getSeconds(), expectedSeconds); + assertEquals(t.getNano(), expectedNanos); + } + + @Test(groups={"tck"}) + public void multipliedBy_max() { + Duration test = Duration.ofSeconds(1); + assertEquals(test.multipliedBy(Long.MAX_VALUE), Duration.ofSeconds(Long.MAX_VALUE)); + } + + @Test(groups={"tck"}) + public void multipliedBy_min() { + Duration test = Duration.ofSeconds(1); + assertEquals(test.multipliedBy(Long.MIN_VALUE), Duration.ofSeconds(Long.MIN_VALUE)); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void multipliedBy_tooBig() { + Duration test = Duration.ofSeconds(1, 1); + test.multipliedBy(Long.MAX_VALUE); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void multipliedBy_tooBig_negative() { + Duration test = Duration.ofSeconds(1, 1); + test.multipliedBy(Long.MIN_VALUE); + } + + //----------------------------------------------------------------------- + // dividedBy() + //----------------------------------------------------------------------- + @DataProvider(name="DividedBy") + Object[][] provider_dividedBy() { + return new Object[][] { + {-4, 666666667, -3, 1, 111111111}, + {-4, 666666667, -2, 1, 666666666}, + {-4, 666666667, -1, 3, 333333333}, + {-4, 666666667, 1, -4, 666666667}, + {-4, 666666667, 2, -2, 333333334}, + {-4, 666666667, 3, -2, 888888889}, + + {-3, 0, -3, 1, 0}, + {-3, 0, -2, 1, 500000000}, + {-3, 0, -1, 3, 0}, + {-3, 0, 1, -3, 0}, + {-3, 0, 2, -2, 500000000}, + {-3, 0, 3, -1, 0}, + + {-2, 0, -3, 0, 666666666}, + {-2, 0, -2, 1, 0}, + {-2, 0, -1, 2, 0}, + {-2, 0, 1, -2, 0}, + {-2, 0, 2, -1, 0}, + {-2, 0, 3, -1, 333333334}, + + {-1, 0, -3, 0, 333333333}, + {-1, 0, -2, 0, 500000000}, + {-1, 0, -1, 1, 0}, + {-1, 0, 1, -1, 0}, + {-1, 0, 2, -1, 500000000}, + {-1, 0, 3, -1, 666666667}, + + {-1, 500000000, -3, 0, 166666666}, + {-1, 500000000, -2, 0, 250000000}, + {-1, 500000000, -1, 0, 500000000}, + {-1, 500000000, 1, -1, 500000000}, + {-1, 500000000, 2, -1, 750000000}, + {-1, 500000000, 3, -1, 833333334}, + + {0, 0, -3, 0, 0}, + {0, 0, -2, 0, 0}, + {0, 0, -1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 2, 0, 0}, + {0, 0, 3, 0, 0}, + + {0, 500000000, -3, -1, 833333334}, + {0, 500000000, -2, -1, 750000000}, + {0, 500000000, -1, -1, 500000000}, + {0, 500000000, 1, 0, 500000000}, + {0, 500000000, 2, 0, 250000000}, + {0, 500000000, 3, 0, 166666666}, + + {1, 0, -3, -1, 666666667}, + {1, 0, -2, -1, 500000000}, + {1, 0, -1, -1, 0}, + {1, 0, 1, 1, 0}, + {1, 0, 2, 0, 500000000}, + {1, 0, 3, 0, 333333333}, + + {2, 0, -3, -1, 333333334}, + {2, 0, -2, -1, 0}, + {2, 0, -1, -2, 0}, + {2, 0, 1, 2, 0}, + {2, 0, 2, 1, 0}, + {2, 0, 3, 0, 666666666}, + + {3, 0, -3, -1, 0}, + {3, 0, -2, -2, 500000000}, + {3, 0, -1, -3, 0}, + {3, 0, 1, 3, 0}, + {3, 0, 2, 1, 500000000}, + {3, 0, 3, 1, 0}, + + {3, 333333333, -3, -2, 888888889}, + {3, 333333333, -2, -2, 333333334}, + {3, 333333333, -1, -4, 666666667}, + {3, 333333333, 1, 3, 333333333}, + {3, 333333333, 2, 1, 666666666}, + {3, 333333333, 3, 1, 111111111}, + }; + } + + @Test(dataProvider="DividedBy", groups={"tck"}) + public void dividedBy(long seconds, int nanos, int divisor, long expectedSeconds, int expectedNanos) { + Duration t = Duration.ofSeconds(seconds, nanos); + t = t.dividedBy(divisor); + assertEquals(t.getSeconds(), expectedSeconds); + assertEquals(t.getNano(), expectedNanos); + } + + @Test(dataProvider="DividedBy", expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void dividedByZero(long seconds, int nanos, int divisor, long expectedSeconds, int expectedNanos) { + Duration t = Duration.ofSeconds(seconds, nanos); + t.dividedBy(0); + fail(t + " divided by zero did not throw ArithmeticException"); + } + + @Test(groups={"tck"}) + public void dividedBy_max() { + Duration test = Duration.ofSeconds(Long.MAX_VALUE); + assertEquals(test.dividedBy(Long.MAX_VALUE), Duration.ofSeconds(1)); + } + + //----------------------------------------------------------------------- + // negated() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_negated() { + assertEquals(Duration.ofSeconds(0).negated(), Duration.ofSeconds(0)); + assertEquals(Duration.ofSeconds(12).negated(), Duration.ofSeconds(-12)); + assertEquals(Duration.ofSeconds(-12).negated(), Duration.ofSeconds(12)); + assertEquals(Duration.ofSeconds(12, 20).negated(), Duration.ofSeconds(-12, -20)); + assertEquals(Duration.ofSeconds(12, -20).negated(), Duration.ofSeconds(-12, 20)); + assertEquals(Duration.ofSeconds(-12, -20).negated(), Duration.ofSeconds(12, 20)); + assertEquals(Duration.ofSeconds(-12, 20).negated(), Duration.ofSeconds(12, -20)); + assertEquals(Duration.ofSeconds(Long.MAX_VALUE).negated(), Duration.ofSeconds(-Long.MAX_VALUE)); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_negated_overflow() { + Duration.ofSeconds(Long.MIN_VALUE).negated(); + } + + //----------------------------------------------------------------------- + // abs() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_abs() { + assertEquals(Duration.ofSeconds(0).abs(), Duration.ofSeconds(0)); + assertEquals(Duration.ofSeconds(12).abs(), Duration.ofSeconds(12)); + assertEquals(Duration.ofSeconds(-12).abs(), Duration.ofSeconds(12)); + assertEquals(Duration.ofSeconds(12, 20).abs(), Duration.ofSeconds(12, 20)); + assertEquals(Duration.ofSeconds(12, -20).abs(), Duration.ofSeconds(12, -20)); + assertEquals(Duration.ofSeconds(-12, -20).abs(), Duration.ofSeconds(12, 20)); + assertEquals(Duration.ofSeconds(-12, 20).abs(), Duration.ofSeconds(12, -20)); + assertEquals(Duration.ofSeconds(Long.MAX_VALUE).abs(), Duration.ofSeconds(Long.MAX_VALUE)); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_abs_overflow() { + Duration.ofSeconds(Long.MIN_VALUE).abs(); + } + + //----------------------------------------------------------------------- + // toNanos() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toNanos() { + Duration test = Duration.ofSeconds(321, 123456789); + assertEquals(test.toNanos(), 321123456789L); + } + + @Test(groups={"tck"}) + public void test_toNanos_max() { + Duration test = Duration.ofSeconds(0, Long.MAX_VALUE); + assertEquals(test.toNanos(), Long.MAX_VALUE); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_toNanos_tooBig() { + Duration test = Duration.ofSeconds(0, Long.MAX_VALUE).plusNanos(1); + test.toNanos(); + } + + //----------------------------------------------------------------------- + // toMillis() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toMillis() { + Duration test = Duration.ofSeconds(321, 123456789); + assertEquals(test.toMillis(), 321000 + 123); + } + + @Test(groups={"tck"}) + public void test_toMillis_max() { + Duration test = Duration.ofSeconds(Long.MAX_VALUE / 1000, (Long.MAX_VALUE % 1000) * 1000000); + assertEquals(test.toMillis(), Long.MAX_VALUE); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_toMillis_tooBig() { + Duration test = Duration.ofSeconds(Long.MAX_VALUE / 1000, ((Long.MAX_VALUE % 1000) + 1) * 1000000); + test.toMillis(); + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_comparisons() { + doTest_comparisons_Duration( + Duration.ofSeconds(-2L, 0), + Duration.ofSeconds(-2L, 999999998), + Duration.ofSeconds(-2L, 999999999), + Duration.ofSeconds(-1L, 0), + Duration.ofSeconds(-1L, 1), + Duration.ofSeconds(-1L, 999999998), + Duration.ofSeconds(-1L, 999999999), + Duration.ofSeconds(0L, 0), + Duration.ofSeconds(0L, 1), + Duration.ofSeconds(0L, 2), + Duration.ofSeconds(0L, 999999999), + Duration.ofSeconds(1L, 0), + Duration.ofSeconds(2L, 0) + ); + } + + void doTest_comparisons_Duration(Duration... durations) { + for (int i = 0; i < durations.length; i++) { + Duration a = durations[i]; + for (int j = 0; j < durations.length; j++) { + Duration b = durations[j]; + if (i < j) { + assertEquals(a.compareTo(b)< 0, true, a + " <=> " + b); + assertEquals(a.isLessThan(b), true, a + " <=> " + b); + assertEquals(a.isGreaterThan(b), false, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else if (i > j) { + assertEquals(a.compareTo(b) > 0, true, a + " <=> " + b); + assertEquals(a.isLessThan(b), false, a + " <=> " + b); + assertEquals(a.isGreaterThan(b), true, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else { + assertEquals(a.compareTo(b), 0, a + " <=> " + b); + assertEquals(a.isLessThan(b), false, a + " <=> " + b); + assertEquals(a.isGreaterThan(b), false, a + " <=> " + b); + assertEquals(a.equals(b), true, a + " <=> " + b); + } + } + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_compareTo_ObjectNull() { + Duration a = Duration.ofSeconds(0L, 0); + a.compareTo(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isLessThan_ObjectNull() { + Duration a = Duration.ofSeconds(0L, 0); + a.isLessThan(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isGreaterThan_ObjectNull() { + Duration a = Duration.ofSeconds(0L, 0); + a.isGreaterThan(null); + } + + @Test(expectedExceptions=ClassCastException.class, groups={"tck"}) + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void compareToNonDuration() { + Comparable c = Duration.ofSeconds(0L); + c.compareTo(new Object()); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_equals() { + Duration test5a = Duration.ofSeconds(5L, 20); + Duration test5b = Duration.ofSeconds(5L, 20); + Duration test5n = Duration.ofSeconds(5L, 30); + Duration test6 = Duration.ofSeconds(6L, 20); + + assertEquals(test5a.equals(test5a), true); + assertEquals(test5a.equals(test5b), true); + assertEquals(test5a.equals(test5n), false); + assertEquals(test5a.equals(test6), false); + + assertEquals(test5b.equals(test5a), true); + assertEquals(test5b.equals(test5b), true); + assertEquals(test5b.equals(test5n), false); + assertEquals(test5b.equals(test6), false); + + assertEquals(test5n.equals(test5a), false); + assertEquals(test5n.equals(test5b), false); + assertEquals(test5n.equals(test5n), true); + assertEquals(test5n.equals(test6), false); + + assertEquals(test6.equals(test5a), false); + assertEquals(test6.equals(test5b), false); + assertEquals(test6.equals(test5n), false); + assertEquals(test6.equals(test6), true); + } + + @Test(groups={"tck"}) + public void test_equals_null() { + Duration test5 = Duration.ofSeconds(5L, 20); + assertEquals(test5.equals(null), false); + } + + @Test(groups={"tck"}) + public void test_equals_otherClass() { + Duration test5 = Duration.ofSeconds(5L, 20); + assertEquals(test5.equals(""), false); + } + + //----------------------------------------------------------------------- + // hashCode() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_hashCode() { + Duration test5a = Duration.ofSeconds(5L, 20); + Duration test5b = Duration.ofSeconds(5L, 20); + Duration test5n = Duration.ofSeconds(5L, 30); + Duration test6 = Duration.ofSeconds(6L, 20); + + assertEquals(test5a.hashCode() == test5a.hashCode(), true); + assertEquals(test5a.hashCode() == test5b.hashCode(), true); + assertEquals(test5b.hashCode() == test5b.hashCode(), true); + + assertEquals(test5a.hashCode() == test5n.hashCode(), false); + assertEquals(test5a.hashCode() == test6.hashCode(), false); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="ToString") + Object[][] provider_toString() { + return new Object[][] { + {0, 0, "PT0S"}, + {0, 1, "PT0.000000001S"}, + {0, 10, "PT0.00000001S"}, + {0, 100, "PT0.0000001S"}, + {0, 1000, "PT0.000001S"}, + {0, 10000, "PT0.00001S"}, + {0, 100000, "PT0.0001S"}, + {0, 1000000, "PT0.001S"}, + {0, 10000000, "PT0.01S"}, + {0, 100000000, "PT0.1S"}, + {0, 120000000, "PT0.12S"}, + {0, 123000000, "PT0.123S"}, + {0, 123400000, "PT0.1234S"}, + {0, 123450000, "PT0.12345S"}, + {0, 123456000, "PT0.123456S"}, + {0, 123456700, "PT0.1234567S"}, + {0, 123456780, "PT0.12345678S"}, + {0, 123456789, "PT0.123456789S"}, + {1, 0, "PT1S"}, + {-1, 0, "PT-1S"}, + {-1, 1000, "PT-0.999999S"}, + {-1, 900000000, "PT-0.1S"}, + {Long.MAX_VALUE, 0, "PT9223372036854775807S"}, + {Long.MIN_VALUE, 0, "PT-9223372036854775808S"}, + }; + } + + @Test(dataProvider="ToString", groups={"tck"}) + public void test_toString(long seconds, int nanos, String expected) { + Duration t = Duration.ofSeconds(seconds, nanos); + assertEquals(t.toString(), expected); + } + +} diff --git a/test/java/time/tck/java/time/TCKInstant.java b/test/java/time/tck/java/time/TCKInstant.java new file mode 100644 index 0000000000000000000000000000000000000000..e1f99d925ed773fbaa98731461059d6da53e83a3 --- /dev/null +++ b/test/java/time/tck/java/time/TCKInstant.java @@ -0,0 +1,1680 @@ +/* + * 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. + * + * 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 tck.java.time; + +import static java.time.temporal.ChronoField.INSTANT_SECONDS; +import static java.time.temporal.ChronoField.MICRO_OF_SECOND; +import static java.time.temporal.ChronoField.MILLI_OF_SECOND; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +import java.time.Clock; +import java.time.DateTimeException; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.JulianFields; +import java.time.temporal.Queries; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test Instant. + */ +@Test +public class TCKInstant extends AbstractDateTimeTest { + + private static final long MIN_SECOND = Instant.MIN.getEpochSecond(); + private static final long MAX_SECOND = Instant.MAX.getEpochSecond(); + + private Instant TEST_12345_123456789; + + @BeforeMethod + public void setUp() { + TEST_12345_123456789 = Instant.ofEpochSecond(12345, 123456789); + } + + //----------------------------------------------------------------------- + @Override + protected List samples() { + TemporalAccessor[] array = {TEST_12345_123456789, Instant.MIN, Instant.MAX, Instant.EPOCH}; + return Arrays.asList(array); + } + + @Override + protected List validFields() { + TemporalField[] array = { + NANO_OF_SECOND, + MICRO_OF_SECOND, + MILLI_OF_SECOND, + INSTANT_SECONDS, + }; + return Arrays.asList(array); + } + + @Override + protected List invalidFields() { + List list = new ArrayList<>(Arrays.asList(ChronoField.values())); + list.removeAll(validFields()); + list.add(JulianFields.JULIAN_DAY); + list.add(JulianFields.MODIFIED_JULIAN_DAY); + list.add(JulianFields.RATA_DIE); + return list; + } + + //----------------------------------------------------------------------- + @Test + public void test_serialization() throws Exception { + assertSerializable(Instant.ofEpochMilli(134l)); + } + + @Test + public void test_serialization_format() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(2); + dos.writeLong(654321); + dos.writeInt(123456789); + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(Instant.ofEpochSecond(654321, 123456789), bytes); + } + + //----------------------------------------------------------------------- + private void check(Instant instant, long epochSecs, int nos) { + assertEquals(instant.getEpochSecond(), epochSecs); + assertEquals(instant.getNano(), nos); + assertEquals(instant, instant); + assertEquals(instant.hashCode(), instant.hashCode()); + } + + //----------------------------------------------------------------------- + @Test + public void constant_EPOCH() { + check(Instant.EPOCH, 0, 0); + } + + @Test + public void constant_MIN() { + check(Instant.MIN, -31557014167219200L, 0); + } + + @Test + public void constant_MAX() { + check(Instant.MAX, 31556889864403199L, 999_999_999); + } + + //----------------------------------------------------------------------- + // now() + //----------------------------------------------------------------------- + @Test + public void now() { + Instant expected = Instant.now(Clock.systemUTC()); + Instant test = Instant.now(); + long diff = Math.abs(test.toEpochMilli() - expected.toEpochMilli()); + assertTrue(diff < 100); // less than 0.1 secs + } + + //----------------------------------------------------------------------- + // now(Clock) + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class) + public void now_Clock_nullClock() { + Instant.now(null); + } + + @Test + public void now_Clock_allSecsInDay_utc() { + for (int i = 0; i < (2 * 24 * 60 * 60); i++) { + Instant expected = Instant.ofEpochSecond(i).plusNanos(123456789L); + Clock clock = Clock.fixed(expected, ZoneOffset.UTC); + Instant test = Instant.now(clock); + assertEquals(test, expected); + } + } + + @Test + public void now_Clock_allSecsInDay_beforeEpoch() { + for (int i =-1; i >= -(24 * 60 * 60); i--) { + Instant expected = Instant.ofEpochSecond(i).plusNanos(123456789L); + Clock clock = Clock.fixed(expected, ZoneOffset.UTC); + Instant test = Instant.now(clock); + assertEquals(test, expected); + } + } + + //----------------------------------------------------------------------- + // ofEpochSecond(long) + //----------------------------------------------------------------------- + @Test + public void factory_seconds_long() { + for (long i = -2; i <= 2; i++) { + Instant t = Instant.ofEpochSecond(i); + assertEquals(t.getEpochSecond(), i); + assertEquals(t.getNano(), 0); + } + } + + //----------------------------------------------------------------------- + // ofEpochSecond(long,long) + //----------------------------------------------------------------------- + @Test + public void factory_seconds_long_long() { + for (long i = -2; i <= 2; i++) { + for (int j = 0; j < 10; j++) { + Instant t = Instant.ofEpochSecond(i, j); + assertEquals(t.getEpochSecond(), i); + assertEquals(t.getNano(), j); + } + for (int j = -10; j < 0; j++) { + Instant t = Instant.ofEpochSecond(i, j); + assertEquals(t.getEpochSecond(), i - 1); + assertEquals(t.getNano(), j + 1000000000); + } + for (int j = 999999990; j < 1000000000; j++) { + Instant t = Instant.ofEpochSecond(i, j); + assertEquals(t.getEpochSecond(), i); + assertEquals(t.getNano(), j); + } + } + } + + @Test + public void factory_seconds_long_long_nanosNegativeAdjusted() { + Instant test = Instant.ofEpochSecond(2L, -1); + assertEquals(test.getEpochSecond(), 1); + assertEquals(test.getNano(), 999999999); + } + + @Test(expectedExceptions=DateTimeException.class) + public void factory_seconds_long_long_tooBig() { + Instant.ofEpochSecond(MAX_SECOND, 1000000000); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void factory_seconds_long_long_tooBigBig() { + Instant.ofEpochSecond(Long.MAX_VALUE, Long.MAX_VALUE); + } + + //----------------------------------------------------------------------- + // ofEpochMilli(long) + //----------------------------------------------------------------------- + @DataProvider(name="MillisInstantNoNanos") + Object[][] provider_factory_millis_long() { + return new Object[][] { + {0, 0, 0}, + {1, 0, 1000000}, + {2, 0, 2000000}, + {999, 0, 999000000}, + {1000, 1, 0}, + {1001, 1, 1000000}, + {-1, -1, 999000000}, + {-2, -1, 998000000}, + {-999, -1, 1000000}, + {-1000, -1, 0}, + {-1001, -2, 999000000}, + }; + } + + @Test(dataProvider="MillisInstantNoNanos") + public void factory_millis_long(long millis, long expectedSeconds, int expectedNanoOfSecond) { + Instant t = Instant.ofEpochMilli(millis); + assertEquals(t.getEpochSecond(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + //----------------------------------------------------------------------- + // parse(String) + //----------------------------------------------------------------------- + // see also parse tests under toString() + @DataProvider(name="Parse") + Object[][] provider_factory_parse() { + return new Object[][] { + {"1970-01-01T00:00:00Z", 0, 0}, + {"1970-01-01t00:00:00Z", 0, 0}, + {"1970-01-01T00:00:00z", 0, 0}, + {"1970-01-01T00:00:00.0Z", 0, 0}, + {"1970-01-01T00:00:00.000000000Z", 0, 0}, + + {"1970-01-01T00:00:00.000000001Z", 0, 1}, + {"1970-01-01T00:00:00.100000000Z", 0, 100000000}, + {"1970-01-01T00:00:01Z", 1, 0}, + {"1970-01-01T00:01:00Z", 60, 0}, + {"1970-01-01T00:01:01Z", 61, 0}, + {"1970-01-01T00:01:01.000000001Z", 61, 1}, + {"1970-01-01T01:00:00.000000000Z", 3600, 0}, + {"1970-01-01T01:01:01.000000001Z", 3661, 1}, + {"1970-01-02T01:01:01.100000000Z", 90061, 100000000}, + }; + } + + @Test(dataProvider="Parse") + public void factory_parse(String text, long expectedEpochSeconds, int expectedNanoOfSecond) { + Instant t = Instant.parse(text); + assertEquals(t.getEpochSecond(), expectedEpochSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @Test(dataProvider="Parse") + public void factory_parseLowercase(String text, long expectedEpochSeconds, int expectedNanoOfSecond) { + Instant t = Instant.parse(text.toLowerCase(Locale.ENGLISH)); + assertEquals(t.getEpochSecond(), expectedEpochSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + +// TODO: should comma be accepted? +// @Test(dataProvider="Parse") +// public void factory_parse_comma(String text, long expectedEpochSeconds, int expectedNanoOfSecond) { +// text = text.replace('.', ','); +// Instant t = Instant.parse(text); +// assertEquals(t.getEpochSecond(), expectedEpochSeconds); +// assertEquals(t.getNano(), expectedNanoOfSecond); +// } + + @DataProvider(name="ParseFailures") + Object[][] provider_factory_parseFailures() { + return new Object[][] { + {""}, + {"Z"}, + {"1970-01-01T00:00:00"}, + {"1970-01-01T00:00:0Z"}, + {"1970-01-01T00:00:00.0000000000Z"}, + }; + } + + @Test(dataProvider="ParseFailures", expectedExceptions=DateTimeParseException.class) + public void factory_parseFailures(String text) { + Instant.parse(text); + } + + @Test(dataProvider="ParseFailures", expectedExceptions=DateTimeParseException.class) + public void factory_parseFailures_comma(String text) { + text = text.replace('.', ','); + Instant.parse(text); + } + + @Test(expectedExceptions=NullPointerException.class) + public void factory_parse_nullText() { + Instant.parse(null); + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @Test + public void test_get_TemporalField() { + Instant test = TEST_12345_123456789; + assertEquals(test.get(ChronoField.NANO_OF_SECOND), 123456789); + assertEquals(test.get(ChronoField.MICRO_OF_SECOND), 123456); + assertEquals(test.get(ChronoField.MILLI_OF_SECOND), 123); + } + + @Test + public void test_getLong_TemporalField() { + Instant test = TEST_12345_123456789; + assertEquals(test.getLong(ChronoField.NANO_OF_SECOND), 123456789); + assertEquals(test.getLong(ChronoField.MICRO_OF_SECOND), 123456); + assertEquals(test.getLong(ChronoField.MILLI_OF_SECOND), 123); + assertEquals(test.getLong(ChronoField.INSTANT_SECONDS), 12345); + } + + //----------------------------------------------------------------------- + // query(TemporalQuery) + //----------------------------------------------------------------------- + @Test + public void test_query_chrono() { + assertEquals(TEST_12345_123456789.query(Queries.chrono()), null); + assertEquals(Queries.chrono().queryFrom(TEST_12345_123456789), null); + } + + @Test + public void test_query_zoneId() { + assertEquals(TEST_12345_123456789.query(Queries.zoneId()), null); + assertEquals(Queries.zoneId().queryFrom(TEST_12345_123456789), null); + } + + @Test + public void test_query_precision() { + assertEquals(TEST_12345_123456789.query(Queries.precision()), NANOS); + assertEquals(Queries.precision().queryFrom(TEST_12345_123456789), NANOS); + } + + @Test + public void test_query_offset() { + assertEquals(TEST_12345_123456789.query(Queries.offset()), null); + assertEquals(Queries.offset().queryFrom(TEST_12345_123456789), null); + } + + @Test + public void test_query_zone() { + assertEquals(TEST_12345_123456789.query(Queries.zone()), null); + assertEquals(Queries.zone().queryFrom(TEST_12345_123456789), null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_query_null() { + TEST_12345_123456789.query(null); + } + + //----------------------------------------------------------------------- + @DataProvider(name="Plus") + Object[][] provider_plus() { + return new Object[][] { + {MIN_SECOND, 0, -MIN_SECOND, 0, 0, 0}, + + {MIN_SECOND, 0, 1, 0, MIN_SECOND + 1, 0}, + {MIN_SECOND, 0, 0, 500, MIN_SECOND, 500}, + {MIN_SECOND, 0, 0, 1000000000, MIN_SECOND + 1, 0}, + + {MIN_SECOND + 1, 0, -1, 0, MIN_SECOND, 0}, + {MIN_SECOND + 1, 0, 0, -500, MIN_SECOND, 999999500}, + {MIN_SECOND + 1, 0, 0, -1000000000, MIN_SECOND, 0}, + + {-4, 666666667, -4, 666666667, -7, 333333334}, + {-4, 666666667, -3, 0, -7, 666666667}, + {-4, 666666667, -2, 0, -6, 666666667}, + {-4, 666666667, -1, 0, -5, 666666667}, + {-4, 666666667, -1, 333333334, -4, 1}, + {-4, 666666667, -1, 666666667, -4, 333333334}, + {-4, 666666667, -1, 999999999, -4, 666666666}, + {-4, 666666667, 0, 0, -4, 666666667}, + {-4, 666666667, 0, 1, -4, 666666668}, + {-4, 666666667, 0, 333333333, -3, 0}, + {-4, 666666667, 0, 666666666, -3, 333333333}, + {-4, 666666667, 1, 0, -3, 666666667}, + {-4, 666666667, 2, 0, -2, 666666667}, + {-4, 666666667, 3, 0, -1, 666666667}, + {-4, 666666667, 3, 333333333, 0, 0}, + + {-3, 0, -4, 666666667, -7, 666666667}, + {-3, 0, -3, 0, -6, 0}, + {-3, 0, -2, 0, -5, 0}, + {-3, 0, -1, 0, -4, 0}, + {-3, 0, -1, 333333334, -4, 333333334}, + {-3, 0, -1, 666666667, -4, 666666667}, + {-3, 0, -1, 999999999, -4, 999999999}, + {-3, 0, 0, 0, -3, 0}, + {-3, 0, 0, 1, -3, 1}, + {-3, 0, 0, 333333333, -3, 333333333}, + {-3, 0, 0, 666666666, -3, 666666666}, + {-3, 0, 1, 0, -2, 0}, + {-3, 0, 2, 0, -1, 0}, + {-3, 0, 3, 0, 0, 0}, + {-3, 0, 3, 333333333, 0, 333333333}, + + {-2, 0, -4, 666666667, -6, 666666667}, + {-2, 0, -3, 0, -5, 0}, + {-2, 0, -2, 0, -4, 0}, + {-2, 0, -1, 0, -3, 0}, + {-2, 0, -1, 333333334, -3, 333333334}, + {-2, 0, -1, 666666667, -3, 666666667}, + {-2, 0, -1, 999999999, -3, 999999999}, + {-2, 0, 0, 0, -2, 0}, + {-2, 0, 0, 1, -2, 1}, + {-2, 0, 0, 333333333, -2, 333333333}, + {-2, 0, 0, 666666666, -2, 666666666}, + {-2, 0, 1, 0, -1, 0}, + {-2, 0, 2, 0, 0, 0}, + {-2, 0, 3, 0, 1, 0}, + {-2, 0, 3, 333333333, 1, 333333333}, + + {-1, 0, -4, 666666667, -5, 666666667}, + {-1, 0, -3, 0, -4, 0}, + {-1, 0, -2, 0, -3, 0}, + {-1, 0, -1, 0, -2, 0}, + {-1, 0, -1, 333333334, -2, 333333334}, + {-1, 0, -1, 666666667, -2, 666666667}, + {-1, 0, -1, 999999999, -2, 999999999}, + {-1, 0, 0, 0, -1, 0}, + {-1, 0, 0, 1, -1, 1}, + {-1, 0, 0, 333333333, -1, 333333333}, + {-1, 0, 0, 666666666, -1, 666666666}, + {-1, 0, 1, 0, 0, 0}, + {-1, 0, 2, 0, 1, 0}, + {-1, 0, 3, 0, 2, 0}, + {-1, 0, 3, 333333333, 2, 333333333}, + + {-1, 666666667, -4, 666666667, -4, 333333334}, + {-1, 666666667, -3, 0, -4, 666666667}, + {-1, 666666667, -2, 0, -3, 666666667}, + {-1, 666666667, -1, 0, -2, 666666667}, + {-1, 666666667, -1, 333333334, -1, 1}, + {-1, 666666667, -1, 666666667, -1, 333333334}, + {-1, 666666667, -1, 999999999, -1, 666666666}, + {-1, 666666667, 0, 0, -1, 666666667}, + {-1, 666666667, 0, 1, -1, 666666668}, + {-1, 666666667, 0, 333333333, 0, 0}, + {-1, 666666667, 0, 666666666, 0, 333333333}, + {-1, 666666667, 1, 0, 0, 666666667}, + {-1, 666666667, 2, 0, 1, 666666667}, + {-1, 666666667, 3, 0, 2, 666666667}, + {-1, 666666667, 3, 333333333, 3, 0}, + + {0, 0, -4, 666666667, -4, 666666667}, + {0, 0, -3, 0, -3, 0}, + {0, 0, -2, 0, -2, 0}, + {0, 0, -1, 0, -1, 0}, + {0, 0, -1, 333333334, -1, 333333334}, + {0, 0, -1, 666666667, -1, 666666667}, + {0, 0, -1, 999999999, -1, 999999999}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, 0, 1}, + {0, 0, 0, 333333333, 0, 333333333}, + {0, 0, 0, 666666666, 0, 666666666}, + {0, 0, 1, 0, 1, 0}, + {0, 0, 2, 0, 2, 0}, + {0, 0, 3, 0, 3, 0}, + {0, 0, 3, 333333333, 3, 333333333}, + + {0, 333333333, -4, 666666667, -3, 0}, + {0, 333333333, -3, 0, -3, 333333333}, + {0, 333333333, -2, 0, -2, 333333333}, + {0, 333333333, -1, 0, -1, 333333333}, + {0, 333333333, -1, 333333334, -1, 666666667}, + {0, 333333333, -1, 666666667, 0, 0}, + {0, 333333333, -1, 999999999, 0, 333333332}, + {0, 333333333, 0, 0, 0, 333333333}, + {0, 333333333, 0, 1, 0, 333333334}, + {0, 333333333, 0, 333333333, 0, 666666666}, + {0, 333333333, 0, 666666666, 0, 999999999}, + {0, 333333333, 1, 0, 1, 333333333}, + {0, 333333333, 2, 0, 2, 333333333}, + {0, 333333333, 3, 0, 3, 333333333}, + {0, 333333333, 3, 333333333, 3, 666666666}, + + {1, 0, -4, 666666667, -3, 666666667}, + {1, 0, -3, 0, -2, 0}, + {1, 0, -2, 0, -1, 0}, + {1, 0, -1, 0, 0, 0}, + {1, 0, -1, 333333334, 0, 333333334}, + {1, 0, -1, 666666667, 0, 666666667}, + {1, 0, -1, 999999999, 0, 999999999}, + {1, 0, 0, 0, 1, 0}, + {1, 0, 0, 1, 1, 1}, + {1, 0, 0, 333333333, 1, 333333333}, + {1, 0, 0, 666666666, 1, 666666666}, + {1, 0, 1, 0, 2, 0}, + {1, 0, 2, 0, 3, 0}, + {1, 0, 3, 0, 4, 0}, + {1, 0, 3, 333333333, 4, 333333333}, + + {2, 0, -4, 666666667, -2, 666666667}, + {2, 0, -3, 0, -1, 0}, + {2, 0, -2, 0, 0, 0}, + {2, 0, -1, 0, 1, 0}, + {2, 0, -1, 333333334, 1, 333333334}, + {2, 0, -1, 666666667, 1, 666666667}, + {2, 0, -1, 999999999, 1, 999999999}, + {2, 0, 0, 0, 2, 0}, + {2, 0, 0, 1, 2, 1}, + {2, 0, 0, 333333333, 2, 333333333}, + {2, 0, 0, 666666666, 2, 666666666}, + {2, 0, 1, 0, 3, 0}, + {2, 0, 2, 0, 4, 0}, + {2, 0, 3, 0, 5, 0}, + {2, 0, 3, 333333333, 5, 333333333}, + + {3, 0, -4, 666666667, -1, 666666667}, + {3, 0, -3, 0, 0, 0}, + {3, 0, -2, 0, 1, 0}, + {3, 0, -1, 0, 2, 0}, + {3, 0, -1, 333333334, 2, 333333334}, + {3, 0, -1, 666666667, 2, 666666667}, + {3, 0, -1, 999999999, 2, 999999999}, + {3, 0, 0, 0, 3, 0}, + {3, 0, 0, 1, 3, 1}, + {3, 0, 0, 333333333, 3, 333333333}, + {3, 0, 0, 666666666, 3, 666666666}, + {3, 0, 1, 0, 4, 0}, + {3, 0, 2, 0, 5, 0}, + {3, 0, 3, 0, 6, 0}, + {3, 0, 3, 333333333, 6, 333333333}, + + {3, 333333333, -4, 666666667, 0, 0}, + {3, 333333333, -3, 0, 0, 333333333}, + {3, 333333333, -2, 0, 1, 333333333}, + {3, 333333333, -1, 0, 2, 333333333}, + {3, 333333333, -1, 333333334, 2, 666666667}, + {3, 333333333, -1, 666666667, 3, 0}, + {3, 333333333, -1, 999999999, 3, 333333332}, + {3, 333333333, 0, 0, 3, 333333333}, + {3, 333333333, 0, 1, 3, 333333334}, + {3, 333333333, 0, 333333333, 3, 666666666}, + {3, 333333333, 0, 666666666, 3, 999999999}, + {3, 333333333, 1, 0, 4, 333333333}, + {3, 333333333, 2, 0, 5, 333333333}, + {3, 333333333, 3, 0, 6, 333333333}, + {3, 333333333, 3, 333333333, 6, 666666666}, + + {MAX_SECOND - 1, 0, 1, 0, MAX_SECOND, 0}, + {MAX_SECOND - 1, 0, 0, 500, MAX_SECOND - 1, 500}, + {MAX_SECOND - 1, 0, 0, 1000000000, MAX_SECOND, 0}, + + {MAX_SECOND, 0, -1, 0, MAX_SECOND - 1, 0}, + {MAX_SECOND, 0, 0, -500, MAX_SECOND - 1, 999999500}, + {MAX_SECOND, 0, 0, -1000000000, MAX_SECOND - 1, 0}, + + {MAX_SECOND, 0, -MAX_SECOND, 0, 0, 0}, + }; + } + + @Test(dataProvider="Plus") + public void plus_Duration(long seconds, int nanos, long otherSeconds, int otherNanos, long expectedSeconds, int expectedNanoOfSecond) { + Instant i = Instant.ofEpochSecond(seconds, nanos).plus(Duration.ofSeconds(otherSeconds, otherNanos)); + assertEquals(i.getEpochSecond(), expectedSeconds); + assertEquals(i.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions=DateTimeException.class) + public void plus_Duration_overflowTooBig() { + Instant i = Instant.ofEpochSecond(MAX_SECOND, 999999999); + i.plus(Duration.ofSeconds(0, 1)); + } + + @Test(expectedExceptions=DateTimeException.class) + public void plus_Duration_overflowTooSmall() { + Instant i = Instant.ofEpochSecond(MIN_SECOND); + i.plus(Duration.ofSeconds(-1, 999999999)); + } + + //-----------------------------------------------------------------------a + @Test(dataProvider="Plus") + public void plus_longTemporalUnit(long seconds, int nanos, long otherSeconds, int otherNanos, long expectedSeconds, int expectedNanoOfSecond) { + Instant i = Instant.ofEpochSecond(seconds, nanos).plus(otherSeconds, SECONDS).plus(otherNanos, NANOS); + assertEquals(i.getEpochSecond(), expectedSeconds); + assertEquals(i.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions=DateTimeException.class) + public void plus_longTemporalUnit_overflowTooBig() { + Instant i = Instant.ofEpochSecond(MAX_SECOND, 999999999); + i.plus(1, NANOS); + } + + @Test(expectedExceptions=DateTimeException.class) + public void plus_longTemporalUnit_overflowTooSmall() { + Instant i = Instant.ofEpochSecond(MIN_SECOND); + i.plus(999999999, NANOS); + i.plus(-1, SECONDS); + } + + //----------------------------------------------------------------------- + @DataProvider(name="PlusSeconds") + Object[][] provider_plusSeconds_long() { + return new Object[][] { + {0, 0, 0, 0, 0}, + {0, 0, 1, 1, 0}, + {0, 0, -1, -1, 0}, + {0, 0, MAX_SECOND, MAX_SECOND, 0}, + {0, 0, MIN_SECOND, MIN_SECOND, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 1, 2, 0}, + {1, 0, -1, 0, 0}, + {1, 0, MAX_SECOND - 1, MAX_SECOND, 0}, + {1, 0, MIN_SECOND, MIN_SECOND + 1, 0}, + {1, 1, 0, 1, 1}, + {1, 1, 1, 2, 1}, + {1, 1, -1, 0, 1}, + {1, 1, MAX_SECOND - 1, MAX_SECOND, 1}, + {1, 1, MIN_SECOND, MIN_SECOND + 1, 1}, + {-1, 1, 0, -1, 1}, + {-1, 1, 1, 0, 1}, + {-1, 1, -1, -2, 1}, + {-1, 1, MAX_SECOND, MAX_SECOND - 1, 1}, + {-1, 1, MIN_SECOND + 1, MIN_SECOND, 1}, + + {MAX_SECOND, 2, -MAX_SECOND, 0, 2}, + {MIN_SECOND, 2, -MIN_SECOND, 0, 2}, + }; + } + + @Test(dataProvider="PlusSeconds") + public void plusSeconds_long(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Instant t = Instant.ofEpochSecond(seconds, nanos); + t = t.plusSeconds(amount); + assertEquals(t.getEpochSecond(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void plusSeconds_long_overflowTooBig() { + Instant t = Instant.ofEpochSecond(1, 0); + t.plusSeconds(Long.MAX_VALUE); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void plusSeconds_long_overflowTooSmall() { + Instant t = Instant.ofEpochSecond(-1, 0); + t.plusSeconds(Long.MIN_VALUE); + } + + //----------------------------------------------------------------------- + @DataProvider(name="PlusMillis") + Object[][] provider_plusMillis_long() { + return new Object[][] { + {0, 0, 0, 0, 0}, + {0, 0, 1, 0, 1000000}, + {0, 0, 999, 0, 999000000}, + {0, 0, 1000, 1, 0}, + {0, 0, 1001, 1, 1000000}, + {0, 0, 1999, 1, 999000000}, + {0, 0, 2000, 2, 0}, + {0, 0, -1, -1, 999000000}, + {0, 0, -999, -1, 1000000}, + {0, 0, -1000, -1, 0}, + {0, 0, -1001, -2, 999000000}, + {0, 0, -1999, -2, 1000000}, + + {0, 1, 0, 0, 1}, + {0, 1, 1, 0, 1000001}, + {0, 1, 998, 0, 998000001}, + {0, 1, 999, 0, 999000001}, + {0, 1, 1000, 1, 1}, + {0, 1, 1998, 1, 998000001}, + {0, 1, 1999, 1, 999000001}, + {0, 1, 2000, 2, 1}, + {0, 1, -1, -1, 999000001}, + {0, 1, -2, -1, 998000001}, + {0, 1, -1000, -1, 1}, + {0, 1, -1001, -2, 999000001}, + + {0, 1000000, 0, 0, 1000000}, + {0, 1000000, 1, 0, 2000000}, + {0, 1000000, 998, 0, 999000000}, + {0, 1000000, 999, 1, 0}, + {0, 1000000, 1000, 1, 1000000}, + {0, 1000000, 1998, 1, 999000000}, + {0, 1000000, 1999, 2, 0}, + {0, 1000000, 2000, 2, 1000000}, + {0, 1000000, -1, 0, 0}, + {0, 1000000, -2, -1, 999000000}, + {0, 1000000, -999, -1, 2000000}, + {0, 1000000, -1000, -1, 1000000}, + {0, 1000000, -1001, -1, 0}, + {0, 1000000, -1002, -2, 999000000}, + + {0, 999999999, 0, 0, 999999999}, + {0, 999999999, 1, 1, 999999}, + {0, 999999999, 999, 1, 998999999}, + {0, 999999999, 1000, 1, 999999999}, + {0, 999999999, 1001, 2, 999999}, + {0, 999999999, -1, 0, 998999999}, + {0, 999999999, -1000, -1, 999999999}, + {0, 999999999, -1001, -1, 998999999}, + + {0, 0, Long.MAX_VALUE, Long.MAX_VALUE / 1000, (int) (Long.MAX_VALUE % 1000) * 1000000}, + {0, 0, Long.MIN_VALUE, Long.MIN_VALUE / 1000 - 1, (int) (Long.MIN_VALUE % 1000) * 1000000 + 1000000000}, + }; + } + + @Test(dataProvider="PlusMillis") + public void plusMillis_long(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Instant t = Instant.ofEpochSecond(seconds, nanos); + t = t.plusMillis(amount); + assertEquals(t.getEpochSecond(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + @Test(dataProvider="PlusMillis") + public void plusMillis_long_oneMore(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Instant t = Instant.ofEpochSecond(seconds + 1, nanos); + t = t.plusMillis(amount); + assertEquals(t.getEpochSecond(), expectedSeconds + 1); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + @Test(dataProvider="PlusMillis") + public void plusMillis_long_minusOneLess(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Instant t = Instant.ofEpochSecond(seconds - 1, nanos); + t = t.plusMillis(amount); + assertEquals(t.getEpochSecond(), expectedSeconds - 1); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @Test + public void plusMillis_long_max() { + Instant t = Instant.ofEpochSecond(MAX_SECOND, 998999999); + t = t.plusMillis(1); + assertEquals(t.getEpochSecond(), MAX_SECOND); + assertEquals(t.getNano(), 999999999); + } + + @Test(expectedExceptions=DateTimeException.class) + public void plusMillis_long_overflowTooBig() { + Instant t = Instant.ofEpochSecond(MAX_SECOND, 999000000); + t.plusMillis(1); + } + + @Test + public void plusMillis_long_min() { + Instant t = Instant.ofEpochSecond(MIN_SECOND, 1000000); + t = t.plusMillis(-1); + assertEquals(t.getEpochSecond(), MIN_SECOND); + assertEquals(t.getNano(), 0); + } + + @Test(expectedExceptions=DateTimeException.class) + public void plusMillis_long_overflowTooSmall() { + Instant t = Instant.ofEpochSecond(MIN_SECOND, 0); + t.plusMillis(-1); + } + + //----------------------------------------------------------------------- + @DataProvider(name="PlusNanos") + Object[][] provider_plusNanos_long() { + return new Object[][] { + {0, 0, 0, 0, 0}, + {0, 0, 1, 0, 1}, + {0, 0, 999999999, 0, 999999999}, + {0, 0, 1000000000, 1, 0}, + {0, 0, 1000000001, 1, 1}, + {0, 0, 1999999999, 1, 999999999}, + {0, 0, 2000000000, 2, 0}, + {0, 0, -1, -1, 999999999}, + {0, 0, -999999999, -1, 1}, + {0, 0, -1000000000, -1, 0}, + {0, 0, -1000000001, -2, 999999999}, + {0, 0, -1999999999, -2, 1}, + + {1, 0, 0, 1, 0}, + {1, 0, 1, 1, 1}, + {1, 0, 999999999, 1, 999999999}, + {1, 0, 1000000000, 2, 0}, + {1, 0, 1000000001, 2, 1}, + {1, 0, 1999999999, 2, 999999999}, + {1, 0, 2000000000, 3, 0}, + {1, 0, -1, 0, 999999999}, + {1, 0, -999999999, 0, 1}, + {1, 0, -1000000000, 0, 0}, + {1, 0, -1000000001, -1, 999999999}, + {1, 0, -1999999999, -1, 1}, + + {-1, 0, 0, -1, 0}, + {-1, 0, 1, -1, 1}, + {-1, 0, 999999999, -1, 999999999}, + {-1, 0, 1000000000, 0, 0}, + {-1, 0, 1000000001, 0, 1}, + {-1, 0, 1999999999, 0, 999999999}, + {-1, 0, 2000000000, 1, 0}, + {-1, 0, -1, -2, 999999999}, + {-1, 0, -999999999, -2, 1}, + {-1, 0, -1000000000, -2, 0}, + {-1, 0, -1000000001, -3, 999999999}, + {-1, 0, -1999999999, -3, 1}, + + {1, 1, 0, 1, 1}, + {1, 1, 1, 1, 2}, + {1, 1, 999999998, 1, 999999999}, + {1, 1, 999999999, 2, 0}, + {1, 1, 1000000000, 2, 1}, + {1, 1, 1999999998, 2, 999999999}, + {1, 1, 1999999999, 3, 0}, + {1, 1, 2000000000, 3, 1}, + {1, 1, -1, 1, 0}, + {1, 1, -2, 0, 999999999}, + {1, 1, -1000000000, 0, 1}, + {1, 1, -1000000001, 0, 0}, + {1, 1, -1000000002, -1, 999999999}, + {1, 1, -2000000000, -1, 1}, + + {1, 999999999, 0, 1, 999999999}, + {1, 999999999, 1, 2, 0}, + {1, 999999999, 999999999, 2, 999999998}, + {1, 999999999, 1000000000, 2, 999999999}, + {1, 999999999, 1000000001, 3, 0}, + {1, 999999999, -1, 1, 999999998}, + {1, 999999999, -1000000000, 0, 999999999}, + {1, 999999999, -1000000001, 0, 999999998}, + {1, 999999999, -1999999999, 0, 0}, + {1, 999999999, -2000000000, -1, 999999999}, + + {MAX_SECOND, 0, 999999999, MAX_SECOND, 999999999}, + {MAX_SECOND - 1, 0, 1999999999, MAX_SECOND, 999999999}, + {MIN_SECOND, 1, -1, MIN_SECOND, 0}, + {MIN_SECOND + 1, 1, -1000000001, MIN_SECOND, 0}, + + {0, 0, MAX_SECOND, MAX_SECOND / 1000000000, (int) (MAX_SECOND % 1000000000)}, + {0, 0, MIN_SECOND, MIN_SECOND / 1000000000 - 1, (int) (MIN_SECOND % 1000000000) + 1000000000}, + }; + } + + @Test(dataProvider="PlusNanos") + public void plusNanos_long(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Instant t = Instant.ofEpochSecond(seconds, nanos); + t = t.plusNanos(amount); + assertEquals(t.getEpochSecond(), expectedSeconds); + assertEquals(t.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions=DateTimeException.class) + public void plusNanos_long_overflowTooBig() { + Instant t = Instant.ofEpochSecond(MAX_SECOND, 999999999); + t.plusNanos(1); + } + + @Test(expectedExceptions=DateTimeException.class) + public void plusNanos_long_overflowTooSmall() { + Instant t = Instant.ofEpochSecond(MIN_SECOND, 0); + t.plusNanos(-1); + } + + //----------------------------------------------------------------------- + @DataProvider(name="Minus") + Object[][] provider_minus() { + return new Object[][] { + {MIN_SECOND, 0, MIN_SECOND, 0, 0, 0}, + + {MIN_SECOND, 0, -1, 0, MIN_SECOND + 1, 0}, + {MIN_SECOND, 0, 0, -500, MIN_SECOND, 500}, + {MIN_SECOND, 0, 0, -1000000000, MIN_SECOND + 1, 0}, + + {MIN_SECOND + 1, 0, 1, 0, MIN_SECOND, 0}, + {MIN_SECOND + 1, 0, 0, 500, MIN_SECOND, 999999500}, + {MIN_SECOND + 1, 0, 0, 1000000000, MIN_SECOND, 0}, + + {-4, 666666667, -4, 666666667, 0, 0}, + {-4, 666666667, -3, 0, -1, 666666667}, + {-4, 666666667, -2, 0, -2, 666666667}, + {-4, 666666667, -1, 0, -3, 666666667}, + {-4, 666666667, -1, 333333334, -3, 333333333}, + {-4, 666666667, -1, 666666667, -3, 0}, + {-4, 666666667, -1, 999999999, -4, 666666668}, + {-4, 666666667, 0, 0, -4, 666666667}, + {-4, 666666667, 0, 1, -4, 666666666}, + {-4, 666666667, 0, 333333333, -4, 333333334}, + {-4, 666666667, 0, 666666666, -4, 1}, + {-4, 666666667, 1, 0, -5, 666666667}, + {-4, 666666667, 2, 0, -6, 666666667}, + {-4, 666666667, 3, 0, -7, 666666667}, + {-4, 666666667, 3, 333333333, -7, 333333334}, + + {-3, 0, -4, 666666667, 0, 333333333}, + {-3, 0, -3, 0, 0, 0}, + {-3, 0, -2, 0, -1, 0}, + {-3, 0, -1, 0, -2, 0}, + {-3, 0, -1, 333333334, -3, 666666666}, + {-3, 0, -1, 666666667, -3, 333333333}, + {-3, 0, -1, 999999999, -3, 1}, + {-3, 0, 0, 0, -3, 0}, + {-3, 0, 0, 1, -4, 999999999}, + {-3, 0, 0, 333333333, -4, 666666667}, + {-3, 0, 0, 666666666, -4, 333333334}, + {-3, 0, 1, 0, -4, 0}, + {-3, 0, 2, 0, -5, 0}, + {-3, 0, 3, 0, -6, 0}, + {-3, 0, 3, 333333333, -7, 666666667}, + + {-2, 0, -4, 666666667, 1, 333333333}, + {-2, 0, -3, 0, 1, 0}, + {-2, 0, -2, 0, 0, 0}, + {-2, 0, -1, 0, -1, 0}, + {-2, 0, -1, 333333334, -2, 666666666}, + {-2, 0, -1, 666666667, -2, 333333333}, + {-2, 0, -1, 999999999, -2, 1}, + {-2, 0, 0, 0, -2, 0}, + {-2, 0, 0, 1, -3, 999999999}, + {-2, 0, 0, 333333333, -3, 666666667}, + {-2, 0, 0, 666666666, -3, 333333334}, + {-2, 0, 1, 0, -3, 0}, + {-2, 0, 2, 0, -4, 0}, + {-2, 0, 3, 0, -5, 0}, + {-2, 0, 3, 333333333, -6, 666666667}, + + {-1, 0, -4, 666666667, 2, 333333333}, + {-1, 0, -3, 0, 2, 0}, + {-1, 0, -2, 0, 1, 0}, + {-1, 0, -1, 0, 0, 0}, + {-1, 0, -1, 333333334, -1, 666666666}, + {-1, 0, -1, 666666667, -1, 333333333}, + {-1, 0, -1, 999999999, -1, 1}, + {-1, 0, 0, 0, -1, 0}, + {-1, 0, 0, 1, -2, 999999999}, + {-1, 0, 0, 333333333, -2, 666666667}, + {-1, 0, 0, 666666666, -2, 333333334}, + {-1, 0, 1, 0, -2, 0}, + {-1, 0, 2, 0, -3, 0}, + {-1, 0, 3, 0, -4, 0}, + {-1, 0, 3, 333333333, -5, 666666667}, + + {-1, 666666667, -4, 666666667, 3, 0}, + {-1, 666666667, -3, 0, 2, 666666667}, + {-1, 666666667, -2, 0, 1, 666666667}, + {-1, 666666667, -1, 0, 0, 666666667}, + {-1, 666666667, -1, 333333334, 0, 333333333}, + {-1, 666666667, -1, 666666667, 0, 0}, + {-1, 666666667, -1, 999999999, -1, 666666668}, + {-1, 666666667, 0, 0, -1, 666666667}, + {-1, 666666667, 0, 1, -1, 666666666}, + {-1, 666666667, 0, 333333333, -1, 333333334}, + {-1, 666666667, 0, 666666666, -1, 1}, + {-1, 666666667, 1, 0, -2, 666666667}, + {-1, 666666667, 2, 0, -3, 666666667}, + {-1, 666666667, 3, 0, -4, 666666667}, + {-1, 666666667, 3, 333333333, -4, 333333334}, + + {0, 0, -4, 666666667, 3, 333333333}, + {0, 0, -3, 0, 3, 0}, + {0, 0, -2, 0, 2, 0}, + {0, 0, -1, 0, 1, 0}, + {0, 0, -1, 333333334, 0, 666666666}, + {0, 0, -1, 666666667, 0, 333333333}, + {0, 0, -1, 999999999, 0, 1}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, -1, 999999999}, + {0, 0, 0, 333333333, -1, 666666667}, + {0, 0, 0, 666666666, -1, 333333334}, + {0, 0, 1, 0, -1, 0}, + {0, 0, 2, 0, -2, 0}, + {0, 0, 3, 0, -3, 0}, + {0, 0, 3, 333333333, -4, 666666667}, + + {0, 333333333, -4, 666666667, 3, 666666666}, + {0, 333333333, -3, 0, 3, 333333333}, + {0, 333333333, -2, 0, 2, 333333333}, + {0, 333333333, -1, 0, 1, 333333333}, + {0, 333333333, -1, 333333334, 0, 999999999}, + {0, 333333333, -1, 666666667, 0, 666666666}, + {0, 333333333, -1, 999999999, 0, 333333334}, + {0, 333333333, 0, 0, 0, 333333333}, + {0, 333333333, 0, 1, 0, 333333332}, + {0, 333333333, 0, 333333333, 0, 0}, + {0, 333333333, 0, 666666666, -1, 666666667}, + {0, 333333333, 1, 0, -1, 333333333}, + {0, 333333333, 2, 0, -2, 333333333}, + {0, 333333333, 3, 0, -3, 333333333}, + {0, 333333333, 3, 333333333, -3, 0}, + + {1, 0, -4, 666666667, 4, 333333333}, + {1, 0, -3, 0, 4, 0}, + {1, 0, -2, 0, 3, 0}, + {1, 0, -1, 0, 2, 0}, + {1, 0, -1, 333333334, 1, 666666666}, + {1, 0, -1, 666666667, 1, 333333333}, + {1, 0, -1, 999999999, 1, 1}, + {1, 0, 0, 0, 1, 0}, + {1, 0, 0, 1, 0, 999999999}, + {1, 0, 0, 333333333, 0, 666666667}, + {1, 0, 0, 666666666, 0, 333333334}, + {1, 0, 1, 0, 0, 0}, + {1, 0, 2, 0, -1, 0}, + {1, 0, 3, 0, -2, 0}, + {1, 0, 3, 333333333, -3, 666666667}, + + {2, 0, -4, 666666667, 5, 333333333}, + {2, 0, -3, 0, 5, 0}, + {2, 0, -2, 0, 4, 0}, + {2, 0, -1, 0, 3, 0}, + {2, 0, -1, 333333334, 2, 666666666}, + {2, 0, -1, 666666667, 2, 333333333}, + {2, 0, -1, 999999999, 2, 1}, + {2, 0, 0, 0, 2, 0}, + {2, 0, 0, 1, 1, 999999999}, + {2, 0, 0, 333333333, 1, 666666667}, + {2, 0, 0, 666666666, 1, 333333334}, + {2, 0, 1, 0, 1, 0}, + {2, 0, 2, 0, 0, 0}, + {2, 0, 3, 0, -1, 0}, + {2, 0, 3, 333333333, -2, 666666667}, + + {3, 0, -4, 666666667, 6, 333333333}, + {3, 0, -3, 0, 6, 0}, + {3, 0, -2, 0, 5, 0}, + {3, 0, -1, 0, 4, 0}, + {3, 0, -1, 333333334, 3, 666666666}, + {3, 0, -1, 666666667, 3, 333333333}, + {3, 0, -1, 999999999, 3, 1}, + {3, 0, 0, 0, 3, 0}, + {3, 0, 0, 1, 2, 999999999}, + {3, 0, 0, 333333333, 2, 666666667}, + {3, 0, 0, 666666666, 2, 333333334}, + {3, 0, 1, 0, 2, 0}, + {3, 0, 2, 0, 1, 0}, + {3, 0, 3, 0, 0, 0}, + {3, 0, 3, 333333333, -1, 666666667}, + + {3, 333333333, -4, 666666667, 6, 666666666}, + {3, 333333333, -3, 0, 6, 333333333}, + {3, 333333333, -2, 0, 5, 333333333}, + {3, 333333333, -1, 0, 4, 333333333}, + {3, 333333333, -1, 333333334, 3, 999999999}, + {3, 333333333, -1, 666666667, 3, 666666666}, + {3, 333333333, -1, 999999999, 3, 333333334}, + {3, 333333333, 0, 0, 3, 333333333}, + {3, 333333333, 0, 1, 3, 333333332}, + {3, 333333333, 0, 333333333, 3, 0}, + {3, 333333333, 0, 666666666, 2, 666666667}, + {3, 333333333, 1, 0, 2, 333333333}, + {3, 333333333, 2, 0, 1, 333333333}, + {3, 333333333, 3, 0, 0, 333333333}, + {3, 333333333, 3, 333333333, 0, 0}, + + {MAX_SECOND - 1, 0, -1, 0, MAX_SECOND, 0}, + {MAX_SECOND - 1, 0, 0, -500, MAX_SECOND - 1, 500}, + {MAX_SECOND - 1, 0, 0, -1000000000, MAX_SECOND, 0}, + + {MAX_SECOND, 0, 1, 0, MAX_SECOND - 1, 0}, + {MAX_SECOND, 0, 0, 500, MAX_SECOND - 1, 999999500}, + {MAX_SECOND, 0, 0, 1000000000, MAX_SECOND - 1, 0}, + + {MAX_SECOND, 0, MAX_SECOND, 0, 0, 0}, + }; + } + + @Test(dataProvider="Minus") + public void minus_Duration(long seconds, int nanos, long otherSeconds, int otherNanos, long expectedSeconds, int expectedNanoOfSecond) { + Instant i = Instant.ofEpochSecond(seconds, nanos).minus(Duration.ofSeconds(otherSeconds, otherNanos)); + assertEquals(i.getEpochSecond(), expectedSeconds); + assertEquals(i.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions=DateTimeException.class) + public void minus_Duration_overflowTooSmall() { + Instant i = Instant.ofEpochSecond(MIN_SECOND); + i.minus(Duration.ofSeconds(0, 1)); + } + + @Test(expectedExceptions=DateTimeException.class) + public void minus_Duration_overflowTooBig() { + Instant i = Instant.ofEpochSecond(MAX_SECOND, 999999999); + i.minus(Duration.ofSeconds(-1, 999999999)); + } + + //----------------------------------------------------------------------- + @Test(dataProvider="Minus") + public void minus_longTemporalUnit(long seconds, int nanos, long otherSeconds, int otherNanos, long expectedSeconds, int expectedNanoOfSecond) { + Instant i = Instant.ofEpochSecond(seconds, nanos).minus(otherSeconds, SECONDS).minus(otherNanos, NANOS); + assertEquals(i.getEpochSecond(), expectedSeconds); + assertEquals(i.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions=DateTimeException.class) + public void minus_longTemporalUnit_overflowTooSmall() { + Instant i = Instant.ofEpochSecond(MIN_SECOND); + i.minus(1, NANOS); + } + + @Test(expectedExceptions=DateTimeException.class) + public void minus_longTemporalUnit_overflowTooBig() { + Instant i = Instant.ofEpochSecond(MAX_SECOND, 999999999); + i.minus(999999999, NANOS); + i.minus(-1, SECONDS); + } + + //----------------------------------------------------------------------- + @DataProvider(name="MinusSeconds") + Object[][] provider_minusSeconds_long() { + return new Object[][] { + {0, 0, 0, 0, 0}, + {0, 0, 1, -1, 0}, + {0, 0, -1, 1, 0}, + {0, 0, -MIN_SECOND, MIN_SECOND, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 1, 0, 0}, + {1, 0, -1, 2, 0}, + {1, 0, -MIN_SECOND + 1, MIN_SECOND, 0}, + {1, 1, 0, 1, 1}, + {1, 1, 1, 0, 1}, + {1, 1, -1, 2, 1}, + {1, 1, -MIN_SECOND, MIN_SECOND + 1, 1}, + {1, 1, -MIN_SECOND + 1, MIN_SECOND, 1}, + {-1, 1, 0, -1, 1}, + {-1, 1, 1, -2, 1}, + {-1, 1, -1, 0, 1}, + {-1, 1, -MAX_SECOND, MAX_SECOND - 1, 1}, + {-1, 1, -(MAX_SECOND + 1), MAX_SECOND, 1}, + + {MIN_SECOND, 2, MIN_SECOND, 0, 2}, + {MIN_SECOND + 1, 2, MIN_SECOND, 1, 2}, + {MAX_SECOND - 1, 2, MAX_SECOND, -1, 2}, + {MAX_SECOND, 2, MAX_SECOND, 0, 2}, + }; + } + + @Test(dataProvider="MinusSeconds") + public void minusSeconds_long(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Instant i = Instant.ofEpochSecond(seconds, nanos); + i = i.minusSeconds(amount); + assertEquals(i.getEpochSecond(), expectedSeconds); + assertEquals(i.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void minusSeconds_long_overflowTooBig() { + Instant i = Instant.ofEpochSecond(1, 0); + i.minusSeconds(Long.MIN_VALUE + 1); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void minusSeconds_long_overflowTooSmall() { + Instant i = Instant.ofEpochSecond(-2, 0); + i.minusSeconds(Long.MAX_VALUE); + } + + //----------------------------------------------------------------------- + @DataProvider(name="MinusMillis") + Object[][] provider_minusMillis_long() { + return new Object[][] { + {0, 0, 0, 0, 0}, + {0, 0, 1, -1, 999000000}, + {0, 0, 999, -1, 1000000}, + {0, 0, 1000, -1, 0}, + {0, 0, 1001, -2, 999000000}, + {0, 0, 1999, -2, 1000000}, + {0, 0, 2000, -2, 0}, + {0, 0, -1, 0, 1000000}, + {0, 0, -999, 0, 999000000}, + {0, 0, -1000, 1, 0}, + {0, 0, -1001, 1, 1000000}, + {0, 0, -1999, 1, 999000000}, + + {0, 1, 0, 0, 1}, + {0, 1, 1, -1, 999000001}, + {0, 1, 998, -1, 2000001}, + {0, 1, 999, -1, 1000001}, + {0, 1, 1000, -1, 1}, + {0, 1, 1998, -2, 2000001}, + {0, 1, 1999, -2, 1000001}, + {0, 1, 2000, -2, 1}, + {0, 1, -1, 0, 1000001}, + {0, 1, -2, 0, 2000001}, + {0, 1, -1000, 1, 1}, + {0, 1, -1001, 1, 1000001}, + + {0, 1000000, 0, 0, 1000000}, + {0, 1000000, 1, 0, 0}, + {0, 1000000, 998, -1, 3000000}, + {0, 1000000, 999, -1, 2000000}, + {0, 1000000, 1000, -1, 1000000}, + {0, 1000000, 1998, -2, 3000000}, + {0, 1000000, 1999, -2, 2000000}, + {0, 1000000, 2000, -2, 1000000}, + {0, 1000000, -1, 0, 2000000}, + {0, 1000000, -2, 0, 3000000}, + {0, 1000000, -999, 1, 0}, + {0, 1000000, -1000, 1, 1000000}, + {0, 1000000, -1001, 1, 2000000}, + {0, 1000000, -1002, 1, 3000000}, + + {0, 999999999, 0, 0, 999999999}, + {0, 999999999, 1, 0, 998999999}, + {0, 999999999, 999, 0, 999999}, + {0, 999999999, 1000, -1, 999999999}, + {0, 999999999, 1001, -1, 998999999}, + {0, 999999999, -1, 1, 999999}, + {0, 999999999, -1000, 1, 999999999}, + {0, 999999999, -1001, 2, 999999}, + + {0, 0, Long.MAX_VALUE, -(Long.MAX_VALUE / 1000) - 1, (int) -(Long.MAX_VALUE % 1000) * 1000000 + 1000000000}, + {0, 0, Long.MIN_VALUE, -(Long.MIN_VALUE / 1000), (int) -(Long.MIN_VALUE % 1000) * 1000000}, + }; + } + + @Test(dataProvider="MinusMillis") + public void minusMillis_long(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Instant i = Instant.ofEpochSecond(seconds, nanos); + i = i.minusMillis(amount); + assertEquals(i.getEpochSecond(), expectedSeconds); + assertEquals(i.getNano(), expectedNanoOfSecond); + } + + @Test(dataProvider="MinusMillis") + public void minusMillis_long_oneMore(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Instant i = Instant.ofEpochSecond(seconds + 1, nanos); + i = i.minusMillis(amount); + assertEquals(i.getEpochSecond(), expectedSeconds + 1); + assertEquals(i.getNano(), expectedNanoOfSecond); + } + + @Test(dataProvider="MinusMillis") + public void minusMillis_long_minusOneLess(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Instant i = Instant.ofEpochSecond(seconds - 1, nanos); + i = i.minusMillis(amount); + assertEquals(i.getEpochSecond(), expectedSeconds - 1); + assertEquals(i.getNano(), expectedNanoOfSecond); + } + + @Test + public void minusMillis_long_max() { + Instant i = Instant.ofEpochSecond(MAX_SECOND, 998999999); + i = i.minusMillis(-1); + assertEquals(i.getEpochSecond(), MAX_SECOND); + assertEquals(i.getNano(), 999999999); + } + + @Test(expectedExceptions=DateTimeException.class) + public void minusMillis_long_overflowTooBig() { + Instant i = Instant.ofEpochSecond(MAX_SECOND, 999000000); + i.minusMillis(-1); + } + + @Test + public void minusMillis_long_min() { + Instant i = Instant.ofEpochSecond(MIN_SECOND, 1000000); + i = i.minusMillis(1); + assertEquals(i.getEpochSecond(), MIN_SECOND); + assertEquals(i.getNano(), 0); + } + + @Test(expectedExceptions=DateTimeException.class) + public void minusMillis_long_overflowTooSmall() { + Instant i = Instant.ofEpochSecond(MIN_SECOND, 0); + i.minusMillis(1); + } + + //----------------------------------------------------------------------- + @DataProvider(name="MinusNanos") + Object[][] provider_minusNanos_long() { + return new Object[][] { + {0, 0, 0, 0, 0}, + {0, 0, 1, -1, 999999999}, + {0, 0, 999999999, -1, 1}, + {0, 0, 1000000000, -1, 0}, + {0, 0, 1000000001, -2, 999999999}, + {0, 0, 1999999999, -2, 1}, + {0, 0, 2000000000, -2, 0}, + {0, 0, -1, 0, 1}, + {0, 0, -999999999, 0, 999999999}, + {0, 0, -1000000000, 1, 0}, + {0, 0, -1000000001, 1, 1}, + {0, 0, -1999999999, 1, 999999999}, + + {1, 0, 0, 1, 0}, + {1, 0, 1, 0, 999999999}, + {1, 0, 999999999, 0, 1}, + {1, 0, 1000000000, 0, 0}, + {1, 0, 1000000001, -1, 999999999}, + {1, 0, 1999999999, -1, 1}, + {1, 0, 2000000000, -1, 0}, + {1, 0, -1, 1, 1}, + {1, 0, -999999999, 1, 999999999}, + {1, 0, -1000000000, 2, 0}, + {1, 0, -1000000001, 2, 1}, + {1, 0, -1999999999, 2, 999999999}, + + {-1, 0, 0, -1, 0}, + {-1, 0, 1, -2, 999999999}, + {-1, 0, 999999999, -2, 1}, + {-1, 0, 1000000000, -2, 0}, + {-1, 0, 1000000001, -3, 999999999}, + {-1, 0, 1999999999, -3, 1}, + {-1, 0, 2000000000, -3, 0}, + {-1, 0, -1, -1, 1}, + {-1, 0, -999999999, -1, 999999999}, + {-1, 0, -1000000000, 0, 0}, + {-1, 0, -1000000001, 0, 1}, + {-1, 0, -1999999999, 0, 999999999}, + + {1, 1, 0, 1, 1}, + {1, 1, 1, 1, 0}, + {1, 1, 999999998, 0, 3}, + {1, 1, 999999999, 0, 2}, + {1, 1, 1000000000, 0, 1}, + {1, 1, 1999999998, -1, 3}, + {1, 1, 1999999999, -1, 2}, + {1, 1, 2000000000, -1, 1}, + {1, 1, -1, 1, 2}, + {1, 1, -2, 1, 3}, + {1, 1, -1000000000, 2, 1}, + {1, 1, -1000000001, 2, 2}, + {1, 1, -1000000002, 2, 3}, + {1, 1, -2000000000, 3, 1}, + + {1, 999999999, 0, 1, 999999999}, + {1, 999999999, 1, 1, 999999998}, + {1, 999999999, 999999999, 1, 0}, + {1, 999999999, 1000000000, 0, 999999999}, + {1, 999999999, 1000000001, 0, 999999998}, + {1, 999999999, -1, 2, 0}, + {1, 999999999, -1000000000, 2, 999999999}, + {1, 999999999, -1000000001, 3, 0}, + {1, 999999999, -1999999999, 3, 999999998}, + {1, 999999999, -2000000000, 3, 999999999}, + + {MAX_SECOND, 0, -999999999, MAX_SECOND, 999999999}, + {MAX_SECOND - 1, 0, -1999999999, MAX_SECOND, 999999999}, + {MIN_SECOND, 1, 1, MIN_SECOND, 0}, + {MIN_SECOND + 1, 1, 1000000001, MIN_SECOND, 0}, + + {0, 0, Long.MAX_VALUE, -(Long.MAX_VALUE / 1000000000) - 1, (int) -(Long.MAX_VALUE % 1000000000) + 1000000000}, + {0, 0, Long.MIN_VALUE, -(Long.MIN_VALUE / 1000000000), (int) -(Long.MIN_VALUE % 1000000000)}, + }; + } + + @Test(dataProvider="MinusNanos") + public void minusNanos_long(long seconds, int nanos, long amount, long expectedSeconds, int expectedNanoOfSecond) { + Instant i = Instant.ofEpochSecond(seconds, nanos); + i = i.minusNanos(amount); + assertEquals(i.getEpochSecond(), expectedSeconds); + assertEquals(i.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions=DateTimeException.class) + public void minusNanos_long_overflowTooBig() { + Instant i = Instant.ofEpochSecond(MAX_SECOND, 999999999); + i.minusNanos(-1); + } + + @Test(expectedExceptions=DateTimeException.class) + public void minusNanos_long_overflowTooSmall() { + Instant i = Instant.ofEpochSecond(MIN_SECOND, 0); + i.minusNanos(1); + } + + //----------------------------------------------------------------------- + // toEpochMilli() + //----------------------------------------------------------------------- + @Test + public void test_toEpochMilli() { + assertEquals(Instant.ofEpochSecond(1L, 1000000).toEpochMilli(), 1001L); + assertEquals(Instant.ofEpochSecond(1L, 2000000).toEpochMilli(), 1002L); + assertEquals(Instant.ofEpochSecond(1L, 567).toEpochMilli(), 1000L); + assertEquals(Instant.ofEpochSecond(Long.MAX_VALUE / 1000).toEpochMilli(), (Long.MAX_VALUE / 1000) * 1000); + assertEquals(Instant.ofEpochSecond(Long.MIN_VALUE / 1000).toEpochMilli(), (Long.MIN_VALUE / 1000) * 1000); + assertEquals(Instant.ofEpochSecond(0L, -1000000).toEpochMilli(), -1L); + assertEquals(Instant.ofEpochSecond(0L, 1000000).toEpochMilli(), 1); + assertEquals(Instant.ofEpochSecond(0L, 999999).toEpochMilli(), 0); + assertEquals(Instant.ofEpochSecond(0L, 1).toEpochMilli(), 0); + assertEquals(Instant.ofEpochSecond(0L, 0).toEpochMilli(), 0); + assertEquals(Instant.ofEpochSecond(0L, -1).toEpochMilli(), -1L); + assertEquals(Instant.ofEpochSecond(0L, -999999).toEpochMilli(), -1L); + assertEquals(Instant.ofEpochSecond(0L, -1000000).toEpochMilli(), -1L); + assertEquals(Instant.ofEpochSecond(0L, -1000001).toEpochMilli(), -2L); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_toEpochMilli_tooBig() { + Instant.ofEpochSecond(Long.MAX_VALUE / 1000 + 1).toEpochMilli(); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_toEpochMilli_tooSmall() { + Instant.ofEpochSecond(Long.MIN_VALUE / 1000 - 1).toEpochMilli(); + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test + public void test_comparisons() { + doTest_comparisons_Instant( + Instant.ofEpochSecond(-2L, 0), + Instant.ofEpochSecond(-2L, 999999998), + Instant.ofEpochSecond(-2L, 999999999), + Instant.ofEpochSecond(-1L, 0), + Instant.ofEpochSecond(-1L, 1), + Instant.ofEpochSecond(-1L, 999999998), + Instant.ofEpochSecond(-1L, 999999999), + Instant.ofEpochSecond(0L, 0), + Instant.ofEpochSecond(0L, 1), + Instant.ofEpochSecond(0L, 2), + Instant.ofEpochSecond(0L, 999999999), + Instant.ofEpochSecond(1L, 0), + Instant.ofEpochSecond(2L, 0) + ); + } + + void doTest_comparisons_Instant(Instant... instants) { + for (int i = 0; i < instants.length; i++) { + Instant a = instants[i]; + for (int j = 0; j < instants.length; j++) { + Instant b = instants[j]; + if (i < j) { + assertEquals(a.compareTo(b) < 0, true, a + " <=> " + b); + assertEquals(a.isBefore(b), true, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else if (i > j) { + assertEquals(a.compareTo(b) > 0, true, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), true, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else { + assertEquals(a.compareTo(b), 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), true, a + " <=> " + b); + } + } + } + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_compareTo_ObjectNull() { + Instant a = Instant.ofEpochSecond(0L, 0); + a.compareTo(null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_isBefore_ObjectNull() { + Instant a = Instant.ofEpochSecond(0L, 0); + a.isBefore(null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_isAfter_ObjectNull() { + Instant a = Instant.ofEpochSecond(0L, 0); + a.isAfter(null); + } + + @Test(expectedExceptions=ClassCastException.class) + @SuppressWarnings({"unchecked", "rawtypes"}) + public void compareToNonInstant() { + Comparable c = Instant.ofEpochSecond(0L); + c.compareTo(new Object()); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @Test + public void test_equals() { + Instant test5a = Instant.ofEpochSecond(5L, 20); + Instant test5b = Instant.ofEpochSecond(5L, 20); + Instant test5n = Instant.ofEpochSecond(5L, 30); + Instant test6 = Instant.ofEpochSecond(6L, 20); + + assertEquals(test5a.equals(test5a), true); + assertEquals(test5a.equals(test5b), true); + assertEquals(test5a.equals(test5n), false); + assertEquals(test5a.equals(test6), false); + + assertEquals(test5b.equals(test5a), true); + assertEquals(test5b.equals(test5b), true); + assertEquals(test5b.equals(test5n), false); + assertEquals(test5b.equals(test6), false); + + assertEquals(test5n.equals(test5a), false); + assertEquals(test5n.equals(test5b), false); + assertEquals(test5n.equals(test5n), true); + assertEquals(test5n.equals(test6), false); + + assertEquals(test6.equals(test5a), false); + assertEquals(test6.equals(test5b), false); + assertEquals(test6.equals(test5n), false); + assertEquals(test6.equals(test6), true); + } + + @Test + public void test_equals_null() { + Instant test5 = Instant.ofEpochSecond(5L, 20); + assertEquals(test5.equals(null), false); + } + + @Test + public void test_equals_otherClass() { + Instant test5 = Instant.ofEpochSecond(5L, 20); + assertEquals(test5.equals(""), false); + } + + //----------------------------------------------------------------------- + // hashCode() + //----------------------------------------------------------------------- + @Test + public void test_hashCode() { + Instant test5a = Instant.ofEpochSecond(5L, 20); + Instant test5b = Instant.ofEpochSecond(5L, 20); + Instant test5n = Instant.ofEpochSecond(5L, 30); + Instant test6 = Instant.ofEpochSecond(6L, 20); + + assertEquals(test5a.hashCode() == test5a.hashCode(), true); + assertEquals(test5a.hashCode() == test5b.hashCode(), true); + assertEquals(test5b.hashCode() == test5b.hashCode(), true); + + assertEquals(test5a.hashCode() == test5n.hashCode(), false); + assertEquals(test5a.hashCode() == test6.hashCode(), false); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="toStringParse") + Object[][] data_toString() { + return new Object[][] { + {Instant.ofEpochSecond(65L, 567), "1970-01-01T00:01:05.000000567Z"}, + {Instant.ofEpochSecond(1, 0), "1970-01-01T00:00:01Z"}, + {Instant.ofEpochSecond(60, 0), "1970-01-01T00:01Z"}, + {Instant.ofEpochSecond(3600, 0), "1970-01-01T01:00Z"}, + {Instant.ofEpochSecond(-1, 0), "1969-12-31T23:59:59Z"}, + + {LocalDateTime.of(0, 1, 2, 0, 0).toInstant(ZoneOffset.UTC), "0000-01-02T00:00Z"}, + {LocalDateTime.of(0, 1, 1, 12, 30).toInstant(ZoneOffset.UTC), "0000-01-01T12:30Z"}, + {LocalDateTime.of(0, 1, 1, 0, 0, 0, 1).toInstant(ZoneOffset.UTC), "0000-01-01T00:00:00.000000001Z"}, + {LocalDateTime.of(0, 1, 1, 0, 0).toInstant(ZoneOffset.UTC), "0000-01-01T00:00Z"}, + + {LocalDateTime.of(-1, 12, 31, 23, 59, 59, 999_999_999).toInstant(ZoneOffset.UTC), "-0001-12-31T23:59:59.999999999Z"}, + {LocalDateTime.of(-1, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "-0001-12-31T12:30Z"}, + {LocalDateTime.of(-1, 12, 30, 12, 30).toInstant(ZoneOffset.UTC), "-0001-12-30T12:30Z"}, + + {LocalDateTime.of(-9999, 1, 2, 12, 30).toInstant(ZoneOffset.UTC), "-9999-01-02T12:30Z"}, + {LocalDateTime.of(-9999, 1, 1, 12, 30).toInstant(ZoneOffset.UTC), "-9999-01-01T12:30Z"}, + {LocalDateTime.of(-9999, 1, 1, 0, 0).toInstant(ZoneOffset.UTC), "-9999-01-01T00:00Z"}, + + {LocalDateTime.of(-10000, 12, 31, 23, 59, 59, 999_999_999).toInstant(ZoneOffset.UTC), "-10000-12-31T23:59:59.999999999Z"}, + {LocalDateTime.of(-10000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "-10000-12-31T12:30Z"}, + {LocalDateTime.of(-10000, 12, 30, 12, 30).toInstant(ZoneOffset.UTC), "-10000-12-30T12:30Z"}, + {LocalDateTime.of(-15000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "-15000-12-31T12:30Z"}, + + {LocalDateTime.of(-19999, 1, 2, 12, 30).toInstant(ZoneOffset.UTC), "-19999-01-02T12:30Z"}, + {LocalDateTime.of(-19999, 1, 1, 12, 30).toInstant(ZoneOffset.UTC), "-19999-01-01T12:30Z"}, + {LocalDateTime.of(-19999, 1, 1, 0, 0).toInstant(ZoneOffset.UTC), "-19999-01-01T00:00Z"}, + + {LocalDateTime.of(-20000, 12, 31, 23, 59, 59, 999_999_999).toInstant(ZoneOffset.UTC), "-20000-12-31T23:59:59.999999999Z"}, + {LocalDateTime.of(-20000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "-20000-12-31T12:30Z"}, + {LocalDateTime.of(-20000, 12, 30, 12, 30).toInstant(ZoneOffset.UTC), "-20000-12-30T12:30Z"}, + {LocalDateTime.of(-25000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "-25000-12-31T12:30Z"}, + + {LocalDateTime.of(9999, 12, 30, 12, 30).toInstant(ZoneOffset.UTC), "9999-12-30T12:30Z"}, + {LocalDateTime.of(9999, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "9999-12-31T12:30Z"}, + {LocalDateTime.of(9999, 12, 31, 23, 59, 59, 999_999_999).toInstant(ZoneOffset.UTC), "9999-12-31T23:59:59.999999999Z"}, + + {LocalDateTime.of(10000, 1, 1, 0, 0).toInstant(ZoneOffset.UTC), "+10000-01-01T00:00Z"}, + {LocalDateTime.of(10000, 1, 1, 12, 30).toInstant(ZoneOffset.UTC), "+10000-01-01T12:30Z"}, + {LocalDateTime.of(10000, 1, 2, 12, 30).toInstant(ZoneOffset.UTC), "+10000-01-02T12:30Z"}, + {LocalDateTime.of(15000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "+15000-12-31T12:30Z"}, + + {LocalDateTime.of(19999, 12, 30, 12, 30).toInstant(ZoneOffset.UTC), "+19999-12-30T12:30Z"}, + {LocalDateTime.of(19999, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "+19999-12-31T12:30Z"}, + {LocalDateTime.of(19999, 12, 31, 23, 59, 59, 999_999_999).toInstant(ZoneOffset.UTC), "+19999-12-31T23:59:59.999999999Z"}, + + {LocalDateTime.of(20000, 1, 1, 0, 0).toInstant(ZoneOffset.UTC), "+20000-01-01T00:00Z"}, + {LocalDateTime.of(20000, 1, 1, 12, 30).toInstant(ZoneOffset.UTC), "+20000-01-01T12:30Z"}, + {LocalDateTime.of(20000, 1, 2, 12, 30).toInstant(ZoneOffset.UTC), "+20000-01-02T12:30Z"}, + {LocalDateTime.of(25000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "+25000-12-31T12:30Z"}, + + {LocalDateTime.of(-999_999_999, 1, 1, 12, 30).toInstant(ZoneOffset.UTC).minus(1, DAYS), "-1000000000-12-31T12:30Z"}, + {LocalDateTime.of(999_999_999, 12, 31, 12, 30).toInstant(ZoneOffset.UTC).plus(1, DAYS), "+1000000000-01-01T12:30Z"}, + + {Instant.MIN, "-1000000000-01-01T00:00Z"}, + {Instant.MAX, "+1000000000-12-31T23:59:59.999999999Z"}, + }; + } + + @Test(dataProvider="toStringParse") + public void test_toString(Instant instant, String expected) { + assertEquals(instant.toString(), expected); + } + + @Test(dataProvider="toStringParse") + public void test_parse(Instant instant, String text) { + assertEquals(Instant.parse(text), instant); + } + + @Test(dataProvider="toStringParse") + public void test_parseLowercase(Instant instant, String text) { + assertEquals(Instant.parse(text.toLowerCase(Locale.ENGLISH)), instant); + } + +} diff --git a/test/java/time/tck/java/time/TCKLocalDate.java b/test/java/time/tck/java/time/TCKLocalDate.java new file mode 100644 index 0000000000000000000000000000000000000000..721da56d36b4224c9e38cc142738617c6dd9118a --- /dev/null +++ b/test/java/time/tck/java/time/TCKLocalDate.java @@ -0,0 +1,2018 @@ +/* + * 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. + * + * 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 tck.java.time; + +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.EPOCH_MONTH; +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import java.time.Clock; +import java.time.DateTimeException; +import java.time.DayOfWeek; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.ISOChrono; +import java.time.temporal.JulianFields; +import java.time.temporal.OffsetDate; +import java.time.temporal.Queries; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalUnit; +import java.time.temporal.Year; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import test.java.time.MockSimplePeriod; +import test.java.time.temporal.MockFieldNoValue; + +/** + * Test LocalDate. + */ +@Test +public class TCKLocalDate extends AbstractDateTimeTest { + + private static final ZoneOffset OFFSET_PONE = ZoneOffset.ofHours(1); + private static final ZoneOffset OFFSET_PTWO = ZoneOffset.ofHours(2); + private static final ZoneId ZONE_PARIS = ZoneId.of("Europe/Paris"); + private static final ZoneId ZONE_GAZA = ZoneId.of("Asia/Gaza"); + + private LocalDate TEST_2007_07_15; + private long MAX_VALID_EPOCHDAYS; + private long MIN_VALID_EPOCHDAYS; + private LocalDate MAX_DATE; + private LocalDate MIN_DATE; + private Instant MAX_INSTANT; + private Instant MIN_INSTANT; + + @BeforeMethod(groups={"tck", "implementation"}) + public void setUp() { + TEST_2007_07_15 = LocalDate.of(2007, 7, 15); + + LocalDate max = LocalDate.MAX; + LocalDate min = LocalDate.MIN; + MAX_VALID_EPOCHDAYS = max.toEpochDay(); + MIN_VALID_EPOCHDAYS = min.toEpochDay(); + MAX_DATE = max; + MIN_DATE = min; + MAX_INSTANT = max.atStartOfDay(ZoneOffset.UTC).toInstant(); + MIN_INSTANT = min.atStartOfDay(ZoneOffset.UTC).toInstant(); + } + + //----------------------------------------------------------------------- + @Override + protected List samples() { + TemporalAccessor[] array = {TEST_2007_07_15, LocalDate.MAX, LocalDate.MIN, }; + return Arrays.asList(array); + } + + @Override + protected List validFields() { + TemporalField[] array = { + DAY_OF_WEEK, + ALIGNED_DAY_OF_WEEK_IN_MONTH, + ALIGNED_DAY_OF_WEEK_IN_YEAR, + DAY_OF_MONTH, + DAY_OF_YEAR, + EPOCH_DAY, + ALIGNED_WEEK_OF_MONTH, + ALIGNED_WEEK_OF_YEAR, + MONTH_OF_YEAR, + EPOCH_MONTH, + YEAR_OF_ERA, + YEAR, + ERA, + JulianFields.JULIAN_DAY, + JulianFields.MODIFIED_JULIAN_DAY, + JulianFields.RATA_DIE, + }; + return Arrays.asList(array); + } + + @Override + protected List invalidFields() { + List list = new ArrayList<>(Arrays.asList(ChronoField.values())); + list.removeAll(validFields()); + return list; + } + + + //----------------------------------------------------------------------- + @Test + public void test_serialization() throws Exception { + assertSerializable(TEST_2007_07_15); + assertSerializable(LocalDate.MIN); + assertSerializable(LocalDate.MAX); + } + + @Test + public void test_serialization_format() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(3); + dos.writeInt(2012); + dos.writeByte(9); + dos.writeByte(16); + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(LocalDate.of(2012, 9, 16), bytes); + } + + //----------------------------------------------------------------------- + private void check(LocalDate test, int y, int m, int d) { + assertEquals(test.getYear(), y); + assertEquals(test.getMonth().getValue(), m); + assertEquals(test.getDayOfMonth(), d); + assertEquals(test, test); + assertEquals(test.hashCode(), test.hashCode()); + assertEquals(LocalDate.of(y, m, d), test); + } + + //----------------------------------------------------------------------- + // constants + //----------------------------------------------------------------------- + @Test + public void constant_MIN() { + check(LocalDate.MIN, Year.MIN_VALUE, 1, 1); + } + + @Test + public void constant_MAX() { + check(LocalDate.MAX, Year.MAX_VALUE, 12, 31); + } + + //----------------------------------------------------------------------- + // now() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now() { + LocalDate expected = LocalDate.now(Clock.systemDefaultZone()); + LocalDate test = LocalDate.now(); + for (int i = 0; i < 100; i++) { + if (expected.equals(test)) { + return; + } + expected = LocalDate.now(Clock.systemDefaultZone()); + test = LocalDate.now(); + } + assertEquals(test, expected); + } + + //----------------------------------------------------------------------- + // now(ZoneId) + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_ZoneId_nullZoneId() { + LocalDate.now((ZoneId) null); + } + + @Test(groups={"tck"}) + public void now_ZoneId() { + ZoneId zone = ZoneId.of("UTC+01:02:03"); + LocalDate expected = LocalDate.now(Clock.system(zone)); + LocalDate test = LocalDate.now(zone); + for (int i = 0; i < 100; i++) { + if (expected.equals(test)) { + return; + } + expected = LocalDate.now(Clock.system(zone)); + test = LocalDate.now(zone); + } + assertEquals(test, expected); + } + + //----------------------------------------------------------------------- + // now(Clock) + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_Clock_nullClock() { + LocalDate.now((Clock) null); + } + + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay_utc() { + for (int i = 0; i < (2 * 24 * 60 * 60); i++) { + Instant instant = Instant.ofEpochSecond(i); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + LocalDate test = LocalDate.now(clock); + assertEquals(test.getYear(), 1970); + assertEquals(test.getMonth(), Month.JANUARY); + assertEquals(test.getDayOfMonth(), (i < 24 * 60 * 60 ? 1 : 2)); + } + } + + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay_offset() { + for (int i = 0; i < (2 * 24 * 60 * 60); i++) { + Instant instant = Instant.ofEpochSecond(i); + Clock clock = Clock.fixed(instant.minusSeconds(OFFSET_PONE.getTotalSeconds()), OFFSET_PONE); + LocalDate test = LocalDate.now(clock); + assertEquals(test.getYear(), 1970); + assertEquals(test.getMonth(), Month.JANUARY); + assertEquals(test.getDayOfMonth(), (i < 24 * 60 * 60) ? 1 : 2); + } + } + + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay_beforeEpoch() { + for (int i =-1; i >= -(2 * 24 * 60 * 60); i--) { + Instant instant = Instant.ofEpochSecond(i); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + LocalDate test = LocalDate.now(clock); + assertEquals(test.getYear(), 1969); + assertEquals(test.getMonth(), Month.DECEMBER); + assertEquals(test.getDayOfMonth(), (i >= -24 * 60 * 60 ? 31 : 30)); + } + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now_Clock_maxYear() { + Clock clock = Clock.fixed(MAX_INSTANT, ZoneOffset.UTC); + LocalDate test = LocalDate.now(clock); + assertEquals(test, MAX_DATE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void now_Clock_tooBig() { + Clock clock = Clock.fixed(MAX_INSTANT.plusSeconds(24 * 60 * 60), ZoneOffset.UTC); + LocalDate.now(clock); + } + + @Test(groups={"tck"}) + public void now_Clock_minYear() { + Clock clock = Clock.fixed(MIN_INSTANT, ZoneOffset.UTC); + LocalDate test = LocalDate.now(clock); + assertEquals(test, MIN_DATE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void now_Clock_tooLow() { + Clock clock = Clock.fixed(MIN_INSTANT.minusNanos(1), ZoneOffset.UTC); + LocalDate.now(clock); + } + + //----------------------------------------------------------------------- + // of() factories + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_intsMonth() { + assertEquals(TEST_2007_07_15, LocalDate.of(2007, Month.JULY, 15)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_intsMonth_29febNonLeap() { + LocalDate.of(2007, Month.FEBRUARY, 29); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_intsMonth_31apr() { + LocalDate.of(2007, Month.APRIL, 31); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_intsMonth_dayTooLow() { + LocalDate.of(2007, Month.JANUARY, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_intsMonth_dayTooHigh() { + LocalDate.of(2007, Month.JANUARY, 32); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_intsMonth_nullMonth() { + LocalDate.of(2007, null, 30); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_intsMonth_yearTooLow() { + LocalDate.of(Integer.MIN_VALUE, Month.JANUARY, 1); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_ints() { + check(TEST_2007_07_15, 2007, 7, 15); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_ints_29febNonLeap() { + LocalDate.of(2007, 2, 29); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_ints_31apr() { + LocalDate.of(2007, 4, 31); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_ints_dayTooLow() { + LocalDate.of(2007, 1, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_ints_dayTooHigh() { + LocalDate.of(2007, 1, 32); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_ints_monthTooLow() { + LocalDate.of(2007, 0, 1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_ints_monthTooHigh() { + LocalDate.of(2007, 13, 1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_ints_yearTooLow() { + LocalDate.of(Integer.MIN_VALUE, 1, 1); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_ofYearDay_ints_nonLeap() { + LocalDate date = LocalDate.of(2007, 1, 1); + for (int i = 1; i < 365; i++) { + assertEquals(LocalDate.ofYearDay(2007, i), date); + date = next(date); + } + } + + @Test(groups={"tck"}) + public void factory_ofYearDay_ints_leap() { + LocalDate date = LocalDate.of(2008, 1, 1); + for (int i = 1; i < 366; i++) { + assertEquals(LocalDate.ofYearDay(2008, i), date); + date = next(date); + } + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofYearDay_ints_366nonLeap() { + LocalDate.ofYearDay(2007, 366); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofYearDay_ints_dayTooLow() { + LocalDate.ofYearDay(2007, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofYearDay_ints_dayTooHigh() { + LocalDate.ofYearDay(2007, 367); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofYearDay_ints_yearTooLow() { + LocalDate.ofYearDay(Integer.MIN_VALUE, 1); + } + + //----------------------------------------------------------------------- + // Since plusDays/minusDays actually depends on MJDays, it cannot be used for testing + private LocalDate next(LocalDate date) { + int newDayOfMonth = date.getDayOfMonth() + 1; + if (newDayOfMonth <= date.getMonth().length(isIsoLeap(date.getYear()))) { + return date.withDayOfMonth(newDayOfMonth); + } + date = date.withDayOfMonth(1); + if (date.getMonth() == Month.DECEMBER) { + date = date.withYear(date.getYear() + 1); + } + return date.with(date.getMonth().plus(1)); + } + + private LocalDate previous(LocalDate date) { + int newDayOfMonth = date.getDayOfMonth() - 1; + if (newDayOfMonth > 0) { + return date.withDayOfMonth(newDayOfMonth); + } + date = date.with(date.getMonth().minus(1)); + if (date.getMonth() == Month.DECEMBER) { + date = date.withYear(date.getYear() - 1); + } + return date.withDayOfMonth(date.getMonth().length(isIsoLeap(date.getYear()))); + } + + //----------------------------------------------------------------------- + // ofEpochDay() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_ofEpochDay() { + long date_0000_01_01 = -678941 - 40587; + assertEquals(LocalDate.ofEpochDay(0), LocalDate.of(1970, 1, 1)); + assertEquals(LocalDate.ofEpochDay(date_0000_01_01), LocalDate.of(0, 1, 1)); + assertEquals(LocalDate.ofEpochDay(date_0000_01_01 - 1), LocalDate.of(-1, 12, 31)); + assertEquals(LocalDate.ofEpochDay(MAX_VALID_EPOCHDAYS), LocalDate.of(Year.MAX_VALUE, 12, 31)); + assertEquals(LocalDate.ofEpochDay(MIN_VALID_EPOCHDAYS), LocalDate.of(Year.MIN_VALUE, 1, 1)); + + LocalDate test = LocalDate.of(0, 1, 1); + for (long i = date_0000_01_01; i < 700000; i++) { + assertEquals(LocalDate.ofEpochDay(i), test); + test = next(test); + } + test = LocalDate.of(0, 1, 1); + for (long i = date_0000_01_01; i > -2000000; i--) { + assertEquals(LocalDate.ofEpochDay(i), test); + test = previous(test); + } + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofEpochDay_aboveMax() { + LocalDate.ofEpochDay(MAX_VALID_EPOCHDAYS + 1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofEpochDay_belowMin() { + LocalDate.ofEpochDay(MIN_VALID_EPOCHDAYS - 1); + } + + //----------------------------------------------------------------------- + // from() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_from_TemporalAccessor() { + assertEquals(LocalDate.from(LocalDate.of(2007, 7, 15)), LocalDate.of(2007, 7, 15)); + assertEquals(LocalDate.from(LocalDateTime.of(2007, 7, 15, 12, 30)), LocalDate.of(2007, 7, 15)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_from_TemporalAccessor_invalid_noDerive() { + LocalDate.from(LocalTime.of(12, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_from_TemporalAccessor_null() { + LocalDate.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // parse() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleToString", groups={"tck"}) + public void factory_parse_validText(int y, int m, int d, String parsable) { + LocalDate t = LocalDate.parse(parsable); + assertNotNull(t, parsable); + assertEquals(t.getYear(), y, parsable); + assertEquals(t.getMonth().getValue(), m, parsable); + assertEquals(t.getDayOfMonth(), d, parsable); + } + + @DataProvider(name="sampleBadParse") + Object[][] provider_sampleBadParse() { + return new Object[][]{ + {"2008/07/05"}, + {"10000-01-01"}, + {"2008-1-1"}, + {"2008--01"}, + {"ABCD-02-01"}, + {"2008-AB-01"}, + {"2008-02-AB"}, + {"-0000-02-01"}, + {"2008-02-01Z"}, + {"2008-02-01+01:00"}, + {"2008-02-01+01:00[Europe/Paris]"}, + }; + } + + @Test(dataProvider="sampleBadParse", expectedExceptions={DateTimeParseException.class}, groups={"tck"}) + public void factory_parse_invalidText(String unparsable) { + LocalDate.parse(unparsable); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_illegalValue() { + LocalDate.parse("2008-06-32"); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_invalidValue() { + LocalDate.parse("2008-06-31"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_nullText() { + LocalDate.parse((String) null); + } + + //----------------------------------------------------------------------- + // parse(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_parse_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d"); + LocalDate test = LocalDate.parse("2010 12 3", f); + assertEquals(test, LocalDate.of(2010, 12, 3)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullText() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d"); + LocalDate.parse((String) null, f); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullFormatter() { + LocalDate.parse("ANY", null); + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @Test + public void test_get_TemporalField() { + LocalDate test = LocalDate.of(2008, 6, 30); + assertEquals(test.get(ChronoField.YEAR), 2008); + assertEquals(test.get(ChronoField.MONTH_OF_YEAR), 6); + assertEquals(test.get(ChronoField.DAY_OF_MONTH), 30); + assertEquals(test.get(ChronoField.DAY_OF_WEEK), 1); + assertEquals(test.get(ChronoField.DAY_OF_YEAR), 182); + } + + @Test + public void test_getLong_TemporalField() { + LocalDate test = LocalDate.of(2008, 6, 30); + assertEquals(test.getLong(ChronoField.YEAR), 2008); + assertEquals(test.getLong(ChronoField.MONTH_OF_YEAR), 6); + assertEquals(test.getLong(ChronoField.DAY_OF_MONTH), 30); + assertEquals(test.getLong(ChronoField.DAY_OF_WEEK), 1); + assertEquals(test.getLong(ChronoField.DAY_OF_YEAR), 182); + } + + //----------------------------------------------------------------------- + // query(TemporalQuery) + //----------------------------------------------------------------------- + @Test + public void test_query_chrono() { + assertEquals(TEST_2007_07_15.query(Queries.chrono()), ISOChrono.INSTANCE); + assertEquals(Queries.chrono().queryFrom(TEST_2007_07_15), ISOChrono.INSTANCE); + } + + @Test + public void test_query_zoneId() { + assertEquals(TEST_2007_07_15.query(Queries.zoneId()), null); + assertEquals(Queries.zoneId().queryFrom(TEST_2007_07_15), null); + } + + @Test + public void test_query_precision() { + assertEquals(TEST_2007_07_15.query(Queries.precision()), ChronoUnit.DAYS); + assertEquals(Queries.precision().queryFrom(TEST_2007_07_15), ChronoUnit.DAYS); + } + + @Test + public void test_query_offset() { + assertEquals(TEST_2007_07_15.query(Queries.offset()), null); + assertEquals(Queries.offset().queryFrom(TEST_2007_07_15), null); + } + + @Test + public void test_query_zone() { + assertEquals(TEST_2007_07_15.query(Queries.zone()), null); + assertEquals(Queries.zone().queryFrom(TEST_2007_07_15), null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_query_null() { + TEST_2007_07_15.query(null); + } + + //----------------------------------------------------------------------- + // get*() + //----------------------------------------------------------------------- + @DataProvider(name="sampleDates") + Object[][] provider_sampleDates() { + return new Object[][] { + {2008, 7, 5}, + {2007, 7, 5}, + {2006, 7, 5}, + {2005, 7, 5}, + {2004, 1, 1}, + {-1, 1, 2}, + }; + } + + //----------------------------------------------------------------------- + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_get(int y, int m, int d) { + LocalDate a = LocalDate.of(y, m, d); + assertEquals(a.getYear(), y); + assertEquals(a.getMonth(), Month.of(m)); + assertEquals(a.getDayOfMonth(), d); + } + + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_getDOY(int y, int m, int d) { + LocalDate a = LocalDate.of(y, m, d); + int total = 0; + for (int i = 1; i < m; i++) { + total += Month.of(i).length(isIsoLeap(y)); + } + int doy = total + d; + assertEquals(a.getDayOfYear(), doy); + } + + @Test(groups={"tck"}) + public void test_getDayOfWeek() { + DayOfWeek dow = DayOfWeek.MONDAY; + for (Month month : Month.values()) { + int length = month.length(false); + for (int i = 1; i <= length; i++) { + LocalDate d = LocalDate.of(2007, month, i); + assertSame(d.getDayOfWeek(), dow); + dow = dow.plus(1); + } + } + } + + //----------------------------------------------------------------------- + // isLeapYear() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_isLeapYear() { + assertEquals(LocalDate.of(1999, 1, 1).isLeapYear(), false); + assertEquals(LocalDate.of(2000, 1, 1).isLeapYear(), true); + assertEquals(LocalDate.of(2001, 1, 1).isLeapYear(), false); + assertEquals(LocalDate.of(2002, 1, 1).isLeapYear(), false); + assertEquals(LocalDate.of(2003, 1, 1).isLeapYear(), false); + assertEquals(LocalDate.of(2004, 1, 1).isLeapYear(), true); + assertEquals(LocalDate.of(2005, 1, 1).isLeapYear(), false); + + assertEquals(LocalDate.of(1500, 1, 1).isLeapYear(), false); + assertEquals(LocalDate.of(1600, 1, 1).isLeapYear(), true); + assertEquals(LocalDate.of(1700, 1, 1).isLeapYear(), false); + assertEquals(LocalDate.of(1800, 1, 1).isLeapYear(), false); + assertEquals(LocalDate.of(1900, 1, 1).isLeapYear(), false); + } + + //----------------------------------------------------------------------- + // lengthOfMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_lengthOfMonth_notLeapYear() { + assertEquals(LocalDate.of(2007, 1, 1).lengthOfMonth(), 31); + assertEquals(LocalDate.of(2007, 2, 1).lengthOfMonth(), 28); + assertEquals(LocalDate.of(2007, 3, 1).lengthOfMonth(), 31); + assertEquals(LocalDate.of(2007, 4, 1).lengthOfMonth(), 30); + assertEquals(LocalDate.of(2007, 5, 1).lengthOfMonth(), 31); + assertEquals(LocalDate.of(2007, 6, 1).lengthOfMonth(), 30); + assertEquals(LocalDate.of(2007, 7, 1).lengthOfMonth(), 31); + assertEquals(LocalDate.of(2007, 8, 1).lengthOfMonth(), 31); + assertEquals(LocalDate.of(2007, 9, 1).lengthOfMonth(), 30); + assertEquals(LocalDate.of(2007, 10, 1).lengthOfMonth(), 31); + assertEquals(LocalDate.of(2007, 11, 1).lengthOfMonth(), 30); + assertEquals(LocalDate.of(2007, 12, 1).lengthOfMonth(), 31); + } + + @Test(groups={"tck"}) + public void test_lengthOfMonth_leapYear() { + assertEquals(LocalDate.of(2008, 1, 1).lengthOfMonth(), 31); + assertEquals(LocalDate.of(2008, 2, 1).lengthOfMonth(), 29); + assertEquals(LocalDate.of(2008, 3, 1).lengthOfMonth(), 31); + assertEquals(LocalDate.of(2008, 4, 1).lengthOfMonth(), 30); + assertEquals(LocalDate.of(2008, 5, 1).lengthOfMonth(), 31); + assertEquals(LocalDate.of(2008, 6, 1).lengthOfMonth(), 30); + assertEquals(LocalDate.of(2008, 7, 1).lengthOfMonth(), 31); + assertEquals(LocalDate.of(2008, 8, 1).lengthOfMonth(), 31); + assertEquals(LocalDate.of(2008, 9, 1).lengthOfMonth(), 30); + assertEquals(LocalDate.of(2008, 10, 1).lengthOfMonth(), 31); + assertEquals(LocalDate.of(2008, 11, 1).lengthOfMonth(), 30); + assertEquals(LocalDate.of(2008, 12, 1).lengthOfMonth(), 31); + } + + //----------------------------------------------------------------------- + // lengthOfYear() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_lengthOfYear() { + assertEquals(LocalDate.of(2007, 1, 1).lengthOfYear(), 365); + assertEquals(LocalDate.of(2008, 1, 1).lengthOfYear(), 366); + } + + //----------------------------------------------------------------------- + // with() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_with_adjustment() { + final LocalDate sample = LocalDate.of(2012, 3, 4); + TemporalAdjuster adjuster = new TemporalAdjuster() { + @Override + public Temporal adjustInto(Temporal dateTime) { + return sample; + } + }; + assertEquals(TEST_2007_07_15.with(adjuster), sample); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_with_adjustment_null() { + TEST_2007_07_15.with((TemporalAdjuster) null); + } + + //----------------------------------------------------------------------- + // with(TemporalField,long) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_with_TemporalField_long_normal() { + LocalDate t = TEST_2007_07_15.with(YEAR, 2008); + assertEquals(t, LocalDate.of(2008, 7, 15)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"} ) + public void test_with_TemporalField_long_null() { + TEST_2007_07_15.with((TemporalField) null, 1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"} ) + public void test_with_TemporalField_long_invalidField() { + TEST_2007_07_15.with(MockFieldNoValue.INSTANCE, 1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"} ) + public void test_with_TemporalField_long_timeField() { + TEST_2007_07_15.with(ChronoField.AMPM_OF_DAY, 1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"} ) + public void test_with_TemporalField_long_invalidValue() { + TEST_2007_07_15.with(ChronoField.DAY_OF_WEEK, -1); + } + + //----------------------------------------------------------------------- + // withYear() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withYear_int_normal() { + LocalDate t = TEST_2007_07_15.withYear(2008); + assertEquals(t, LocalDate.of(2008, 7, 15)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withYear_int_invalid() { + TEST_2007_07_15.withYear(Year.MIN_VALUE - 1); + } + + @Test(groups={"tck"}) + public void test_withYear_int_adjustDay() { + LocalDate t = LocalDate.of(2008, 2, 29).withYear(2007); + LocalDate expected = LocalDate.of(2007, 2, 28); + assertEquals(t, expected); + } + + //----------------------------------------------------------------------- + // withMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withMonth_int_normal() { + LocalDate t = TEST_2007_07_15.withMonth(1); + assertEquals(t, LocalDate.of(2007, 1, 15)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withMonth_int_invalid() { + TEST_2007_07_15.withMonth(13); + } + + @Test(groups={"tck"}) + public void test_withMonth_int_adjustDay() { + LocalDate t = LocalDate.of(2007, 12, 31).withMonth(11); + LocalDate expected = LocalDate.of(2007, 11, 30); + assertEquals(t, expected); + } + + //----------------------------------------------------------------------- + // withDayOfMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withDayOfMonth_normal() { + LocalDate t = TEST_2007_07_15.withDayOfMonth(1); + assertEquals(t, LocalDate.of(2007, 7, 1)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfMonth_illegal() { + TEST_2007_07_15.withDayOfMonth(32); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfMonth_invalid() { + LocalDate.of(2007, 11, 30).withDayOfMonth(31); + } + + //----------------------------------------------------------------------- + // withDayOfYear(int) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withDayOfYear_normal() { + LocalDate t = TEST_2007_07_15.withDayOfYear(33); + assertEquals(t, LocalDate.of(2007, 2, 2)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfYear_illegal() { + TEST_2007_07_15.withDayOfYear(367); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfYear_invalid() { + TEST_2007_07_15.withDayOfYear(366); + } + + //----------------------------------------------------------------------- + // plus(Period) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plus_Period_positiveMonths() { + MockSimplePeriod period = MockSimplePeriod.of(7, ChronoUnit.MONTHS); + LocalDate t = TEST_2007_07_15.plus(period); + assertEquals(t, LocalDate.of(2008, 2, 15)); + } + + @Test(groups={"tck"}) + public void test_plus_Period_negativeDays() { + MockSimplePeriod period = MockSimplePeriod.of(-25, ChronoUnit.DAYS); + LocalDate t = TEST_2007_07_15.plus(period); + assertEquals(t, LocalDate.of(2007, 6, 20)); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_plus_Period_timeNotAllowed() { + MockSimplePeriod period = MockSimplePeriod.of(7, ChronoUnit.HOURS); + TEST_2007_07_15.plus(period); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_plus_Period_null() { + TEST_2007_07_15.plus((MockSimplePeriod) null); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plus_Period_invalidTooLarge() { + MockSimplePeriod period = MockSimplePeriod.of(1, ChronoUnit.YEARS); + LocalDate.of(Year.MAX_VALUE, 1, 1).plus(period); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plus_Period_invalidTooSmall() { + MockSimplePeriod period = MockSimplePeriod.of(-1, ChronoUnit.YEARS); + LocalDate.of(Year.MIN_VALUE, 1, 1).plus(period); + } + + //----------------------------------------------------------------------- + // plus(long,TemporalUnit) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plus_longTemporalUnit_positiveMonths() { + LocalDate t = TEST_2007_07_15.plus(7, ChronoUnit.MONTHS); + assertEquals(t, LocalDate.of(2008, 2, 15)); + } + + @Test(groups={"tck"}) + public void test_plus_longTemporalUnit_negativeDays() { + LocalDate t = TEST_2007_07_15.plus(-25, ChronoUnit.DAYS); + assertEquals(t, LocalDate.of(2007, 6, 20)); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_plus_longTemporalUnit_timeNotAllowed() { + TEST_2007_07_15.plus(7, ChronoUnit.HOURS); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_plus_longTemporalUnit_null() { + TEST_2007_07_15.plus(1, (TemporalUnit) null); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plus_longTemporalUnit_invalidTooLarge() { + LocalDate.of(Year.MAX_VALUE, 1, 1).plus(1, ChronoUnit.YEARS); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plus_longTemporalUnit_invalidTooSmall() { + LocalDate.of(Year.MIN_VALUE, 1, 1).plus(-1, ChronoUnit.YEARS); + } + + //----------------------------------------------------------------------- + // plusYears() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusYears_long_normal() { + LocalDate t = TEST_2007_07_15.plusYears(1); + assertEquals(t, LocalDate.of(2008, 7, 15)); + } + + @Test(groups={"tck"}) + public void test_plusYears_long_negative() { + LocalDate t = TEST_2007_07_15.plusYears(-1); + assertEquals(t, LocalDate.of(2006, 7, 15)); + } + + @Test(groups={"tck"}) + public void test_plusYears_long_adjustDay() { + LocalDate t = LocalDate.of(2008, 2, 29).plusYears(1); + LocalDate expected = LocalDate.of(2009, 2, 28); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_plusYears_long_big() { + long years = 20L + Year.MAX_VALUE; + LocalDate test = LocalDate.of(-40, 6, 1).plusYears(years); + assertEquals(test, LocalDate.of((int) (-40L + years), 6, 1)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_long_invalidTooLarge() { + LocalDate test = LocalDate.of(Year.MAX_VALUE, 6, 1); + test.plusYears(1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_long_invalidTooLargeMaxAddMax() { + LocalDate test = LocalDate.of(Year.MAX_VALUE, 12, 1); + test.plusYears(Long.MAX_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_long_invalidTooLargeMaxAddMin() { + LocalDate test = LocalDate.of(Year.MAX_VALUE, 12, 1); + test.plusYears(Long.MIN_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_long_invalidTooSmall_validInt() { + LocalDate.of(Year.MIN_VALUE, 1, 1).plusYears(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_long_invalidTooSmall_invalidInt() { + LocalDate.of(Year.MIN_VALUE, 1, 1).plusYears(-10); + } + + //----------------------------------------------------------------------- + // plusMonths() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusMonths_long_normal() { + LocalDate t = TEST_2007_07_15.plusMonths(1); + assertEquals(t, LocalDate.of(2007, 8, 15)); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_overYears() { + LocalDate t = TEST_2007_07_15.plusMonths(25); + assertEquals(t, LocalDate.of(2009, 8, 15)); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_negative() { + LocalDate t = TEST_2007_07_15.plusMonths(-1); + assertEquals(t, LocalDate.of(2007, 6, 15)); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_negativeAcrossYear() { + LocalDate t = TEST_2007_07_15.plusMonths(-7); + assertEquals(t, LocalDate.of(2006, 12, 15)); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_negativeOverYears() { + LocalDate t = TEST_2007_07_15.plusMonths(-31); + assertEquals(t, LocalDate.of(2004, 12, 15)); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_adjustDayFromLeapYear() { + LocalDate t = LocalDate.of(2008, 2, 29).plusMonths(12); + LocalDate expected = LocalDate.of(2009, 2, 28); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_adjustDayFromMonthLength() { + LocalDate t = LocalDate.of(2007, 3, 31).plusMonths(1); + LocalDate expected = LocalDate.of(2007, 4, 30); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_big() { + long months = 20L + Integer.MAX_VALUE; + LocalDate test = LocalDate.of(-40, 6, 1).plusMonths(months); + assertEquals(test, LocalDate.of((int) (-40L + months / 12), 6 + (int) (months % 12), 1)); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_plusMonths_long_invalidTooLarge() { + LocalDate.of(Year.MAX_VALUE, 12, 1).plusMonths(1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusMonths_long_invalidTooLargeMaxAddMax() { + LocalDate test = LocalDate.of(Year.MAX_VALUE, 12, 1); + test.plusMonths(Long.MAX_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusMonths_long_invalidTooLargeMaxAddMin() { + LocalDate test = LocalDate.of(Year.MAX_VALUE, 12, 1); + test.plusMonths(Long.MIN_VALUE); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_plusMonths_long_invalidTooSmall() { + LocalDate.of(Year.MIN_VALUE, 1, 1).plusMonths(-1); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_normal() { + LocalDate t = TEST_2007_07_15.plusWeeks(1); + assertEquals(t, LocalDate.of(2007, 7, 22)); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_overMonths() { + LocalDate t = TEST_2007_07_15.plusWeeks(9); + assertEquals(t, LocalDate.of(2007, 9, 16)); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_overYears() { + LocalDate t = LocalDate.of(2006, 7, 16).plusWeeks(52); + assertEquals(t, TEST_2007_07_15); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_overLeapYears() { + LocalDate t = TEST_2007_07_15.plusYears(-1).plusWeeks(104); + assertEquals(t, LocalDate.of(2008, 7, 12)); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_negative() { + LocalDate t = TEST_2007_07_15.plusWeeks(-1); + assertEquals(t, LocalDate.of(2007, 7, 8)); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_negativeAcrossYear() { + LocalDate t = TEST_2007_07_15.plusWeeks(-28); + assertEquals(t, LocalDate.of(2006, 12, 31)); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_negativeOverYears() { + LocalDate t = TEST_2007_07_15.plusWeeks(-104); + assertEquals(t, LocalDate.of(2005, 7, 17)); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_maximum() { + LocalDate t = LocalDate.of(Year.MAX_VALUE, 12, 24).plusWeeks(1); + LocalDate expected = LocalDate.of(Year.MAX_VALUE, 12, 31); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_minimum() { + LocalDate t = LocalDate.of(Year.MIN_VALUE, 1, 8).plusWeeks(-1); + LocalDate expected = LocalDate.of(Year.MIN_VALUE, 1, 1); + assertEquals(t, expected); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_plusWeeks_invalidTooLarge() { + LocalDate.of(Year.MAX_VALUE, 12, 25).plusWeeks(1); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_plusWeeks_invalidTooSmall() { + LocalDate.of(Year.MIN_VALUE, 1, 7).plusWeeks(-1); + } + + @Test(expectedExceptions={ArithmeticException.class}, groups={"tck"}) + public void test_plusWeeks_invalidMaxMinusMax() { + LocalDate.of(Year.MAX_VALUE, 12, 25).plusWeeks(Long.MAX_VALUE); + } + + @Test(expectedExceptions={ArithmeticException.class}, groups={"tck"}) + public void test_plusWeeks_invalidMaxMinusMin() { + LocalDate.of(Year.MAX_VALUE, 12, 25).plusWeeks(Long.MIN_VALUE); + } + + @Test(groups={"tck"}) + public void test_plusDays_normal() { + LocalDate t = TEST_2007_07_15.plusDays(1); + assertEquals(t, LocalDate.of(2007, 7, 16)); + } + + @Test(groups={"tck"}) + public void test_plusDays_overMonths() { + LocalDate t = TEST_2007_07_15.plusDays(62); + assertEquals(t, LocalDate.of(2007, 9, 15)); + } + + @Test(groups={"tck"}) + public void test_plusDays_overYears() { + LocalDate t = LocalDate.of(2006, 7, 14).plusDays(366); + assertEquals(t, TEST_2007_07_15); + } + + @Test(groups={"tck"}) + public void test_plusDays_overLeapYears() { + LocalDate t = TEST_2007_07_15.plusYears(-1).plusDays(365 + 366); + assertEquals(t, LocalDate.of(2008, 7, 15)); + } + + @Test(groups={"tck"}) + public void test_plusDays_negative() { + LocalDate t = TEST_2007_07_15.plusDays(-1); + assertEquals(t, LocalDate.of(2007, 7, 14)); + } + + @Test(groups={"tck"}) + public void test_plusDays_negativeAcrossYear() { + LocalDate t = TEST_2007_07_15.plusDays(-196); + assertEquals(t, LocalDate.of(2006, 12, 31)); + } + + @Test(groups={"tck"}) + public void test_plusDays_negativeOverYears() { + LocalDate t = TEST_2007_07_15.plusDays(-730); + assertEquals(t, LocalDate.of(2005, 7, 15)); + } + + @Test(groups={"tck"}) + public void test_plusDays_maximum() { + LocalDate t = LocalDate.of(Year.MAX_VALUE, 12, 30).plusDays(1); + LocalDate expected = LocalDate.of(Year.MAX_VALUE, 12, 31); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_plusDays_minimum() { + LocalDate t = LocalDate.of(Year.MIN_VALUE, 1, 2).plusDays(-1); + LocalDate expected = LocalDate.of(Year.MIN_VALUE, 1, 1); + assertEquals(t, expected); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_plusDays_invalidTooLarge() { + LocalDate.of(Year.MAX_VALUE, 12, 31).plusDays(1); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_plusDays_invalidTooSmall() { + LocalDate.of(Year.MIN_VALUE, 1, 1).plusDays(-1); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_plusDays_overflowTooLarge() { + LocalDate.of(Year.MAX_VALUE, 12, 31).plusDays(Long.MAX_VALUE); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_plusDays_overflowTooSmall() { + LocalDate.of(Year.MIN_VALUE, 1, 1).plusDays(Long.MIN_VALUE); + } + + //----------------------------------------------------------------------- + // minus(Period) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minus_Period_positiveMonths() { + MockSimplePeriod period = MockSimplePeriod.of(7, ChronoUnit.MONTHS); + LocalDate t = TEST_2007_07_15.minus(period); + assertEquals(t, LocalDate.of(2006, 12, 15)); + } + + @Test(groups={"tck"}) + public void test_minus_Period_negativeDays() { + MockSimplePeriod period = MockSimplePeriod.of(-25, ChronoUnit.DAYS); + LocalDate t = TEST_2007_07_15.minus(period); + assertEquals(t, LocalDate.of(2007, 8, 9)); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_minus_Period_timeNotAllowed() { + MockSimplePeriod period = MockSimplePeriod.of(7, ChronoUnit.HOURS); + TEST_2007_07_15.minus(period); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_minus_Period_null() { + TEST_2007_07_15.minus((MockSimplePeriod) null); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minus_Period_invalidTooLarge() { + MockSimplePeriod period = MockSimplePeriod.of(-1, ChronoUnit.YEARS); + LocalDate.of(Year.MAX_VALUE, 1, 1).minus(period); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minus_Period_invalidTooSmall() { + MockSimplePeriod period = MockSimplePeriod.of(1, ChronoUnit.YEARS); + LocalDate.of(Year.MIN_VALUE, 1, 1).minus(period); + } + + //----------------------------------------------------------------------- + // minus(long,TemporalUnit) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minus_longTemporalUnit_positiveMonths() { + LocalDate t = TEST_2007_07_15.minus(7, ChronoUnit.MONTHS); + assertEquals(t, LocalDate.of(2006, 12, 15)); + } + + @Test(groups={"tck"}) + public void test_minus_longTemporalUnit_negativeDays() { + LocalDate t = TEST_2007_07_15.minus(-25, ChronoUnit.DAYS); + assertEquals(t, LocalDate.of(2007, 8, 9)); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_minus_longTemporalUnit_timeNotAllowed() { + TEST_2007_07_15.minus(7, ChronoUnit.HOURS); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_minus_longTemporalUnit_null() { + TEST_2007_07_15.minus(1, (TemporalUnit) null); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minus_longTemporalUnit_invalidTooLarge() { + LocalDate.of(Year.MAX_VALUE, 1, 1).minus(-1, ChronoUnit.YEARS); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minus_longTemporalUnit_invalidTooSmall() { + LocalDate.of(Year.MIN_VALUE, 1, 1).minus(1, ChronoUnit.YEARS); + } + + //----------------------------------------------------------------------- + // minusYears() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusYears_long_normal() { + LocalDate t = TEST_2007_07_15.minusYears(1); + assertEquals(t, LocalDate.of(2006, 7, 15)); + } + + @Test(groups={"tck"}) + public void test_minusYears_long_negative() { + LocalDate t = TEST_2007_07_15.minusYears(-1); + assertEquals(t, LocalDate.of(2008, 7, 15)); + } + + @Test(groups={"tck"}) + public void test_minusYears_long_adjustDay() { + LocalDate t = LocalDate.of(2008, 2, 29).minusYears(1); + LocalDate expected = LocalDate.of(2007, 2, 28); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_minusYears_long_big() { + long years = 20L + Year.MAX_VALUE; + LocalDate test = LocalDate.of(40, 6, 1).minusYears(years); + assertEquals(test, LocalDate.of((int) (40L - years), 6, 1)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_long_invalidTooLarge() { + LocalDate test = LocalDate.of(Year.MAX_VALUE, 6, 1); + test.minusYears(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_long_invalidTooLargeMaxAddMax() { + LocalDate test = LocalDate.of(Year.MAX_VALUE, 12, 1); + test.minusYears(Long.MAX_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_long_invalidTooLargeMaxAddMin() { + LocalDate test = LocalDate.of(Year.MAX_VALUE, 12, 1); + test.minusYears(Long.MIN_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_long_invalidTooSmall() { + LocalDate.of(Year.MIN_VALUE, 1, 1).minusYears(1); + } + + //----------------------------------------------------------------------- + // minusMonths() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusMonths_long_normal() { + LocalDate t = TEST_2007_07_15.minusMonths(1); + assertEquals(t, LocalDate.of(2007, 6, 15)); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_overYears() { + LocalDate t = TEST_2007_07_15.minusMonths(25); + assertEquals(t, LocalDate.of(2005, 6, 15)); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_negative() { + LocalDate t = TEST_2007_07_15.minusMonths(-1); + assertEquals(t, LocalDate.of(2007, 8, 15)); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_negativeAcrossYear() { + LocalDate t = TEST_2007_07_15.minusMonths(-7); + assertEquals(t, LocalDate.of(2008, 2, 15)); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_negativeOverYears() { + LocalDate t = TEST_2007_07_15.minusMonths(-31); + assertEquals(t, LocalDate.of(2010, 2, 15)); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_adjustDayFromLeapYear() { + LocalDate t = LocalDate.of(2008, 2, 29).minusMonths(12); + LocalDate expected = LocalDate.of(2007, 2, 28); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_adjustDayFromMonthLength() { + LocalDate t = LocalDate.of(2007, 3, 31).minusMonths(1); + LocalDate expected = LocalDate.of(2007, 2, 28); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_big() { + long months = 20L + Integer.MAX_VALUE; + LocalDate test = LocalDate.of(40, 6, 1).minusMonths(months); + assertEquals(test, LocalDate.of((int) (40L - months / 12), 6 - (int) (months % 12), 1)); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_minusMonths_long_invalidTooLarge() { + LocalDate.of(Year.MAX_VALUE, 12, 1).minusMonths(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusMonths_long_invalidTooLargeMaxAddMax() { + LocalDate test = LocalDate.of(Year.MAX_VALUE, 12, 1); + test.minusMonths(Long.MAX_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusMonths_long_invalidTooLargeMaxAddMin() { + LocalDate test = LocalDate.of(Year.MAX_VALUE, 12, 1); + test.minusMonths(Long.MIN_VALUE); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_minusMonths_long_invalidTooSmall() { + LocalDate.of(Year.MIN_VALUE, 1, 1).minusMonths(1); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_normal() { + LocalDate t = TEST_2007_07_15.minusWeeks(1); + assertEquals(t, LocalDate.of(2007, 7, 8)); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_overMonths() { + LocalDate t = TEST_2007_07_15.minusWeeks(9); + assertEquals(t, LocalDate.of(2007, 5, 13)); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_overYears() { + LocalDate t = LocalDate.of(2008, 7, 13).minusWeeks(52); + assertEquals(t, TEST_2007_07_15); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_overLeapYears() { + LocalDate t = TEST_2007_07_15.minusYears(-1).minusWeeks(104); + assertEquals(t, LocalDate.of(2006, 7, 18)); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_negative() { + LocalDate t = TEST_2007_07_15.minusWeeks(-1); + assertEquals(t, LocalDate.of(2007, 7, 22)); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_negativeAcrossYear() { + LocalDate t = TEST_2007_07_15.minusWeeks(-28); + assertEquals(t, LocalDate.of(2008, 1, 27)); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_negativeOverYears() { + LocalDate t = TEST_2007_07_15.minusWeeks(-104); + assertEquals(t, LocalDate.of(2009, 7, 12)); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_maximum() { + LocalDate t = LocalDate.of(Year.MAX_VALUE, 12, 24).minusWeeks(-1); + LocalDate expected = LocalDate.of(Year.MAX_VALUE, 12, 31); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_minimum() { + LocalDate t = LocalDate.of(Year.MIN_VALUE, 1, 8).minusWeeks(1); + LocalDate expected = LocalDate.of(Year.MIN_VALUE, 1, 1); + assertEquals(t, expected); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_minusWeeks_invalidTooLarge() { + LocalDate.of(Year.MAX_VALUE, 12, 25).minusWeeks(-1); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_minusWeeks_invalidTooSmall() { + LocalDate.of(Year.MIN_VALUE, 1, 7).minusWeeks(1); + } + + @Test(expectedExceptions={ArithmeticException.class}, groups={"tck"}) + public void test_minusWeeks_invalidMaxMinusMax() { + LocalDate.of(Year.MAX_VALUE, 12, 25).minusWeeks(Long.MAX_VALUE); + } + + @Test(expectedExceptions={ArithmeticException.class}, groups={"tck"}) + public void test_minusWeeks_invalidMaxMinusMin() { + LocalDate.of(Year.MAX_VALUE, 12, 25).minusWeeks(Long.MIN_VALUE); + } + + @Test(groups={"tck"}) + public void test_minusDays_normal() { + LocalDate t = TEST_2007_07_15.minusDays(1); + assertEquals(t, LocalDate.of(2007, 7, 14)); + } + + @Test(groups={"tck"}) + public void test_minusDays_overMonths() { + LocalDate t = TEST_2007_07_15.minusDays(62); + assertEquals(t, LocalDate.of(2007, 5, 14)); + } + + @Test(groups={"tck"}) + public void test_minusDays_overYears() { + LocalDate t = LocalDate.of(2008, 7, 16).minusDays(367); + assertEquals(t, TEST_2007_07_15); + } + + @Test(groups={"tck"}) + public void test_minusDays_overLeapYears() { + LocalDate t = TEST_2007_07_15.plusYears(2).minusDays(365 + 366); + assertEquals(t, TEST_2007_07_15); + } + + @Test(groups={"tck"}) + public void test_minusDays_negative() { + LocalDate t = TEST_2007_07_15.minusDays(-1); + assertEquals(t, LocalDate.of(2007, 7, 16)); + } + + @Test(groups={"tck"}) + public void test_minusDays_negativeAcrossYear() { + LocalDate t = TEST_2007_07_15.minusDays(-169); + assertEquals(t, LocalDate.of(2007, 12, 31)); + } + + @Test(groups={"tck"}) + public void test_minusDays_negativeOverYears() { + LocalDate t = TEST_2007_07_15.minusDays(-731); + assertEquals(t, LocalDate.of(2009, 7, 15)); + } + + @Test(groups={"tck"}) + public void test_minusDays_maximum() { + LocalDate t = LocalDate.of(Year.MAX_VALUE, 12, 30).minusDays(-1); + LocalDate expected = LocalDate.of(Year.MAX_VALUE, 12, 31); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_minusDays_minimum() { + LocalDate t = LocalDate.of(Year.MIN_VALUE, 1, 2).minusDays(1); + LocalDate expected = LocalDate.of(Year.MIN_VALUE, 1, 1); + assertEquals(t, expected); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_minusDays_invalidTooLarge() { + LocalDate.of(Year.MAX_VALUE, 12, 31).minusDays(-1); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_minusDays_invalidTooSmall() { + LocalDate.of(Year.MIN_VALUE, 1, 1).minusDays(1); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_minusDays_overflowTooLarge() { + LocalDate.of(Year.MAX_VALUE, 12, 31).minusDays(Long.MIN_VALUE); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_minusDays_overflowTooSmall() { + LocalDate.of(Year.MIN_VALUE, 1, 1).minusDays(Long.MAX_VALUE); + } + + //----------------------------------------------------------------------- + // atTime() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atTime_LocalTime() { + LocalDate t = LocalDate.of(2008, 6, 30); + assertEquals(t.atTime(LocalTime.of(11, 30)), LocalDateTime.of(2008, 6, 30, 11, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_atTime_LocalTime_null() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime((LocalTime) null); + } + + //------------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atTime_int_int() { + LocalDate t = LocalDate.of(2008, 6, 30); + assertEquals(t.atTime(11, 30), LocalDateTime.of(2008, 6, 30, 11, 30)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_hourTooSmall() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(-1, 30); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_hourTooBig() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(24, 30); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_minuteTooSmall() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(11, -1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_minuteTooBig() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(11, 60); + } + + @Test(groups={"tck"}) + public void test_atTime_int_int_int() { + LocalDate t = LocalDate.of(2008, 6, 30); + assertEquals(t.atTime(11, 30, 40), LocalDateTime.of(2008, 6, 30, 11, 30, 40)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_int_hourTooSmall() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(-1, 30, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_int_hourTooBig() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(24, 30, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_int_minuteTooSmall() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(11, -1, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_int_minuteTooBig() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(11, 60, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_int_secondTooSmall() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(11, 30, -1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_int_secondTooBig() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(11, 30, 60); + } + + @Test(groups={"tck"}) + public void test_atTime_int_int_int_int() { + LocalDate t = LocalDate.of(2008, 6, 30); + assertEquals(t.atTime(11, 30, 40, 50), LocalDateTime.of(2008, 6, 30, 11, 30, 40, 50)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_int_int_hourTooSmall() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(-1, 30, 40, 50); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_int_int_hourTooBig() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(24, 30, 40, 50); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_int_int_minuteTooSmall() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(11, -1, 40, 50); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_int_int_minuteTooBig() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(11, 60, 40, 50); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_int_int_secondTooSmall() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(11, 30, -1, 50); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_int_int_secondTooBig() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(11, 30, 60, 50); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_int_int_nanoTooSmall() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(11, 30, 40, -1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atTime_int_int_int_int_nanoTooBig() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atTime(11, 30, 40, 1000000000); + } + + //----------------------------------------------------------------------- + // atOffset() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atOffset() { + LocalDate t = LocalDate.of(2008, 6, 30); + assertEquals(t.atOffset(OFFSET_PTWO), OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PTWO)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_atOffset_nullZoneOffset() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atOffset((ZoneOffset) null); + } + + //----------------------------------------------------------------------- + // atStartOfDay() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atStartOfDay() { + LocalDate t = LocalDate.of(2008, 6, 30); + assertEquals(t.atStartOfDay(ZONE_PARIS), + ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 0, 0), ZONE_PARIS)); + } + + @Test(groups={"tck"}) + public void test_atStartOfDay_dstGap() { + LocalDate t = LocalDate.of(2007, 4, 1); + assertEquals(t.atStartOfDay(ZONE_GAZA), + ZonedDateTime.of(LocalDateTime.of(2007, 4, 1, 1, 0), ZONE_GAZA)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_atStartOfDay_nullTimeZone() { + LocalDate t = LocalDate.of(2008, 6, 30); + t.atStartOfDay((ZoneId) null); + } + + //----------------------------------------------------------------------- + // toEpochDay() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toEpochDay() { + long date_0000_01_01 = -678941 - 40587; + + LocalDate test = LocalDate.of(0, 1, 1); + for (long i = date_0000_01_01; i < 700000; i++) { + assertEquals(test.toEpochDay(), i); + test = next(test); + } + test = LocalDate.of(0, 1, 1); + for (long i = date_0000_01_01; i > -2000000; i--) { + assertEquals(test.toEpochDay(), i); + test = previous(test); + } + + assertEquals(LocalDate.of(1858, 11, 17).toEpochDay(), -40587); + assertEquals(LocalDate.of(1, 1, 1).toEpochDay(), -678575 - 40587); + assertEquals(LocalDate.of(1995, 9, 27).toEpochDay(), 49987 - 40587); + assertEquals(LocalDate.of(1970, 1, 1).toEpochDay(), 0); + assertEquals(LocalDate.of(-1, 12, 31).toEpochDay(), -678942 - 40587); + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_comparisons() { + doTest_comparisons_LocalDate( + LocalDate.of(Year.MIN_VALUE, 1, 1), + LocalDate.of(Year.MIN_VALUE, 12, 31), + LocalDate.of(-1, 1, 1), + LocalDate.of(-1, 12, 31), + LocalDate.of(0, 1, 1), + LocalDate.of(0, 12, 31), + LocalDate.of(1, 1, 1), + LocalDate.of(1, 12, 31), + LocalDate.of(2006, 1, 1), + LocalDate.of(2006, 12, 31), + LocalDate.of(2007, 1, 1), + LocalDate.of(2007, 12, 31), + LocalDate.of(2008, 1, 1), + LocalDate.of(2008, 2, 29), + LocalDate.of(2008, 12, 31), + LocalDate.of(Year.MAX_VALUE, 1, 1), + LocalDate.of(Year.MAX_VALUE, 12, 31) + ); + } + + void doTest_comparisons_LocalDate(LocalDate... localDates) { + for (int i = 0; i < localDates.length; i++) { + LocalDate a = localDates[i]; + for (int j = 0; j < localDates.length; j++) { + LocalDate b = localDates[j]; + if (i < j) { + assertTrue(a.compareTo(b) < 0, a + " <=> " + b); + assertEquals(a.isBefore(b), true, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else if (i > j) { + assertTrue(a.compareTo(b) > 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), true, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else { + assertEquals(a.compareTo(b), 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), true, a + " <=> " + b); + } + } + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_compareTo_ObjectNull() { + TEST_2007_07_15.compareTo(null); + } + + @Test(groups={"tck"}) + public void test_isBefore() { + assertTrue(TEST_2007_07_15.isBefore(LocalDate.of(2007, 07, 16))); + assertFalse(TEST_2007_07_15.isBefore(LocalDate.of(2007, 07, 14))); + assertFalse(TEST_2007_07_15.isBefore(TEST_2007_07_15)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isBefore_ObjectNull() { + TEST_2007_07_15.isBefore(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isAfter_ObjectNull() { + TEST_2007_07_15.isAfter(null); + } + + @Test(groups={"tck"}) + public void test_isAfter() { + assertTrue(TEST_2007_07_15.isAfter(LocalDate.of(2007, 07, 14))); + assertFalse(TEST_2007_07_15.isAfter(LocalDate.of(2007, 07, 16))); + assertFalse(TEST_2007_07_15.isAfter(TEST_2007_07_15)); + } + + @Test(expectedExceptions=ClassCastException.class, groups={"tck"}) + @SuppressWarnings({"unchecked", "rawtypes"}) + public void compareToNonLocalDate() { + Comparable c = TEST_2007_07_15; + c.compareTo(new Object()); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleDates" , groups={"tck"}) + public void test_equals_true(int y, int m, int d) { + LocalDate a = LocalDate.of(y, m, d); + LocalDate b = LocalDate.of(y, m, d); + assertEquals(a.equals(b), true); + } + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_equals_false_year_differs(int y, int m, int d) { + LocalDate a = LocalDate.of(y, m, d); + LocalDate b = LocalDate.of(y + 1, m, d); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_equals_false_month_differs(int y, int m, int d) { + LocalDate a = LocalDate.of(y, m, d); + LocalDate b = LocalDate.of(y, m + 1, d); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_equals_false_day_differs(int y, int m, int d) { + LocalDate a = LocalDate.of(y, m, d); + LocalDate b = LocalDate.of(y, m, d + 1); + assertEquals(a.equals(b), false); + } + + @Test(groups={"tck"}) + public void test_equals_itself_true() { + assertEquals(TEST_2007_07_15.equals(TEST_2007_07_15), true); + } + + @Test(groups={"tck"}) + public void test_equals_string_false() { + assertEquals(TEST_2007_07_15.equals("2007-07-15"), false); + } + + @Test(groups={"tck"}) + public void test_equals_null_false() { + assertEquals(TEST_2007_07_15.equals(null), false); + } + + //----------------------------------------------------------------------- + // hashCode() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_hashCode(int y, int m, int d) { + LocalDate a = LocalDate.of(y, m, d); + assertEquals(a.hashCode(), a.hashCode()); + LocalDate b = LocalDate.of(y, m, d); + assertEquals(a.hashCode(), b.hashCode()); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="sampleToString") + Object[][] provider_sampleToString() { + return new Object[][] { + {2008, 7, 5, "2008-07-05"}, + {2007, 12, 31, "2007-12-31"}, + {999, 12, 31, "0999-12-31"}, + {-1, 1, 2, "-0001-01-02"}, + {9999, 12, 31, "9999-12-31"}, + {-9999, 12, 31, "-9999-12-31"}, + {10000, 1, 1, "+10000-01-01"}, + {-10000, 1, 1, "-10000-01-01"}, + {12345678, 1, 1, "+12345678-01-01"}, + {-12345678, 1, 1, "-12345678-01-01"}, + }; + } + + @Test(dataProvider="sampleToString", groups={"tck"}) + public void test_toString(int y, int m, int d, String expected) { + LocalDate t = LocalDate.of(y, m, d); + String str = t.toString(); + assertEquals(str, expected); + } + + //----------------------------------------------------------------------- + // toString(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d"); + String t = LocalDate.of(2010, 12, 3).toString(f); + assertEquals(t, "2010 12 3"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_toString_formatter_null() { + LocalDate.of(2010, 12, 3).toString(null); + } + +} diff --git a/test/java/time/tck/java/time/TCKLocalDateTime.java b/test/java/time/tck/java/time/TCKLocalDateTime.java new file mode 100644 index 0000000000000000000000000000000000000000..1fa08f3c93e1b97bb3ccb601ce7f9b6b79108ae0 --- /dev/null +++ b/test/java/time/tck/java/time/TCKLocalDateTime.java @@ -0,0 +1,3045 @@ +/* + * 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. + * + * 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 tck.java.time; + +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; +import static java.time.temporal.ChronoField.AMPM_OF_DAY; +import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM; +import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.EPOCH_MONTH; +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoField.HOUR_OF_AMPM; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.MICRO_OF_DAY; +import static java.time.temporal.ChronoField.MICRO_OF_SECOND; +import static java.time.temporal.ChronoField.MILLI_OF_DAY; +import static java.time.temporal.ChronoField.MILLI_OF_SECOND; +import static java.time.temporal.ChronoField.MINUTE_OF_DAY; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.NANO_OF_DAY; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.SECOND_OF_DAY; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import java.time.Clock; +import java.time.DateTimeException; +import java.time.DayOfWeek; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.Period; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.ISOChrono; +import java.time.temporal.JulianFields; +import java.time.temporal.OffsetDateTime; +import java.time.temporal.Queries; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.TemporalUnit; +import java.time.temporal.Year; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import test.java.time.MockSimplePeriod; + +/** + * Test LocalDateTime. + */ +@Test +public class TCKLocalDateTime extends AbstractDateTimeTest { + + private static final ZoneOffset OFFSET_PONE = ZoneOffset.ofHours(1); + private static final ZoneOffset OFFSET_PTWO = ZoneOffset.ofHours(2); + private static final ZoneOffset OFFSET_MTWO = ZoneOffset.ofHours(-2); + private static final ZoneId ZONE_PARIS = ZoneId.of("Europe/Paris"); + private static final ZoneId ZONE_GAZA = ZoneId.of("Asia/Gaza"); + + private LocalDateTime TEST_2007_07_15_12_30_40_987654321 = LocalDateTime.of(2007, 7, 15, 12, 30, 40, 987654321); + private LocalDateTime MAX_DATE_TIME; + private LocalDateTime MIN_DATE_TIME; + private Instant MAX_INSTANT; + private Instant MIN_INSTANT; + + @BeforeMethod(groups={"implementation","tck"}) + public void setUp() { + MAX_DATE_TIME = LocalDateTime.MAX; + MIN_DATE_TIME = LocalDateTime.MIN; + MAX_INSTANT = MAX_DATE_TIME.atZone(ZoneOffset.UTC).toInstant(); + MIN_INSTANT = MIN_DATE_TIME.atZone(ZoneOffset.UTC).toInstant(); + } + + //----------------------------------------------------------------------- + @Override + protected List samples() { + TemporalAccessor[] array = {TEST_2007_07_15_12_30_40_987654321, LocalDateTime.MAX, LocalDateTime.MIN, }; + return Arrays.asList(array); + } + + @Override + protected List validFields() { + TemporalField[] array = { + NANO_OF_SECOND, + NANO_OF_DAY, + MICRO_OF_SECOND, + MICRO_OF_DAY, + MILLI_OF_SECOND, + MILLI_OF_DAY, + SECOND_OF_MINUTE, + SECOND_OF_DAY, + MINUTE_OF_HOUR, + MINUTE_OF_DAY, + CLOCK_HOUR_OF_AMPM, + HOUR_OF_AMPM, + CLOCK_HOUR_OF_DAY, + HOUR_OF_DAY, + AMPM_OF_DAY, + DAY_OF_WEEK, + ALIGNED_DAY_OF_WEEK_IN_MONTH, + ALIGNED_DAY_OF_WEEK_IN_YEAR, + DAY_OF_MONTH, + DAY_OF_YEAR, + EPOCH_DAY, + ALIGNED_WEEK_OF_MONTH, + ALIGNED_WEEK_OF_YEAR, + MONTH_OF_YEAR, + EPOCH_MONTH, + YEAR_OF_ERA, + YEAR, + ERA, + JulianFields.JULIAN_DAY, + JulianFields.MODIFIED_JULIAN_DAY, + JulianFields.RATA_DIE, + }; + return Arrays.asList(array); + } + + @Override + protected List invalidFields() { + List list = new ArrayList<>(Arrays.asList(ChronoField.values())); + list.removeAll(validFields()); + return list; + } + + //----------------------------------------------------------------------- + private void check(LocalDateTime test, int y, int m, int d, int h, int mi, int s, int n) { + assertEquals(test.getYear(), y); + assertEquals(test.getMonth().getValue(), m); + assertEquals(test.getDayOfMonth(), d); + assertEquals(test.getHour(), h); + assertEquals(test.getMinute(), mi); + assertEquals(test.getSecond(), s); + assertEquals(test.getNano(), n); + assertEquals(test, test); + assertEquals(test.hashCode(), test.hashCode()); + assertEquals(LocalDateTime.of(y, m, d, h, mi, s, n), test); + } + + private LocalDateTime createDateMidnight(int year, int month, int day) { + return LocalDateTime.of(year, month, day, 0, 0); + } + + //----------------------------------------------------------------------- + @Test + public void test_serialization() throws Exception { + assertSerializable(TEST_2007_07_15_12_30_40_987654321); + assertSerializable(LocalDateTime.MIN); + assertSerializable(LocalDateTime.MAX); + } + + @Test + public void test_serialization_format() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(5); + dos.writeInt(2012); + dos.writeByte(9); + dos.writeByte(16); + dos.writeByte(22); + dos.writeByte(17); + dos.writeByte(59); + dos.writeInt(459_000_000); + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(LocalDateTime.of(2012, 9, 16, 22, 17, 59, 459_000_000), bytes); + } + + //----------------------------------------------------------------------- + // constants + //----------------------------------------------------------------------- + @Test + public void constant_MIN() { + check(LocalDateTime.MIN, Year.MIN_VALUE, 1, 1, 0, 0, 0, 0); + } + + @Test + public void constant_MAX() { + check(LocalDateTime.MAX, Year.MAX_VALUE, 12, 31, 23, 59, 59, 999999999); + } + + //----------------------------------------------------------------------- + // now() + //----------------------------------------------------------------------- + @Test(timeOut=30000, groups={"tck"}) // TODO: remove when time zone loading is faster + public void now() { + LocalDateTime expected = LocalDateTime.now(Clock.systemDefaultZone()); + LocalDateTime test = LocalDateTime.now(); + long diff = Math.abs(test.getTime().toNanoOfDay() - expected.getTime().toNanoOfDay()); + if (diff >= 100000000) { + // may be date change + expected = LocalDateTime.now(Clock.systemDefaultZone()); + test = LocalDateTime.now(); + diff = Math.abs(test.getTime().toNanoOfDay() - expected.getTime().toNanoOfDay()); + } + assertTrue(diff < 100000000); // less than 0.1 secs + } + + //----------------------------------------------------------------------- + // now(ZoneId) + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_ZoneId_nullZoneId() { + LocalDateTime.now((ZoneId) null); + } + + @Test(groups={"tck"}) + public void now_ZoneId() { + ZoneId zone = ZoneId.of("UTC+01:02:03"); + LocalDateTime expected = LocalDateTime.now(Clock.system(zone)); + LocalDateTime test = LocalDateTime.now(zone); + for (int i = 0; i < 100; i++) { + if (expected.equals(test)) { + return; + } + expected = LocalDateTime.now(Clock.system(zone)); + test = LocalDateTime.now(zone); + } + assertEquals(test, expected); + } + + //----------------------------------------------------------------------- + // now(Clock) + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_Clock_nullClock() { + LocalDateTime.now((Clock) null); + } + + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay_utc() { + for (int i = 0; i < (2 * 24 * 60 * 60); i++) { + Instant instant = Instant.ofEpochSecond(i).plusNanos(123456789L); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + LocalDateTime test = LocalDateTime.now(clock); + assertEquals(test.getYear(), 1970); + assertEquals(test.getMonth(), Month.JANUARY); + assertEquals(test.getDayOfMonth(), (i < 24 * 60 * 60 ? 1 : 2)); + assertEquals(test.getHour(), (i / (60 * 60)) % 24); + assertEquals(test.getMinute(), (i / 60) % 60); + assertEquals(test.getSecond(), i % 60); + assertEquals(test.getNano(), 123456789); + } + } + + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay_offset() { + for (int i = 0; i < (2 * 24 * 60 * 60); i++) { + Instant instant = Instant.ofEpochSecond(i).plusNanos(123456789L); + Clock clock = Clock.fixed(instant.minusSeconds(OFFSET_PONE.getTotalSeconds()), OFFSET_PONE); + LocalDateTime test = LocalDateTime.now(clock); + assertEquals(test.getYear(), 1970); + assertEquals(test.getMonth(), Month.JANUARY); + assertEquals(test.getDayOfMonth(), (i < 24 * 60 * 60) ? 1 : 2); + assertEquals(test.getHour(), (i / (60 * 60)) % 24); + assertEquals(test.getMinute(), (i / 60) % 60); + assertEquals(test.getSecond(), i % 60); + assertEquals(test.getNano(), 123456789); + } + } + + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay_beforeEpoch() { + LocalTime expected = LocalTime.MIDNIGHT.plusNanos(123456789L); + for (int i =-1; i >= -(24 * 60 * 60); i--) { + Instant instant = Instant.ofEpochSecond(i).plusNanos(123456789L); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + LocalDateTime test = LocalDateTime.now(clock); + assertEquals(test.getYear(), 1969); + assertEquals(test.getMonth(), Month.DECEMBER); + assertEquals(test.getDayOfMonth(), 31); + expected = expected.minusSeconds(1); + assertEquals(test.getTime(), expected); + } + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now_Clock_maxYear() { + Clock clock = Clock.fixed(MAX_INSTANT, ZoneOffset.UTC); + LocalDateTime test = LocalDateTime.now(clock); + assertEquals(test, MAX_DATE_TIME); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void now_Clock_tooBig() { + Clock clock = Clock.fixed(MAX_INSTANT.plusSeconds(24 * 60 * 60), ZoneOffset.UTC); + LocalDateTime.now(clock); + } + + @Test(groups={"tck"}) + public void now_Clock_minYear() { + Clock clock = Clock.fixed(MIN_INSTANT, ZoneOffset.UTC); + LocalDateTime test = LocalDateTime.now(clock); + assertEquals(test, MIN_DATE_TIME); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void now_Clock_tooLow() { + Clock clock = Clock.fixed(MIN_INSTANT.minusNanos(1), ZoneOffset.UTC); + LocalDateTime.now(clock); + } + + //----------------------------------------------------------------------- + // of() factories + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_4intsMonth() { + LocalDateTime dateTime = LocalDateTime.of(2007, Month.JULY, 15, 12, 30); + check(dateTime, 2007, 7, 15, 12, 30, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_4intsMonth_yearTooLow() { + LocalDateTime.of(Integer.MIN_VALUE, Month.JULY, 15, 12, 30); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_4intsMonth_nullMonth() { + LocalDateTime.of(2007, null, 15, 12, 30); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_4intsMonth_dayTooLow() { + LocalDateTime.of(2007, Month.JULY, -1, 12, 30); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_4intsMonth_dayTooHigh() { + LocalDateTime.of(2007, Month.JULY, 32, 12, 30); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_4intsMonth_hourTooLow() { + LocalDateTime.of(2007, Month.JULY, 15, -1, 30); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_4intsMonth_hourTooHigh() { + LocalDateTime.of(2007, Month.JULY, 15, 24, 30); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_4intsMonth_minuteTooLow() { + LocalDateTime.of(2007, Month.JULY, 15, 12, -1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_4intsMonth_minuteTooHigh() { + LocalDateTime.of(2007, Month.JULY, 15, 12, 60); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_5intsMonth() { + LocalDateTime dateTime = LocalDateTime.of(2007, Month.JULY, 15, 12, 30, 40); + check(dateTime, 2007, 7, 15, 12, 30, 40, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5intsMonth_yearTooLow() { + LocalDateTime.of(Integer.MIN_VALUE, Month.JULY, 15, 12, 30, 40); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_5intsMonth_nullMonth() { + LocalDateTime.of(2007, null, 15, 12, 30, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5intsMonth_dayTooLow() { + LocalDateTime.of(2007, Month.JULY, -1, 12, 30, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5intsMonth_dayTooHigh() { + LocalDateTime.of(2007, Month.JULY, 32, 12, 30, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5intsMonth_hourTooLow() { + LocalDateTime.of(2007, Month.JULY, 15, -1, 30, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5intsMonth_hourTooHigh() { + LocalDateTime.of(2007, Month.JULY, 15, 24, 30, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5intsMonth_minuteTooLow() { + LocalDateTime.of(2007, Month.JULY, 15, 12, -1, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5intsMonth_minuteTooHigh() { + LocalDateTime.of(2007, Month.JULY, 15, 12, 60, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5intsMonth_secondTooLow() { + LocalDateTime.of(2007, Month.JULY, 15, 12, 30, -1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5intsMonth_secondTooHigh() { + LocalDateTime.of(2007, Month.JULY, 15, 12, 30, 60); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_6intsMonth() { + LocalDateTime dateTime = LocalDateTime.of(2007, Month.JULY, 15, 12, 30, 40, 987654321); + check(dateTime, 2007, 7, 15, 12, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6intsMonth_yearTooLow() { + LocalDateTime.of(Integer.MIN_VALUE, Month.JULY, 15, 12, 30, 40, 987654321); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_6intsMonth_nullMonth() { + LocalDateTime.of(2007, null, 15, 12, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6intsMonth_dayTooLow() { + LocalDateTime.of(2007, Month.JULY, -1, 12, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6intsMonth_dayTooHigh() { + LocalDateTime.of(2007, Month.JULY, 32, 12, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6intsMonth_hourTooLow() { + LocalDateTime.of(2007, Month.JULY, 15, -1, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6intsMonth_hourTooHigh() { + LocalDateTime.of(2007, Month.JULY, 15, 24, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6intsMonth_minuteTooLow() { + LocalDateTime.of(2007, Month.JULY, 15, 12, -1, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6intsMonth_minuteTooHigh() { + LocalDateTime.of(2007, Month.JULY, 15, 12, 60, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6intsMonth_secondTooLow() { + LocalDateTime.of(2007, Month.JULY, 15, 12, 30, -1, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6intsMonth_secondTooHigh() { + LocalDateTime.of(2007, Month.JULY, 15, 12, 30, 60, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6intsMonth_nanoTooLow() { + LocalDateTime.of(2007, Month.JULY, 15, 12, 30, 40, -1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6intsMonth_nanoTooHigh() { + LocalDateTime.of(2007, Month.JULY, 15, 12, 30, 40, 1000000000); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_5ints() { + LocalDateTime dateTime = LocalDateTime.of(2007, 7, 15, 12, 30); + check(dateTime, 2007, 7, 15, 12, 30, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5ints_yearTooLow() { + LocalDateTime.of(Integer.MIN_VALUE, 7, 15, 12, 30); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5ints_monthTooLow() { + LocalDateTime.of(2007, 0, 15, 12, 30); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5ints_monthTooHigh() { + LocalDateTime.of(2007, 13, 15, 12, 30); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5ints_dayTooLow() { + LocalDateTime.of(2007, 7, -1, 12, 30); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5ints_dayTooHigh() { + LocalDateTime.of(2007, 7, 32, 12, 30); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5ints_hourTooLow() { + LocalDateTime.of(2007, 7, 15, -1, 30); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5ints_hourTooHigh() { + LocalDateTime.of(2007, 7, 15, 24, 30); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5ints_minuteTooLow() { + LocalDateTime.of(2007, 7, 15, 12, -1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_5ints_minuteTooHigh() { + LocalDateTime.of(2007, 7, 15, 12, 60); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_6ints() { + LocalDateTime dateTime = LocalDateTime.of(2007, 7, 15, 12, 30, 40); + check(dateTime, 2007, 7, 15, 12, 30, 40, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6ints_yearTooLow() { + LocalDateTime.of(Integer.MIN_VALUE, 7, 15, 12, 30, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6ints_monthTooLow() { + LocalDateTime.of(2007, 0, 15, 12, 30, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6ints_monthTooHigh() { + LocalDateTime.of(2007, 13, 15, 12, 30, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6ints_dayTooLow() { + LocalDateTime.of(2007, 7, -1, 12, 30, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6ints_dayTooHigh() { + LocalDateTime.of(2007, 7, 32, 12, 30, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6ints_hourTooLow() { + LocalDateTime.of(2007, 7, 15, -1, 30, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6ints_hourTooHigh() { + LocalDateTime.of(2007, 7, 15, 24, 30, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6ints_minuteTooLow() { + LocalDateTime.of(2007, 7, 15, 12, -1, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6ints_minuteTooHigh() { + LocalDateTime.of(2007, 7, 15, 12, 60, 40); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6ints_secondTooLow() { + LocalDateTime.of(2007, 7, 15, 12, 30, -1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_6ints_secondTooHigh() { + LocalDateTime.of(2007, 7, 15, 12, 30, 60); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_7ints() { + LocalDateTime dateTime = LocalDateTime.of(2007, 7, 15, 12, 30, 40, 987654321); + check(dateTime, 2007, 7, 15, 12, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_7ints_yearTooLow() { + LocalDateTime.of(Integer.MIN_VALUE, 7, 15, 12, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_7ints_monthTooLow() { + LocalDateTime.of(2007, 0, 15, 12, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_7ints_monthTooHigh() { + LocalDateTime.of(2007, 13, 15, 12, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_7ints_dayTooLow() { + LocalDateTime.of(2007, 7, -1, 12, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_7ints_dayTooHigh() { + LocalDateTime.of(2007, 7, 32, 12, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_7ints_hourTooLow() { + LocalDateTime.of(2007, 7, 15, -1, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_7ints_hourTooHigh() { + LocalDateTime.of(2007, 7, 15, 24, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_7ints_minuteTooLow() { + LocalDateTime.of(2007, 7, 15, 12, -1, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_7ints_minuteTooHigh() { + LocalDateTime.of(2007, 7, 15, 12, 60, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_7ints_secondTooLow() { + LocalDateTime.of(2007, 7, 15, 12, 30, -1, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_7ints_secondTooHigh() { + LocalDateTime.of(2007, 7, 15, 12, 30, 60, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_7ints_nanoTooLow() { + LocalDateTime.of(2007, 7, 15, 12, 30, 40, -1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_7ints_nanoTooHigh() { + LocalDateTime.of(2007, 7, 15, 12, 30, 40, 1000000000); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_LocalDate_LocalTime() { + LocalDateTime dateTime = LocalDateTime.of(LocalDate.of(2007, 7, 15), LocalTime.of(12, 30, 40, 987654321)); + check(dateTime, 2007, 7, 15, 12, 30, 40, 987654321); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_LocalDate_LocalTime_nullLocalDate() { + LocalDateTime.of(null, LocalTime.of(12, 30, 40, 987654321)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_LocalDate_LocalTime_nullLocalTime() { + LocalDateTime.of(LocalDate.of(2007, 7, 15), null); + } + + //----------------------------------------------------------------------- + // ofInstant() + //----------------------------------------------------------------------- + @DataProvider(name="instantFactory") + Object[][] data_instantFactory() { + return new Object[][] { + {Instant.ofEpochSecond(86400 + 3600 + 120 + 4, 500), ZONE_PARIS, LocalDateTime.of(1970, 1, 2, 2, 2, 4, 500)}, + {Instant.ofEpochSecond(86400 + 3600 + 120 + 4, 500), OFFSET_MTWO, LocalDateTime.of(1970, 1, 1, 23, 2, 4, 500)}, + {Instant.ofEpochSecond(-86400 + 4, 500), OFFSET_PTWO, LocalDateTime.of(1969, 12, 31, 2, 0, 4, 500)}, + {OffsetDateTime.of(LocalDateTime.of(Year.MIN_VALUE, 1, 1, 0, 0), ZoneOffset.UTC).toInstant(), + ZoneOffset.UTC, LocalDateTime.MIN}, + {OffsetDateTime.of(LocalDateTime.of(Year.MAX_VALUE, 12, 31, 23, 59, 59, 999_999_999), ZoneOffset.UTC).toInstant(), + ZoneOffset.UTC, LocalDateTime.MAX}, + }; + } + + @Test(dataProvider="instantFactory") + public void factory_ofInstant(Instant instant, ZoneId zone, LocalDateTime expected) { + LocalDateTime test = LocalDateTime.ofInstant(instant, zone); + assertEquals(test, expected); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofInstant_instantTooBig() { + LocalDateTime.ofInstant(Instant.MAX, OFFSET_PONE) ; + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofInstant_instantTooSmall() { + LocalDateTime.ofInstant(Instant.MIN, OFFSET_PONE) ; + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_ofInstant_nullInstant() { + LocalDateTime.ofInstant((Instant) null, ZONE_GAZA); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_ofInstant_nullZone() { + LocalDateTime.ofInstant(Instant.EPOCH, (ZoneId) null); + } + + //----------------------------------------------------------------------- + // ofEpochSecond() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_ofEpochSecond_longOffset_afterEpoch() { + LocalDateTime base = LocalDateTime.of(1970, 1, 1, 2, 0, 0, 500); + for (int i = 0; i < 100000; i++) { + LocalDateTime test = LocalDateTime.ofEpochSecond(i, 500, OFFSET_PTWO); + assertEquals(test, base.plusSeconds(i)); + } + } + + @Test(groups={"tck"}) + public void factory_ofEpochSecond_longOffset_beforeEpoch() { + LocalDateTime base = LocalDateTime.of(1970, 1, 1, 2, 0, 0, 500); + for (int i = 0; i < 100000; i++) { + LocalDateTime test = LocalDateTime.ofEpochSecond(-i, 500, OFFSET_PTWO); + assertEquals(test, base.minusSeconds(i)); + } + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofEpochSecond_longOffset_tooBig() { + LocalDateTime.ofEpochSecond(Long.MAX_VALUE, 500, OFFSET_PONE); // TODO: better test + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofEpochSecond_longOffset_tooSmall() { + LocalDateTime.ofEpochSecond(Long.MIN_VALUE, 500, OFFSET_PONE); // TODO: better test + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofEpochSecond_badNanos_toBig() { + LocalDateTime.ofEpochSecond(0, 1_000_000_000, OFFSET_PONE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofEpochSecond_badNanos_toSmall() { + LocalDateTime.ofEpochSecond(0, -1, OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_ofEpochSecond_longOffset_nullOffset() { + LocalDateTime.ofEpochSecond(0L, 500, null); + } + + //----------------------------------------------------------------------- + // from() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_from_TemporalAccessor() { + LocalDateTime base = LocalDateTime.of(2007, 7, 15, 17, 30); + assertEquals(LocalDateTime.from(base), base); + assertEquals(LocalDateTime.from(ZonedDateTime.of(base, ZoneOffset.ofHours(2))), base); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_from_TemporalAccessor_invalid_noDerive() { + LocalDateTime.from(LocalTime.of(12, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_from_TemporalAccessor_null() { + LocalDateTime.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // parse() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleToString", groups={"tck"}) + public void test_parse(int y, int month, int d, int h, int m, int s, int n, String text) { + LocalDateTime t = LocalDateTime.parse(text); + assertEquals(t.getYear(), y); + assertEquals(t.getMonth().getValue(), month); + assertEquals(t.getDayOfMonth(), d); + assertEquals(t.getHour(), h); + assertEquals(t.getMinute(), m); + assertEquals(t.getSecond(), s); + assertEquals(t.getNano(), n); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_illegalValue() { + LocalDateTime.parse("2008-06-32T11:15"); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_invalidValue() { + LocalDateTime.parse("2008-06-31T11:15"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_nullText() { + LocalDateTime.parse((String) null); + } + + //----------------------------------------------------------------------- + // parse(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_parse_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d H m s"); + LocalDateTime test = LocalDateTime.parse("2010 12 3 11 30 45", f); + assertEquals(test, LocalDateTime.of(2010, 12, 3, 11, 30, 45)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullText() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d H m s"); + LocalDateTime.parse((String) null, f); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullFormatter() { + LocalDateTime.parse("ANY", null); + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @Test + public void test_get_TemporalField() { + LocalDateTime test = LocalDateTime.of(2008, 6, 30, 12, 30, 40, 987654321); + assertEquals(test.get(ChronoField.YEAR), 2008); + assertEquals(test.get(ChronoField.MONTH_OF_YEAR), 6); + assertEquals(test.get(ChronoField.DAY_OF_MONTH), 30); + assertEquals(test.get(ChronoField.DAY_OF_WEEK), 1); + assertEquals(test.get(ChronoField.DAY_OF_YEAR), 182); + + assertEquals(test.get(ChronoField.HOUR_OF_DAY), 12); + assertEquals(test.get(ChronoField.MINUTE_OF_HOUR), 30); + assertEquals(test.get(ChronoField.SECOND_OF_MINUTE), 40); + assertEquals(test.get(ChronoField.NANO_OF_SECOND), 987654321); + assertEquals(test.get(ChronoField.HOUR_OF_AMPM), 0); + assertEquals(test.get(ChronoField.AMPM_OF_DAY), 1); + } + + @Test + public void test_getLong_TemporalField() { + LocalDateTime test = LocalDateTime.of(2008, 6, 30, 12, 30, 40, 987654321); + assertEquals(test.getLong(ChronoField.YEAR), 2008); + assertEquals(test.getLong(ChronoField.MONTH_OF_YEAR), 6); + assertEquals(test.getLong(ChronoField.DAY_OF_MONTH), 30); + assertEquals(test.getLong(ChronoField.DAY_OF_WEEK), 1); + assertEquals(test.getLong(ChronoField.DAY_OF_YEAR), 182); + + assertEquals(test.getLong(ChronoField.HOUR_OF_DAY), 12); + assertEquals(test.getLong(ChronoField.MINUTE_OF_HOUR), 30); + assertEquals(test.getLong(ChronoField.SECOND_OF_MINUTE), 40); + assertEquals(test.getLong(ChronoField.NANO_OF_SECOND), 987654321); + assertEquals(test.getLong(ChronoField.HOUR_OF_AMPM), 0); + assertEquals(test.getLong(ChronoField.AMPM_OF_DAY), 1); + } + + //----------------------------------------------------------------------- + // query(TemporalQuery) + //----------------------------------------------------------------------- + @Test + public void test_query_chrono() { + assertEquals(TEST_2007_07_15_12_30_40_987654321.query(Queries.chrono()), ISOChrono.INSTANCE); + assertEquals(Queries.chrono().queryFrom(TEST_2007_07_15_12_30_40_987654321), ISOChrono.INSTANCE); + } + + @Test + public void test_query_zoneId() { + assertEquals(TEST_2007_07_15_12_30_40_987654321.query(Queries.zoneId()), null); + assertEquals(Queries.zoneId().queryFrom(TEST_2007_07_15_12_30_40_987654321), null); + } + + @Test + public void test_query_precision() { + assertEquals(TEST_2007_07_15_12_30_40_987654321.query(Queries.precision()), NANOS); + assertEquals(Queries.precision().queryFrom(TEST_2007_07_15_12_30_40_987654321), NANOS); + } + + @Test + public void test_query_offset() { + assertEquals(TEST_2007_07_15_12_30_40_987654321.query(Queries.offset()), null); + assertEquals(Queries.offset().queryFrom(TEST_2007_07_15_12_30_40_987654321), null); + } + + @Test + public void test_query_zone() { + assertEquals(TEST_2007_07_15_12_30_40_987654321.query(Queries.zone()), null); + assertEquals(Queries.zone().queryFrom(TEST_2007_07_15_12_30_40_987654321), null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_query_null() { + TEST_2007_07_15_12_30_40_987654321.query(null); + } + + //----------------------------------------------------------------------- + @DataProvider(name="sampleDates") + Object[][] provider_sampleDates() { + return new Object[][] { + {2008, 7, 5}, + {2007, 7, 5}, + {2006, 7, 5}, + {2005, 7, 5}, + {2004, 1, 1}, + {-1, 1, 2}, + }; + } + + @DataProvider(name="sampleTimes") + Object[][] provider_sampleTimes() { + return new Object[][] { + {0, 0, 0, 0}, + {0, 0, 0, 1}, + {0, 0, 1, 0}, + {0, 0, 1, 1}, + {0, 1, 0, 0}, + {0, 1, 0, 1}, + {0, 1, 1, 0}, + {0, 1, 1, 1}, + {1, 0, 0, 0}, + {1, 0, 0, 1}, + {1, 0, 1, 0}, + {1, 0, 1, 1}, + {1, 1, 0, 0}, + {1, 1, 0, 1}, + {1, 1, 1, 0}, + {1, 1, 1, 1}, + }; + } + + //----------------------------------------------------------------------- + // get*() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_get_dates(int y, int m, int d) { + LocalDateTime a = LocalDateTime.of(y, m, d, 12, 30); + assertEquals(a.getYear(), y); + assertEquals(a.getMonth(), Month.of(m)); + assertEquals(a.getDayOfMonth(), d); + } + + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_getDOY(int y, int m, int d) { + LocalDateTime a = LocalDateTime.of(y, m, d, 12 ,30); + int total = 0; + for (int i = 1; i < m; i++) { + total += Month.of(i).length(isIsoLeap(y)); + } + int doy = total + d; + assertEquals(a.getDayOfYear(), doy); + } + + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_get_times(int h, int m, int s, int ns) { + LocalDateTime a = LocalDateTime.of(TEST_2007_07_15_12_30_40_987654321.getDate(), LocalTime.of(h, m, s, ns)); + assertEquals(a.getHour(), h); + assertEquals(a.getMinute(), m); + assertEquals(a.getSecond(), s); + assertEquals(a.getNano(), ns); + } + + //----------------------------------------------------------------------- + // getDayOfWeek() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_getDayOfWeek() { + DayOfWeek dow = DayOfWeek.MONDAY; + for (Month month : Month.values()) { + int length = month.length(false); + for (int i = 1; i <= length; i++) { + LocalDateTime d = LocalDateTime.of(LocalDate.of(2007, month, i), + TEST_2007_07_15_12_30_40_987654321.getTime()); + assertSame(d.getDayOfWeek(), dow); + dow = dow.plus(1); + } + } + } + + //----------------------------------------------------------------------- + // with() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_with_adjustment() { + final LocalDateTime sample = LocalDateTime.of(2012, 3, 4, 23, 5); + TemporalAdjuster adjuster = new TemporalAdjuster() { + @Override + public Temporal adjustInto(Temporal dateTime) { + return sample; + } + }; + assertEquals(TEST_2007_07_15_12_30_40_987654321.with(adjuster), sample); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_with_adjustment_null() { + TEST_2007_07_15_12_30_40_987654321.with((TemporalAdjuster) null); + } + + //----------------------------------------------------------------------- + // withYear() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withYear_int_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.withYear(2008); + check(t, 2008, 7, 15, 12, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withYear_int_invalid() { + TEST_2007_07_15_12_30_40_987654321.withYear(Year.MIN_VALUE - 1); + } + + @Test(groups={"tck"}) + public void test_withYear_int_adjustDay() { + LocalDateTime t = LocalDateTime.of(2008, 2, 29, 12, 30).withYear(2007); + LocalDateTime expected = LocalDateTime.of(2007, 2, 28, 12, 30); + assertEquals(t, expected); + } + + //----------------------------------------------------------------------- + // withMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withMonth_int_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.withMonth(1); + check(t, 2007, 1, 15, 12, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withMonth_int_invalid() { + TEST_2007_07_15_12_30_40_987654321.withMonth(13); + } + + @Test(groups={"tck"}) + public void test_withMonth_int_adjustDay() { + LocalDateTime t = LocalDateTime.of(2007, 12, 31, 12, 30).withMonth(11); + LocalDateTime expected = LocalDateTime.of(2007, 11, 30, 12, 30); + assertEquals(t, expected); + } + + //----------------------------------------------------------------------- + // withDayOfMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withDayOfMonth_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.withDayOfMonth(1); + check(t, 2007, 7, 1, 12, 30, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfMonth_invalid() { + LocalDateTime.of(2007, 11, 30, 12, 30).withDayOfMonth(32); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfMonth_invalidCombination() { + LocalDateTime.of(2007, 11, 30, 12, 30).withDayOfMonth(31); + } + + //----------------------------------------------------------------------- + // withDayOfYear(int) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withDayOfYear_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.withDayOfYear(33); + assertEquals(t, LocalDateTime.of(2007, 2, 2, 12, 30, 40, 987654321)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfYear_illegal() { + TEST_2007_07_15_12_30_40_987654321.withDayOfYear(367); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfYear_invalid() { + TEST_2007_07_15_12_30_40_987654321.withDayOfYear(366); + } + + //----------------------------------------------------------------------- + // withHour() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withHour_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321; + for (int i = 0; i < 24; i++) { + t = t.withHour(i); + assertEquals(t.getHour(), i); + } + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withHour_hourTooLow() { + TEST_2007_07_15_12_30_40_987654321.withHour(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withHour_hourTooHigh() { + TEST_2007_07_15_12_30_40_987654321.withHour(24); + } + + //----------------------------------------------------------------------- + // withMinute() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withMinute_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321; + for (int i = 0; i < 60; i++) { + t = t.withMinute(i); + assertEquals(t.getMinute(), i); + } + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withMinute_minuteTooLow() { + TEST_2007_07_15_12_30_40_987654321.withMinute(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withMinute_minuteTooHigh() { + TEST_2007_07_15_12_30_40_987654321.withMinute(60); + } + + //----------------------------------------------------------------------- + // withSecond() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withSecond_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321; + for (int i = 0; i < 60; i++) { + t = t.withSecond(i); + assertEquals(t.getSecond(), i); + } + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withSecond_secondTooLow() { + TEST_2007_07_15_12_30_40_987654321.withSecond(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withSecond_secondTooHigh() { + TEST_2007_07_15_12_30_40_987654321.withSecond(60); + } + + //----------------------------------------------------------------------- + // withNano() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withNanoOfSecond_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321; + t = t.withNano(1); + assertEquals(t.getNano(), 1); + t = t.withNano(10); + assertEquals(t.getNano(), 10); + t = t.withNano(100); + assertEquals(t.getNano(), 100); + t = t.withNano(999999999); + assertEquals(t.getNano(), 999999999); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withNanoOfSecond_nanoTooLow() { + TEST_2007_07_15_12_30_40_987654321.withNano(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withNanoOfSecond_nanoTooHigh() { + TEST_2007_07_15_12_30_40_987654321.withNano(1000000000); + } + + //----------------------------------------------------------------------- + // truncatedTo(TemporalUnit) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_truncatedTo_normal() { + assertEquals(TEST_2007_07_15_12_30_40_987654321.truncatedTo(NANOS), TEST_2007_07_15_12_30_40_987654321); + assertEquals(TEST_2007_07_15_12_30_40_987654321.truncatedTo(SECONDS), TEST_2007_07_15_12_30_40_987654321.withNano(0)); + assertEquals(TEST_2007_07_15_12_30_40_987654321.truncatedTo(DAYS), TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_truncatedTo_null() { + TEST_2007_07_15_12_30_40_987654321.truncatedTo(null); + } + + //----------------------------------------------------------------------- + // plus(adjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plus_adjuster() { + Period p = Period.ofTime(0, 0, 62, 3); + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plus(p); + assertEquals(t, LocalDateTime.of(2007, 7, 15, 12, 31, 42, 987654324)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_plus_adjuster_null() { + TEST_2007_07_15_12_30_40_987654321.plus((TemporalAdder) null); + } + + //----------------------------------------------------------------------- + // plus(Period) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plus_Period_positiveMonths() { + MockSimplePeriod period = MockSimplePeriod.of(7, ChronoUnit.MONTHS); + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plus(period); + assertEquals(t, LocalDateTime.of(2008, 2, 15, 12, 30, 40, 987654321)); + } + + @Test(groups={"tck"}) + public void test_plus_Period_negativeDays() { + MockSimplePeriod period = MockSimplePeriod.of(-25, ChronoUnit.DAYS); + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plus(period); + assertEquals(t, LocalDateTime.of(2007, 6, 20, 12, 30, 40, 987654321)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_plus_Period_null() { + TEST_2007_07_15_12_30_40_987654321.plus((MockSimplePeriod) null); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plus_Period_invalidTooLarge() { + MockSimplePeriod period = MockSimplePeriod.of(1, ChronoUnit.YEARS); + LocalDateTime.of(Year.MAX_VALUE, 1, 1, 0, 0).plus(period); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plus_Period_invalidTooSmall() { + MockSimplePeriod period = MockSimplePeriod.of(-1, ChronoUnit.YEARS); + LocalDateTime.of(Year.MIN_VALUE, 1, 1, 0, 0).plus(period); + } + + //----------------------------------------------------------------------- + // plus(long,TemporalUnit) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plus_longTemporalUnit_positiveMonths() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plus(7, ChronoUnit.MONTHS); + assertEquals(t, LocalDateTime.of(2008, 2, 15, 12, 30, 40, 987654321)); + } + + @Test(groups={"tck"}) + public void test_plus_longTemporalUnit_negativeDays() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plus(-25, ChronoUnit.DAYS); + assertEquals(t, LocalDateTime.of(2007, 6, 20, 12, 30, 40, 987654321)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_plus_longTemporalUnit_null() { + TEST_2007_07_15_12_30_40_987654321.plus(1, (TemporalUnit) null); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plus_longTemporalUnit_invalidTooLarge() { + LocalDateTime.of(Year.MAX_VALUE, 1, 1, 0, 0).plus(1, ChronoUnit.YEARS); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plus_longTemporalUnit_invalidTooSmall() { + LocalDateTime.of(Year.MIN_VALUE, 1, 1, 0, 0).plus(-1, ChronoUnit.YEARS); + } + + //----------------------------------------------------------------------- + // plusYears() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusYears_int_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusYears(1); + check(t, 2008, 7, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusYears_int_negative() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusYears(-1); + check(t, 2006, 7, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusYears_int_adjustDay() { + LocalDateTime t = createDateMidnight(2008, 2, 29).plusYears(1); + check(t, 2009, 2, 28, 0, 0, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_int_invalidTooLarge() { + createDateMidnight(Year.MAX_VALUE, 1, 1).plusYears(1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_int_invalidTooSmall() { + LocalDate.of(Year.MIN_VALUE, 1, 1).plusYears(-1); + } + + //----------------------------------------------------------------------- + // plusMonths() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusMonths_int_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusMonths(1); + check(t, 2007, 8, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusMonths_int_overYears() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusMonths(25); + check(t, 2009, 8, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusMonths_int_negative() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusMonths(-1); + check(t, 2007, 6, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusMonths_int_negativeAcrossYear() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusMonths(-7); + check(t, 2006, 12, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusMonths_int_negativeOverYears() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusMonths(-31); + check(t, 2004, 12, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusMonths_int_adjustDayFromLeapYear() { + LocalDateTime t = createDateMidnight(2008, 2, 29).plusMonths(12); + check(t, 2009, 2, 28, 0, 0, 0, 0); + } + + @Test(groups={"tck"}) + public void test_plusMonths_int_adjustDayFromMonthLength() { + LocalDateTime t = createDateMidnight(2007, 3, 31).plusMonths(1); + check(t, 2007, 4, 30, 0, 0, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusMonths_int_invalidTooLarge() { + createDateMidnight(Year.MAX_VALUE, 12, 1).plusMonths(1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusMonths_int_invalidTooSmall() { + createDateMidnight(Year.MIN_VALUE, 1, 1).plusMonths(-1); + } + + //----------------------------------------------------------------------- + // plusWeeks() + //----------------------------------------------------------------------- + @DataProvider(name="samplePlusWeeksSymmetry") + Object[][] provider_samplePlusWeeksSymmetry() { + return new Object[][] { + {createDateMidnight(-1, 1, 1)}, + {createDateMidnight(-1, 2, 28)}, + {createDateMidnight(-1, 3, 1)}, + {createDateMidnight(-1, 12, 31)}, + {createDateMidnight(0, 1, 1)}, + {createDateMidnight(0, 2, 28)}, + {createDateMidnight(0, 2, 29)}, + {createDateMidnight(0, 3, 1)}, + {createDateMidnight(0, 12, 31)}, + {createDateMidnight(2007, 1, 1)}, + {createDateMidnight(2007, 2, 28)}, + {createDateMidnight(2007, 3, 1)}, + {createDateMidnight(2007, 12, 31)}, + {createDateMidnight(2008, 1, 1)}, + {createDateMidnight(2008, 2, 28)}, + {createDateMidnight(2008, 2, 29)}, + {createDateMidnight(2008, 3, 1)}, + {createDateMidnight(2008, 12, 31)}, + {createDateMidnight(2099, 1, 1)}, + {createDateMidnight(2099, 2, 28)}, + {createDateMidnight(2099, 3, 1)}, + {createDateMidnight(2099, 12, 31)}, + {createDateMidnight(2100, 1, 1)}, + {createDateMidnight(2100, 2, 28)}, + {createDateMidnight(2100, 3, 1)}, + {createDateMidnight(2100, 12, 31)}, + }; + } + + @Test(dataProvider="samplePlusWeeksSymmetry", groups={"tck"}) + public void test_plusWeeks_symmetry(LocalDateTime reference) { + for (int weeks = 0; weeks < 365 * 8; weeks++) { + LocalDateTime t = reference.plusWeeks(weeks).plusWeeks(-weeks); + assertEquals(t, reference); + + t = reference.plusWeeks(-weeks).plusWeeks(weeks); + assertEquals(t, reference); + } + } + + @Test(groups={"tck"}) + public void test_plusWeeks_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusWeeks(1); + check(t, 2007, 7, 22, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_overMonths() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusWeeks(9); + check(t, 2007, 9, 16, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_overYears() { + LocalDateTime t = LocalDateTime.of(2006, 7, 16, 12, 30, 40, 987654321).plusWeeks(52); + assertEquals(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_overLeapYears() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusYears(-1).plusWeeks(104); + check(t, 2008, 7, 12, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_negative() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusWeeks(-1); + check(t, 2007, 7, 8, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_negativeAcrossYear() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusWeeks(-28); + check(t, 2006, 12, 31, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_negativeOverYears() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusWeeks(-104); + check(t, 2005, 7, 17, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_maximum() { + LocalDateTime t = createDateMidnight(Year.MAX_VALUE, 12, 24).plusWeeks(1); + check(t, Year.MAX_VALUE, 12, 31, 0, 0, 0, 0); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_minimum() { + LocalDateTime t = createDateMidnight(Year.MIN_VALUE, 1, 8).plusWeeks(-1); + check(t, Year.MIN_VALUE, 1, 1, 0, 0, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusWeeks_invalidTooLarge() { + createDateMidnight(Year.MAX_VALUE, 12, 25).plusWeeks(1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusWeeks_invalidTooSmall() { + createDateMidnight(Year.MIN_VALUE, 1, 7).plusWeeks(-1); + } + + //----------------------------------------------------------------------- + // plusDays() + //----------------------------------------------------------------------- + @DataProvider(name="samplePlusDaysSymmetry") + Object[][] provider_samplePlusDaysSymmetry() { + return new Object[][] { + {createDateMidnight(-1, 1, 1)}, + {createDateMidnight(-1, 2, 28)}, + {createDateMidnight(-1, 3, 1)}, + {createDateMidnight(-1, 12, 31)}, + {createDateMidnight(0, 1, 1)}, + {createDateMidnight(0, 2, 28)}, + {createDateMidnight(0, 2, 29)}, + {createDateMidnight(0, 3, 1)}, + {createDateMidnight(0, 12, 31)}, + {createDateMidnight(2007, 1, 1)}, + {createDateMidnight(2007, 2, 28)}, + {createDateMidnight(2007, 3, 1)}, + {createDateMidnight(2007, 12, 31)}, + {createDateMidnight(2008, 1, 1)}, + {createDateMidnight(2008, 2, 28)}, + {createDateMidnight(2008, 2, 29)}, + {createDateMidnight(2008, 3, 1)}, + {createDateMidnight(2008, 12, 31)}, + {createDateMidnight(2099, 1, 1)}, + {createDateMidnight(2099, 2, 28)}, + {createDateMidnight(2099, 3, 1)}, + {createDateMidnight(2099, 12, 31)}, + {createDateMidnight(2100, 1, 1)}, + {createDateMidnight(2100, 2, 28)}, + {createDateMidnight(2100, 3, 1)}, + {createDateMidnight(2100, 12, 31)}, + }; + } + + @Test(dataProvider="samplePlusDaysSymmetry", groups={"tck"}) + public void test_plusDays_symmetry(LocalDateTime reference) { + for (int days = 0; days < 365 * 8; days++) { + LocalDateTime t = reference.plusDays(days).plusDays(-days); + assertEquals(t, reference); + + t = reference.plusDays(-days).plusDays(days); + assertEquals(t, reference); + } + } + + @Test(groups={"tck"}) + public void test_plusDays_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusDays(1); + check(t, 2007, 7, 16, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusDays_overMonths() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusDays(62); + check(t, 2007, 9, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusDays_overYears() { + LocalDateTime t = LocalDateTime.of(2006, 7, 14, 12, 30, 40, 987654321).plusDays(366); + assertEquals(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_plusDays_overLeapYears() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusYears(-1).plusDays(365 + 366); + check(t, 2008, 7, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusDays_negative() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusDays(-1); + check(t, 2007, 7, 14, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusDays_negativeAcrossYear() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusDays(-196); + check(t, 2006, 12, 31, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusDays_negativeOverYears() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusDays(-730); + check(t, 2005, 7, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_plusDays_maximum() { + LocalDateTime t = createDateMidnight(Year.MAX_VALUE, 12, 30).plusDays(1); + check(t, Year.MAX_VALUE, 12, 31, 0, 0, 0, 0); + } + + @Test(groups={"tck"}) + public void test_plusDays_minimum() { + LocalDateTime t = createDateMidnight(Year.MIN_VALUE, 1, 2).plusDays(-1); + check(t, Year.MIN_VALUE, 1, 1, 0, 0, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusDays_invalidTooLarge() { + createDateMidnight(Year.MAX_VALUE, 12, 31).plusDays(1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusDays_invalidTooSmall() { + createDateMidnight(Year.MIN_VALUE, 1, 1).plusDays(-1); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_plusDays_overflowTooLarge() { + createDateMidnight(Year.MAX_VALUE, 12, 31).plusDays(Long.MAX_VALUE); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_plusDays_overflowTooSmall() { + createDateMidnight(Year.MIN_VALUE, 1, 1).plusDays(Long.MIN_VALUE); + } + + //----------------------------------------------------------------------- + // plusHours() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusHours_one() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDate d = t.getDate(); + + for (int i = 0; i < 50; i++) { + t = t.plusHours(1); + + if ((i + 1) % 24 == 0) { + d = d.plusDays(1); + } + + assertEquals(t.getDate(), d); + assertEquals(t.getHour(), (i + 1) % 24); + } + } + + @Test(groups={"tck"}) + public void test_plusHours_fromZero() { + LocalDateTime base = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDate d = base.getDate().minusDays(3); + LocalTime t = LocalTime.of(21, 0); + + for (int i = -50; i < 50; i++) { + LocalDateTime dt = base.plusHours(i); + t = t.plusHours(1); + + if (t.getHour() == 0) { + d = d.plusDays(1); + } + + assertEquals(dt.getDate(), d); + assertEquals(dt.getTime(), t); + } + } + + @Test(groups={"tck"}) + public void test_plusHours_fromOne() { + LocalDateTime base = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(1, 0)); + LocalDate d = base.getDate().minusDays(3); + LocalTime t = LocalTime.of(22, 0); + + for (int i = -50; i < 50; i++) { + LocalDateTime dt = base.plusHours(i); + + t = t.plusHours(1); + + if (t.getHour() == 0) { + d = d.plusDays(1); + } + + assertEquals(dt.getDate(), d); + assertEquals(dt.getTime(), t); + } + } + + //----------------------------------------------------------------------- + // plusMinutes() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusMinutes_one() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDate d = t.getDate(); + + int hour = 0; + int min = 0; + + for (int i = 0; i < 70; i++) { + t = t.plusMinutes(1); + min++; + if (min == 60) { + hour++; + min = 0; + } + + assertEquals(t.getDate(), d); + assertEquals(t.getHour(), hour); + assertEquals(t.getMinute(), min); + } + } + + @Test(groups={"tck"}) + public void test_plusMinutes_fromZero() { + LocalDateTime base = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDate d = base.getDate().minusDays(1); + LocalTime t = LocalTime.of(22, 49); + + for (int i = -70; i < 70; i++) { + LocalDateTime dt = base.plusMinutes(i); + t = t.plusMinutes(1); + + if (t == LocalTime.MIDNIGHT) { + d = d.plusDays(1); + } + + assertEquals(dt.getDate(), d, String.valueOf(i)); + assertEquals(dt.getTime(), t, String.valueOf(i)); + } + } + + @Test(groups={"tck"}) + public void test_plusMinutes_noChange_oneDay() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusMinutes(24 * 60); + assertEquals(t.getDate(), TEST_2007_07_15_12_30_40_987654321.getDate().plusDays(1)); + } + + //----------------------------------------------------------------------- + // plusSeconds() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusSeconds_one() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDate d = t.getDate(); + + int hour = 0; + int min = 0; + int sec = 0; + + for (int i = 0; i < 3700; i++) { + t = t.plusSeconds(1); + sec++; + if (sec == 60) { + min++; + sec = 0; + } + if (min == 60) { + hour++; + min = 0; + } + + assertEquals(t.getDate(), d); + assertEquals(t.getHour(), hour); + assertEquals(t.getMinute(), min); + assertEquals(t.getSecond(), sec); + } + } + + @DataProvider(name="plusSeconds_fromZero") + Iterator plusSeconds_fromZero() { + return new Iterator() { + int delta = 30; + + int i = -3660; + LocalDate date = TEST_2007_07_15_12_30_40_987654321.getDate().minusDays(1); + int hour = 22; + int min = 59; + int sec = 0; + + public boolean hasNext() { + return i <= 3660; + } + + public Object[] next() { + final Object[] ret = new Object[] {i, date, hour, min, sec}; + i += delta; + sec += delta; + + if (sec >= 60) { + min++; + sec -= 60; + + if (min == 60) { + hour++; + min = 0; + + if (hour == 24) { + hour = 0; + } + } + } + + if (i == 0) { + date = date.plusDays(1); + } + + return ret; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Test(dataProvider="plusSeconds_fromZero", groups={"tck"}) + public void test_plusSeconds_fromZero(int seconds, LocalDate date, int hour, int min, int sec) { + LocalDateTime base = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDateTime t = base.plusSeconds(seconds); + + assertEquals(date, t.getDate()); + assertEquals(hour, t.getHour()); + assertEquals(min, t.getMinute()); + assertEquals(sec, t.getSecond()); + } + + @Test(groups={"tck"}) + public void test_plusSeconds_noChange_oneDay() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusSeconds(24 * 60 * 60); + assertEquals(t.getDate(), TEST_2007_07_15_12_30_40_987654321.getDate().plusDays(1)); + } + + //----------------------------------------------------------------------- + // plusNanos() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusNanos_halfABillion() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDate d = t.getDate(); + + int hour = 0; + int min = 0; + int sec = 0; + int nanos = 0; + + for (long i = 0; i < 3700 * 1000000000L; i+= 500000000) { + t = t.plusNanos(500000000); + nanos += 500000000; + if (nanos == 1000000000) { + sec++; + nanos = 0; + } + if (sec == 60) { + min++; + sec = 0; + } + if (min == 60) { + hour++; + min = 0; + } + + assertEquals(t.getDate(), d, String.valueOf(i)); + assertEquals(t.getHour(), hour); + assertEquals(t.getMinute(), min); + assertEquals(t.getSecond(), sec); + assertEquals(t.getNano(), nanos); + } + } + + @DataProvider(name="plusNanos_fromZero") + Iterator plusNanos_fromZero() { + return new Iterator() { + long delta = 7500000000L; + + long i = -3660 * 1000000000L; + LocalDate date = TEST_2007_07_15_12_30_40_987654321.getDate().minusDays(1); + int hour = 22; + int min = 59; + int sec = 0; + long nanos = 0; + + public boolean hasNext() { + return i <= 3660 * 1000000000L; + } + + public Object[] next() { + final Object[] ret = new Object[] {i, date, hour, min, sec, (int)nanos}; + i += delta; + nanos += delta; + + if (nanos >= 1000000000L) { + sec += nanos / 1000000000L; + nanos %= 1000000000L; + + if (sec >= 60) { + min++; + sec %= 60; + + if (min == 60) { + hour++; + min = 0; + + if (hour == 24) { + hour = 0; + date = date.plusDays(1); + } + } + } + } + + return ret; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Test(dataProvider="plusNanos_fromZero", groups={"tck"}) + public void test_plusNanos_fromZero(long nanoseconds, LocalDate date, int hour, int min, int sec, int nanos) { + LocalDateTime base = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDateTime t = base.plusNanos(nanoseconds); + + assertEquals(date, t.getDate()); + assertEquals(hour, t.getHour()); + assertEquals(min, t.getMinute()); + assertEquals(sec, t.getSecond()); + assertEquals(nanos, t.getNano()); + } + + @Test(groups={"tck"}) + public void test_plusNanos_noChange_oneDay() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusNanos(24 * 60 * 60 * 1000000000L); + assertEquals(t.getDate(), TEST_2007_07_15_12_30_40_987654321.getDate().plusDays(1)); + } + + //----------------------------------------------------------------------- + // minus(adjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minus_adjuster() { + Period p = Period.ofTime(0, 0, 62, 3); + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minus(p); + assertEquals(t, LocalDateTime.of(2007, 7, 15, 12, 29, 38, 987654318)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_minus_adjuster_null() { + TEST_2007_07_15_12_30_40_987654321.minus((TemporalSubtractor) null); + } + + //----------------------------------------------------------------------- + // minus(Period) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minus_Period_positiveMonths() { + MockSimplePeriod period = MockSimplePeriod.of(7, ChronoUnit.MONTHS); + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minus(period); + assertEquals(t, LocalDateTime.of(2006, 12, 15, 12, 30, 40, 987654321)); + } + + @Test(groups={"tck"}) + public void test_minus_Period_negativeDays() { + MockSimplePeriod period = MockSimplePeriod.of(-25, ChronoUnit.DAYS); + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minus(period); + assertEquals(t, LocalDateTime.of(2007, 8, 9, 12, 30, 40, 987654321)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_minus_Period_null() { + TEST_2007_07_15_12_30_40_987654321.minus((MockSimplePeriod) null); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minus_Period_invalidTooLarge() { + MockSimplePeriod period = MockSimplePeriod.of(-1, ChronoUnit.YEARS); + LocalDateTime.of(Year.MAX_VALUE, 1, 1, 0, 0).minus(period); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minus_Period_invalidTooSmall() { + MockSimplePeriod period = MockSimplePeriod.of(1, ChronoUnit.YEARS); + LocalDateTime.of(Year.MIN_VALUE, 1, 1, 0, 0).minus(period); + } + + //----------------------------------------------------------------------- + // minus(long,TemporalUnit) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minus_longTemporalUnit_positiveMonths() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minus(7, ChronoUnit.MONTHS); + assertEquals(t, LocalDateTime.of(2006, 12, 15, 12, 30, 40, 987654321)); + } + + @Test(groups={"tck"}) + public void test_minus_longTemporalUnit_negativeDays() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minus(-25, ChronoUnit.DAYS); + assertEquals(t, LocalDateTime.of(2007, 8, 9, 12, 30, 40, 987654321)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_minus_longTemporalUnit_null() { + TEST_2007_07_15_12_30_40_987654321.minus(1, (TemporalUnit) null); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minus_longTemporalUnit_invalidTooLarge() { + LocalDateTime.of(Year.MAX_VALUE, 1, 1, 0, 0).minus(-1, ChronoUnit.YEARS); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minus_longTemporalUnit_invalidTooSmall() { + LocalDateTime.of(Year.MIN_VALUE, 1, 1, 0, 0).minus(1, ChronoUnit.YEARS); + } + + //----------------------------------------------------------------------- + // minusYears() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusYears_int_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusYears(1); + check(t, 2006, 7, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusYears_int_negative() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusYears(-1); + check(t, 2008, 7, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusYears_int_adjustDay() { + LocalDateTime t = createDateMidnight(2008, 2, 29).minusYears(1); + check(t, 2007, 2, 28, 0, 0, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_int_invalidTooLarge() { + createDateMidnight(Year.MAX_VALUE, 1, 1).minusYears(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_int_invalidTooSmall() { + createDateMidnight(Year.MIN_VALUE, 1, 1).minusYears(1); + } + + //----------------------------------------------------------------------- + // minusMonths() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusMonths_int_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusMonths(1); + check(t, 2007, 6, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusMonths_int_overYears() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusMonths(25); + check(t, 2005, 6, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusMonths_int_negative() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusMonths(-1); + check(t, 2007, 8, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusMonths_int_negativeAcrossYear() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusMonths(-7); + check(t, 2008, 2, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusMonths_int_negativeOverYears() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusMonths(-31); + check(t, 2010, 2, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusMonths_int_adjustDayFromLeapYear() { + LocalDateTime t = createDateMidnight(2008, 2, 29).minusMonths(12); + check(t, 2007, 2, 28, 0, 0, 0, 0); + } + + @Test(groups={"tck"}) + public void test_minusMonths_int_adjustDayFromMonthLength() { + LocalDateTime t = createDateMidnight(2007, 3, 31).minusMonths(1); + check(t, 2007, 2, 28, 0, 0, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusMonths_int_invalidTooLarge() { + createDateMidnight(Year.MAX_VALUE, 12, 1).minusMonths(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusMonths_int_invalidTooSmall() { + createDateMidnight(Year.MIN_VALUE, 1, 1).minusMonths(1); + } + + //----------------------------------------------------------------------- + // minusWeeks() + //----------------------------------------------------------------------- + @DataProvider(name="sampleMinusWeeksSymmetry") + Object[][] provider_sampleMinusWeeksSymmetry() { + return new Object[][] { + {createDateMidnight(-1, 1, 1)}, + {createDateMidnight(-1, 2, 28)}, + {createDateMidnight(-1, 3, 1)}, + {createDateMidnight(-1, 12, 31)}, + {createDateMidnight(0, 1, 1)}, + {createDateMidnight(0, 2, 28)}, + {createDateMidnight(0, 2, 29)}, + {createDateMidnight(0, 3, 1)}, + {createDateMidnight(0, 12, 31)}, + {createDateMidnight(2007, 1, 1)}, + {createDateMidnight(2007, 2, 28)}, + {createDateMidnight(2007, 3, 1)}, + {createDateMidnight(2007, 12, 31)}, + {createDateMidnight(2008, 1, 1)}, + {createDateMidnight(2008, 2, 28)}, + {createDateMidnight(2008, 2, 29)}, + {createDateMidnight(2008, 3, 1)}, + {createDateMidnight(2008, 12, 31)}, + {createDateMidnight(2099, 1, 1)}, + {createDateMidnight(2099, 2, 28)}, + {createDateMidnight(2099, 3, 1)}, + {createDateMidnight(2099, 12, 31)}, + {createDateMidnight(2100, 1, 1)}, + {createDateMidnight(2100, 2, 28)}, + {createDateMidnight(2100, 3, 1)}, + {createDateMidnight(2100, 12, 31)}, + }; + } + + @Test(dataProvider="sampleMinusWeeksSymmetry", groups={"tck"}) + public void test_minusWeeks_symmetry(LocalDateTime reference) { + for (int weeks = 0; weeks < 365 * 8; weeks++) { + LocalDateTime t = reference.minusWeeks(weeks).minusWeeks(-weeks); + assertEquals(t, reference); + + t = reference.minusWeeks(-weeks).minusWeeks(weeks); + assertEquals(t, reference); + } + } + + @Test(groups={"tck"}) + public void test_minusWeeks_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusWeeks(1); + check(t, 2007, 7, 8, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_overMonths() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusWeeks(9); + check(t, 2007, 5, 13, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_overYears() { + LocalDateTime t = LocalDateTime.of(2008, 7, 13, 12, 30, 40, 987654321).minusWeeks(52); + assertEquals(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_overLeapYears() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusYears(-1).minusWeeks(104); + check(t, 2006, 7, 18, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_negative() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusWeeks(-1); + check(t, 2007, 7, 22, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_negativeAcrossYear() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusWeeks(-28); + check(t, 2008, 1, 27, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_negativeOverYears() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusWeeks(-104); + check(t, 2009, 7, 12, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_maximum() { + LocalDateTime t = createDateMidnight(Year.MAX_VALUE, 12, 24).minusWeeks(-1); + check(t, Year.MAX_VALUE, 12, 31, 0, 0, 0, 0); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_minimum() { + LocalDateTime t = createDateMidnight(Year.MIN_VALUE, 1, 8).minusWeeks(1); + check(t, Year.MIN_VALUE, 1, 1, 0, 0, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusWeeks_invalidTooLarge() { + createDateMidnight(Year.MAX_VALUE, 12, 25).minusWeeks(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusWeeks_invalidTooSmall() { + createDateMidnight(Year.MIN_VALUE, 1, 7).minusWeeks(1); + } + + //----------------------------------------------------------------------- + // minusDays() + //----------------------------------------------------------------------- + @DataProvider(name="sampleMinusDaysSymmetry") + Object[][] provider_sampleMinusDaysSymmetry() { + return new Object[][] { + {createDateMidnight(-1, 1, 1)}, + {createDateMidnight(-1, 2, 28)}, + {createDateMidnight(-1, 3, 1)}, + {createDateMidnight(-1, 12, 31)}, + {createDateMidnight(0, 1, 1)}, + {createDateMidnight(0, 2, 28)}, + {createDateMidnight(0, 2, 29)}, + {createDateMidnight(0, 3, 1)}, + {createDateMidnight(0, 12, 31)}, + {createDateMidnight(2007, 1, 1)}, + {createDateMidnight(2007, 2, 28)}, + {createDateMidnight(2007, 3, 1)}, + {createDateMidnight(2007, 12, 31)}, + {createDateMidnight(2008, 1, 1)}, + {createDateMidnight(2008, 2, 28)}, + {createDateMidnight(2008, 2, 29)}, + {createDateMidnight(2008, 3, 1)}, + {createDateMidnight(2008, 12, 31)}, + {createDateMidnight(2099, 1, 1)}, + {createDateMidnight(2099, 2, 28)}, + {createDateMidnight(2099, 3, 1)}, + {createDateMidnight(2099, 12, 31)}, + {createDateMidnight(2100, 1, 1)}, + {createDateMidnight(2100, 2, 28)}, + {createDateMidnight(2100, 3, 1)}, + {createDateMidnight(2100, 12, 31)}, + }; + } + + @Test(dataProvider="sampleMinusDaysSymmetry", groups={"tck"}) + public void test_minusDays_symmetry(LocalDateTime reference) { + for (int days = 0; days < 365 * 8; days++) { + LocalDateTime t = reference.minusDays(days).minusDays(-days); + assertEquals(t, reference); + + t = reference.minusDays(-days).minusDays(days); + assertEquals(t, reference); + } + } + + @Test(groups={"tck"}) + public void test_minusDays_normal() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusDays(1); + check(t, 2007, 7, 14, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusDays_overMonths() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusDays(62); + check(t, 2007, 5, 14, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusDays_overYears() { + LocalDateTime t = LocalDateTime.of(2008, 7, 16, 12, 30, 40, 987654321).minusDays(367); + assertEquals(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_minusDays_overLeapYears() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusYears(2).minusDays(365 + 366); + assertEquals(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_minusDays_negative() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusDays(-1); + check(t, 2007, 7, 16, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusDays_negativeAcrossYear() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusDays(-169); + check(t, 2007, 12, 31, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusDays_negativeOverYears() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusDays(-731); + check(t, 2009, 7, 15, 12, 30, 40, 987654321); + } + + @Test(groups={"tck"}) + public void test_minusDays_maximum() { + LocalDateTime t = createDateMidnight(Year.MAX_VALUE, 12, 30).minusDays(-1); + check(t, Year.MAX_VALUE, 12, 31, 0, 0, 0, 0); + } + + @Test(groups={"tck"}) + public void test_minusDays_minimum() { + LocalDateTime t = createDateMidnight(Year.MIN_VALUE, 1, 2).minusDays(1); + check(t, Year.MIN_VALUE, 1, 1, 0, 0, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusDays_invalidTooLarge() { + createDateMidnight(Year.MAX_VALUE, 12, 31).minusDays(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusDays_invalidTooSmall() { + createDateMidnight(Year.MIN_VALUE, 1, 1).minusDays(1); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_minusDays_overflowTooLarge() { + createDateMidnight(Year.MAX_VALUE, 12, 31).minusDays(Long.MIN_VALUE); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_minusDays_overflowTooSmall() { + createDateMidnight(Year.MIN_VALUE, 1, 1).minusDays(Long.MAX_VALUE); + } + + //----------------------------------------------------------------------- + // minusHours() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusHours_one() { + LocalDateTime t =TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDate d = t.getDate(); + + for (int i = 0; i < 50; i++) { + t = t.minusHours(1); + + if (i % 24 == 0) { + d = d.minusDays(1); + } + + assertEquals(t.getDate(), d); + assertEquals(t.getHour(), (((-i + 23) % 24) + 24) % 24); + } + } + + @Test(groups={"tck"}) + public void test_minusHours_fromZero() { + LocalDateTime base = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDate d = base.getDate().plusDays(2); + LocalTime t = LocalTime.of(3, 0); + + for (int i = -50; i < 50; i++) { + LocalDateTime dt = base.minusHours(i); + t = t.minusHours(1); + + if (t.getHour() == 23) { + d = d.minusDays(1); + } + + assertEquals(dt.getDate(), d, String.valueOf(i)); + assertEquals(dt.getTime(), t); + } + } + + @Test(groups={"tck"}) + public void test_minusHours_fromOne() { + LocalDateTime base = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(1, 0)); + LocalDate d = base.getDate().plusDays(2); + LocalTime t = LocalTime.of(4, 0); + + for (int i = -50; i < 50; i++) { + LocalDateTime dt = base.minusHours(i); + + t = t.minusHours(1); + + if (t.getHour() == 23) { + d = d.minusDays(1); + } + + assertEquals(dt.getDate(), d, String.valueOf(i)); + assertEquals(dt.getTime(), t); + } + } + + //----------------------------------------------------------------------- + // minusMinutes() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusMinutes_one() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDate d = t.getDate().minusDays(1); + + int hour = 0; + int min = 0; + + for (int i = 0; i < 70; i++) { + t = t.minusMinutes(1); + min--; + if (min == -1) { + hour--; + min = 59; + + if (hour == -1) { + hour = 23; + } + } + assertEquals(t.getDate(), d); + assertEquals(t.getHour(), hour); + assertEquals(t.getMinute(), min); + } + } + + @Test(groups={"tck"}) + public void test_minusMinutes_fromZero() { + LocalDateTime base = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDate d = base.getDate().minusDays(1); + LocalTime t = LocalTime.of(22, 49); + + for (int i = 70; i > -70; i--) { + LocalDateTime dt = base.minusMinutes(i); + t = t.plusMinutes(1); + + if (t == LocalTime.MIDNIGHT) { + d = d.plusDays(1); + } + + assertEquals(dt.getDate(), d); + assertEquals(dt.getTime(), t); + } + } + + @Test(groups={"tck"}) + public void test_minusMinutes_noChange_oneDay() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusMinutes(24 * 60); + assertEquals(t.getDate(), TEST_2007_07_15_12_30_40_987654321.getDate().minusDays(1)); + } + + //----------------------------------------------------------------------- + // minusSeconds() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusSeconds_one() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDate d = t.getDate().minusDays(1); + + int hour = 0; + int min = 0; + int sec = 0; + + for (int i = 0; i < 3700; i++) { + t = t.minusSeconds(1); + sec--; + if (sec == -1) { + min--; + sec = 59; + + if (min == -1) { + hour--; + min = 59; + + if (hour == -1) { + hour = 23; + } + } + } + + assertEquals(t.getDate(), d); + assertEquals(t.getHour(), hour); + assertEquals(t.getMinute(), min); + assertEquals(t.getSecond(), sec); + } + } + + @DataProvider(name="minusSeconds_fromZero") + Iterator minusSeconds_fromZero() { + return new Iterator() { + int delta = 30; + + int i = 3660; + LocalDate date = TEST_2007_07_15_12_30_40_987654321.getDate().minusDays(1); + int hour = 22; + int min = 59; + int sec = 0; + + public boolean hasNext() { + return i >= -3660; + } + + public Object[] next() { + final Object[] ret = new Object[] {i, date, hour, min, sec}; + i -= delta; + sec += delta; + + if (sec >= 60) { + min++; + sec -= 60; + + if (min == 60) { + hour++; + min = 0; + + if (hour == 24) { + hour = 0; + } + } + } + + if (i == 0) { + date = date.plusDays(1); + } + + return ret; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Test(dataProvider="minusSeconds_fromZero", groups={"tck"}) + public void test_minusSeconds_fromZero(int seconds, LocalDate date, int hour, int min, int sec) { + LocalDateTime base = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDateTime t = base.minusSeconds(seconds); + + assertEquals(date, t.getDate()); + assertEquals(hour, t.getHour()); + assertEquals(min, t.getMinute()); + assertEquals(sec, t.getSecond()); + } + + //----------------------------------------------------------------------- + // minusNanos() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusNanos_halfABillion() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDate d = t.getDate().minusDays(1); + + int hour = 0; + int min = 0; + int sec = 0; + int nanos = 0; + + for (long i = 0; i < 3700 * 1000000000L; i+= 500000000) { + t = t.minusNanos(500000000); + nanos -= 500000000; + + if (nanos < 0) { + sec--; + nanos += 1000000000; + + if (sec == -1) { + min--; + sec += 60; + + if (min == -1) { + hour--; + min += 60; + + if (hour == -1) { + hour += 24; + } + } + } + } + + assertEquals(t.getDate(), d); + assertEquals(t.getHour(), hour); + assertEquals(t.getMinute(), min); + assertEquals(t.getSecond(), sec); + assertEquals(t.getNano(), nanos); + } + } + + @DataProvider(name="minusNanos_fromZero") + Iterator minusNanos_fromZero() { + return new Iterator() { + long delta = 7500000000L; + + long i = 3660 * 1000000000L; + LocalDate date = TEST_2007_07_15_12_30_40_987654321.getDate().minusDays(1); + int hour = 22; + int min = 59; + int sec = 0; + long nanos = 0; + + public boolean hasNext() { + return i >= -3660 * 1000000000L; + } + + public Object[] next() { + final Object[] ret = new Object[] {i, date, hour, min, sec, (int)nanos}; + i -= delta; + nanos += delta; + + if (nanos >= 1000000000L) { + sec += nanos / 1000000000L; + nanos %= 1000000000L; + + if (sec >= 60) { + min++; + sec %= 60; + + if (min == 60) { + hour++; + min = 0; + + if (hour == 24) { + hour = 0; + date = date.plusDays(1); + } + } + } + } + + return ret; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Test(dataProvider="minusNanos_fromZero", groups={"tck"}) + public void test_minusNanos_fromZero(long nanoseconds, LocalDate date, int hour, int min, int sec, int nanos) { + LocalDateTime base = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.MIDNIGHT); + LocalDateTime t = base.minusNanos(nanoseconds); + + assertEquals(date, t.getDate()); + assertEquals(hour, t.getHour()); + assertEquals(min, t.getMinute()); + assertEquals(sec, t.getSecond()); + assertEquals(nanos, t.getNano()); + } + + //----------------------------------------------------------------------- + // atOffset() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atOffset() { + LocalDateTime t = LocalDateTime.of(2008, 6, 30, 11, 30); + assertEquals(t.atOffset(OFFSET_PTWO), OffsetDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 30), OFFSET_PTWO)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_atOffset_nullZoneOffset() { + LocalDateTime t = LocalDateTime.of(2008, 6, 30, 11, 30); + t.atOffset((ZoneOffset) null); + } + + //----------------------------------------------------------------------- + // atZone() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atZone() { + LocalDateTime t = LocalDateTime.of(2008, 6, 30, 11, 30); + assertEquals(t.atZone(ZONE_PARIS), + ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 30), ZONE_PARIS)); + } + + @Test(groups={"tck"}) + public void test_atZone_Offset() { + LocalDateTime t = LocalDateTime.of(2008, 6, 30, 11, 30); + assertEquals(t.atZone(OFFSET_PTWO), ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 30), OFFSET_PTWO)); + } + + @Test(groups={"tck"}) + public void test_atZone_dstGap() { + LocalDateTime t = LocalDateTime.of(2007, 4, 1, 0, 0); + assertEquals(t.atZone(ZONE_GAZA), + ZonedDateTime.of(LocalDateTime.of(2007, 4, 1, 1, 0), ZONE_GAZA)); + } + + @Test(groups={"tck"}) + public void test_atZone_dstOverlap() { + LocalDateTime t = LocalDateTime.of(2007, 10, 28, 2, 30); + assertEquals(t.atZone(ZONE_PARIS), + ZonedDateTime.ofStrict(LocalDateTime.of(2007, 10, 28, 2, 30), OFFSET_PTWO, ZONE_PARIS)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_atZone_nullTimeZone() { + LocalDateTime t = LocalDateTime.of(2008, 6, 30, 11, 30); + t.atZone((ZoneId) null); + } + + //----------------------------------------------------------------------- + // toEpochSecond() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toEpochSecond_afterEpoch() { + for (int i = -5; i < 5; i++) { + ZoneOffset offset = ZoneOffset.ofHours(i); + for (int j = 0; j < 100000; j++) { + LocalDateTime a = LocalDateTime.of(1970, 1, 1, 0, 0).plusSeconds(j); + assertEquals(a.toEpochSecond(offset), j - i * 3600); + } + } + } + + @Test(groups={"tck"}) + public void test_toEpochSecond_beforeEpoch() { + for (int i = 0; i < 100000; i++) { + LocalDateTime a = LocalDateTime.of(1970, 1, 1, 0, 0).minusSeconds(i); + assertEquals(a.toEpochSecond(ZoneOffset.UTC), -i); + } + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_comparisons() { + test_comparisons_LocalDateTime( + LocalDate.of(Year.MIN_VALUE, 1, 1), + LocalDate.of(Year.MIN_VALUE, 12, 31), + LocalDate.of(-1, 1, 1), + LocalDate.of(-1, 12, 31), + LocalDate.of(0, 1, 1), + LocalDate.of(0, 12, 31), + LocalDate.of(1, 1, 1), + LocalDate.of(1, 12, 31), + LocalDate.of(2008, 1, 1), + LocalDate.of(2008, 2, 29), + LocalDate.of(2008, 12, 31), + LocalDate.of(Year.MAX_VALUE, 1, 1), + LocalDate.of(Year.MAX_VALUE, 12, 31) + ); + } + + void test_comparisons_LocalDateTime(LocalDate... localDates) { + test_comparisons_LocalDateTime( + localDates, + LocalTime.MIDNIGHT, + LocalTime.of(0, 0, 0, 999999999), + LocalTime.of(0, 0, 59, 0), + LocalTime.of(0, 0, 59, 999999999), + LocalTime.of(0, 59, 0, 0), + LocalTime.of(0, 59, 59, 999999999), + LocalTime.NOON, + LocalTime.of(12, 0, 0, 999999999), + LocalTime.of(12, 0, 59, 0), + LocalTime.of(12, 0, 59, 999999999), + LocalTime.of(12, 59, 0, 0), + LocalTime.of(12, 59, 59, 999999999), + LocalTime.of(23, 0, 0, 0), + LocalTime.of(23, 0, 0, 999999999), + LocalTime.of(23, 0, 59, 0), + LocalTime.of(23, 0, 59, 999999999), + LocalTime.of(23, 59, 0, 0), + LocalTime.of(23, 59, 59, 999999999) + ); + } + + void test_comparisons_LocalDateTime(LocalDate[] localDates, LocalTime... localTimes) { + LocalDateTime[] localDateTimes = new LocalDateTime[localDates.length * localTimes.length]; + int i = 0; + + for (LocalDate localDate : localDates) { + for (LocalTime localTime : localTimes) { + localDateTimes[i++] = LocalDateTime.of(localDate, localTime); + } + } + + doTest_comparisons_LocalDateTime(localDateTimes); + } + + void doTest_comparisons_LocalDateTime(LocalDateTime[] localDateTimes) { + for (int i = 0; i < localDateTimes.length; i++) { + LocalDateTime a = localDateTimes[i]; + for (int j = 0; j < localDateTimes.length; j++) { + LocalDateTime b = localDateTimes[j]; + if (i < j) { + assertTrue(a.compareTo(b) < 0, a + " <=> " + b); + assertEquals(a.isBefore(b), true, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else if (i > j) { + assertTrue(a.compareTo(b) > 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), true, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else { + assertEquals(a.compareTo(b), 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), true, a + " <=> " + b); + } + } + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_compareTo_ObjectNull() { + TEST_2007_07_15_12_30_40_987654321.compareTo(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isBefore_ObjectNull() { + TEST_2007_07_15_12_30_40_987654321.isBefore(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isAfter_ObjectNull() { + TEST_2007_07_15_12_30_40_987654321.isAfter(null); + } + + @Test(expectedExceptions=ClassCastException.class, groups={"tck"}) + @SuppressWarnings({"unchecked", "rawtypes"}) + public void compareToNonLocalDateTime() { + Comparable c = TEST_2007_07_15_12_30_40_987654321; + c.compareTo(new Object()); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @DataProvider(name="sampleDateTimes") + Iterator provider_sampleDateTimes() { + return new Iterator() { + Object[][] sampleDates = provider_sampleDates(); + Object[][] sampleTimes = provider_sampleTimes(); + int datesIndex = 0; + int timesIndex = 0; + + public boolean hasNext() { + return datesIndex < sampleDates.length; + } + + public Object[] next() { + Object[] sampleDate = sampleDates[datesIndex]; + Object[] sampleTime = sampleTimes[timesIndex]; + + Object[] ret = new Object[sampleDate.length + sampleTime.length]; + + System.arraycopy(sampleDate, 0, ret, 0, sampleDate.length); + System.arraycopy(sampleTime, 0, ret, sampleDate.length, sampleTime.length); + + if (++timesIndex == sampleTimes.length) { + datesIndex++; + timesIndex = 0; + } + + return ret; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Test(dataProvider="sampleDateTimes", groups={"tck"}) + public void test_equals_true(int y, int m, int d, int h, int mi, int s, int n) { + LocalDateTime a = LocalDateTime.of(y, m, d, h, mi, s, n); + LocalDateTime b = LocalDateTime.of(y, m, d, h, mi, s, n); + assertTrue(a.equals(b)); + } + + @Test(dataProvider="sampleDateTimes", groups={"tck"}) + public void test_equals_false_year_differs(int y, int m, int d, int h, int mi, int s, int n) { + LocalDateTime a = LocalDateTime.of(y, m, d, h, mi, s, n); + LocalDateTime b = LocalDateTime.of(y + 1, m, d, h, mi, s, n); + assertFalse(a.equals(b)); + } + + @Test(dataProvider="sampleDateTimes", groups={"tck"}) + public void test_equals_false_month_differs(int y, int m, int d, int h, int mi, int s, int n) { + LocalDateTime a = LocalDateTime.of(y, m, d, h, mi, s, n); + LocalDateTime b = LocalDateTime.of(y, m + 1, d, h, mi, s, n); + assertFalse(a.equals(b)); + } + + @Test(dataProvider="sampleDateTimes", groups={"tck"}) + public void test_equals_false_day_differs(int y, int m, int d, int h, int mi, int s, int n) { + LocalDateTime a = LocalDateTime.of(y, m, d, h, mi, s, n); + LocalDateTime b = LocalDateTime.of(y, m, d + 1, h, mi, s, n); + assertFalse(a.equals(b)); + } + + @Test(dataProvider="sampleDateTimes", groups={"tck"}) + public void test_equals_false_hour_differs(int y, int m, int d, int h, int mi, int s, int n) { + LocalDateTime a = LocalDateTime.of(y, m, d, h, mi, s, n); + LocalDateTime b = LocalDateTime.of(y, m, d, h + 1, mi, s, n); + assertFalse(a.equals(b)); + } + + @Test(dataProvider="sampleDateTimes", groups={"tck"}) + public void test_equals_false_minute_differs(int y, int m, int d, int h, int mi, int s, int n) { + LocalDateTime a = LocalDateTime.of(y, m, d, h, mi, s, n); + LocalDateTime b = LocalDateTime.of(y, m, d, h, mi + 1, s, n); + assertFalse(a.equals(b)); + } + + @Test(dataProvider="sampleDateTimes", groups={"tck"}) + public void test_equals_false_second_differs(int y, int m, int d, int h, int mi, int s, int n) { + LocalDateTime a = LocalDateTime.of(y, m, d, h, mi, s, n); + LocalDateTime b = LocalDateTime.of(y, m, d, h, mi, s + 1, n); + assertFalse(a.equals(b)); + } + + @Test(dataProvider="sampleDateTimes", groups={"tck"}) + public void test_equals_false_nano_differs(int y, int m, int d, int h, int mi, int s, int n) { + LocalDateTime a = LocalDateTime.of(y, m, d, h, mi, s, n); + LocalDateTime b = LocalDateTime.of(y, m, d, h, mi, s, n + 1); + assertFalse(a.equals(b)); + } + + @Test(groups={"tck"}) + public void test_equals_itself_true() { + assertEquals(TEST_2007_07_15_12_30_40_987654321.equals(TEST_2007_07_15_12_30_40_987654321), true); + } + + @Test(groups={"tck"}) + public void test_equals_string_false() { + assertEquals(TEST_2007_07_15_12_30_40_987654321.equals("2007-07-15T12:30:40.987654321"), false); + } + + @Test(groups={"tck"}) + public void test_equals_null_false() { + assertEquals(TEST_2007_07_15_12_30_40_987654321.equals(null), false); + } + + //----------------------------------------------------------------------- + // hashCode() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleDateTimes", groups={"tck"}) + public void test_hashCode(int y, int m, int d, int h, int mi, int s, int n) { + LocalDateTime a = LocalDateTime.of(y, m, d, h, mi, s, n); + assertEquals(a.hashCode(), a.hashCode()); + LocalDateTime b = LocalDateTime.of(y, m, d, h, mi, s, n); + assertEquals(a.hashCode(), b.hashCode()); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="sampleToString") + Object[][] provider_sampleToString() { + return new Object[][] { + {2008, 7, 5, 2, 1, 0, 0, "2008-07-05T02:01"}, + {2007, 12, 31, 23, 59, 1, 0, "2007-12-31T23:59:01"}, + {999, 12, 31, 23, 59, 59, 990000000, "0999-12-31T23:59:59.990"}, + {-1, 1, 2, 23, 59, 59, 999990000, "-0001-01-02T23:59:59.999990"}, + {-2008, 1, 2, 23, 59, 59, 999999990, "-2008-01-02T23:59:59.999999990"}, + }; + } + + @Test(dataProvider="sampleToString", groups={"tck"}) + public void test_toString(int y, int m, int d, int h, int mi, int s, int n, String expected) { + LocalDateTime t = LocalDateTime.of(y, m, d, h, mi, s, n); + String str = t.toString(); + assertEquals(str, expected); + } + + //----------------------------------------------------------------------- + // toString(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d H m s"); + String t = LocalDateTime.of(2010, 12, 3, 11, 30, 45).toString(f); + assertEquals(t, "2010 12 3 11 30 45"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_toString_formatter_null() { + LocalDateTime.of(2010, 12, 3, 11, 30, 45).toString(null); + } + +} diff --git a/test/java/time/tck/java/time/TCKLocalTime.java b/test/java/time/tck/java/time/TCKLocalTime.java new file mode 100644 index 0000000000000000000000000000000000000000..1cc5486c7e26623187fdb9986efec5ee02d5c351 --- /dev/null +++ b/test/java/time/tck/java/time/TCKLocalTime.java @@ -0,0 +1,2226 @@ +/* + * 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. + * + * 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 tck.java.time; + +import static java.time.temporal.ChronoField.AMPM_OF_DAY; +import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM; +import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY; +import static java.time.temporal.ChronoField.HOUR_OF_AMPM; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.MICRO_OF_DAY; +import static java.time.temporal.ChronoField.MICRO_OF_SECOND; +import static java.time.temporal.ChronoField.MILLI_OF_DAY; +import static java.time.temporal.ChronoField.MILLI_OF_SECOND; +import static java.time.temporal.ChronoField.MINUTE_OF_DAY; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.NANO_OF_DAY; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.SECOND_OF_DAY; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.FOREVER; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MICROS; +import static java.time.temporal.ChronoUnit.MILLIS; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static java.time.temporal.ChronoUnit.WEEKS; +import static java.time.temporal.ChronoUnit.YEARS; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.List; + +import java.time.Clock; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.JulianFields; +import java.time.temporal.OffsetTime; +import java.time.temporal.Queries; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.TemporalUnit; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import test.java.time.MockSimplePeriod; + +/** + * Test LocalTime. + */ +@Test +public class TCKLocalTime extends AbstractDateTimeTest { + + private static final ZoneOffset OFFSET_PTWO = ZoneOffset.ofHours(2); + + private LocalTime TEST_12_30_40_987654321; + + private static final TemporalUnit[] INVALID_UNITS; + static { + EnumSet set = EnumSet.range(WEEKS, FOREVER); + INVALID_UNITS = (TemporalUnit[]) set.toArray(new TemporalUnit[set.size()]); + } + + @BeforeMethod(groups={"tck","implementation"}) + public void setUp() { + TEST_12_30_40_987654321 = LocalTime.of(12, 30, 40, 987654321); + } + + //----------------------------------------------------------------------- + @Override + protected List samples() { + TemporalAccessor[] array = {TEST_12_30_40_987654321, LocalTime.MIN, LocalTime.MAX, LocalTime.MIDNIGHT, LocalTime.NOON}; + return Arrays.asList(array); + } + + @Override + protected List validFields() { + TemporalField[] array = { + NANO_OF_SECOND, + NANO_OF_DAY, + MICRO_OF_SECOND, + MICRO_OF_DAY, + MILLI_OF_SECOND, + MILLI_OF_DAY, + SECOND_OF_MINUTE, + SECOND_OF_DAY, + MINUTE_OF_HOUR, + MINUTE_OF_DAY, + CLOCK_HOUR_OF_AMPM, + HOUR_OF_AMPM, + CLOCK_HOUR_OF_DAY, + HOUR_OF_DAY, + AMPM_OF_DAY, + }; + return Arrays.asList(array); + } + + @Override + protected List invalidFields() { + List list = new ArrayList<>(Arrays.asList(ChronoField.values())); + list.removeAll(validFields()); + list.add(JulianFields.JULIAN_DAY); + list.add(JulianFields.MODIFIED_JULIAN_DAY); + list.add(JulianFields.RATA_DIE); + return list; + } + + //----------------------------------------------------------------------- + @Test + public void test_serialization() throws Exception { + assertSerializable(TEST_12_30_40_987654321); + assertSerializable(LocalTime.MIN); + assertSerializable(LocalTime.MAX); + } + + @Test + public void test_serialization_format_h() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(4); + dos.writeByte(-1 - 22); + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(LocalTime.of(22, 0), bytes); + } + + @Test + public void test_serialization_format_hm() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(4); + dos.writeByte(22); + dos.writeByte(-1 - 17); + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(LocalTime.of(22, 17), bytes); + } + + @Test + public void test_serialization_format_hms() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(4); + dos.writeByte(22); + dos.writeByte(17); + dos.writeByte(-1 - 59); + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(LocalTime.of(22, 17, 59), bytes); + } + + @Test + public void test_serialization_format_hmsn() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(4); + dos.writeByte(22); + dos.writeByte(17); + dos.writeByte(59); + dos.writeInt(459_000_000); + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(LocalTime.of(22, 17, 59, 459_000_000), bytes); + } + + //----------------------------------------------------------------------- + private void check(LocalTime test, int h, int m, int s, int n) { + assertEquals(test.getHour(), h); + assertEquals(test.getMinute(), m); + assertEquals(test.getSecond(), s); + assertEquals(test.getNano(), n); + assertEquals(test, test); + assertEquals(test.hashCode(), test.hashCode()); + assertEquals(LocalTime.of(h, m, s, n), test); + } + + //----------------------------------------------------------------------- + // constants + //----------------------------------------------------------------------- + @Test(groups={"tck","implementation"}) + public void constant_MIDNIGHT() { + check(LocalTime.MIDNIGHT, 0, 0, 0, 0); + } + + @Test + public void constant_MIDDAY() { + check(LocalTime.NOON, 12, 0, 0, 0); + } + + @Test + public void constant_MIN() { + check(LocalTime.MIN, 0, 0, 0, 0); + } + + @Test + public void constant_MAX() { + check(LocalTime.MAX, 23, 59, 59, 999999999); + } + + //----------------------------------------------------------------------- + // now() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now() { + LocalTime expected = LocalTime.now(Clock.systemDefaultZone()); + LocalTime test = LocalTime.now(); + long diff = Math.abs(test.toNanoOfDay() - expected.toNanoOfDay()); + assertTrue(diff < 100000000); // less than 0.1 secs + } + + //----------------------------------------------------------------------- + // now(ZoneId) + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_ZoneId_nullZoneId() { + LocalTime.now((ZoneId) null); + } + + @Test(groups={"tck"}) + public void now_ZoneId() { + ZoneId zone = ZoneId.of("UTC+01:02:03"); + LocalTime expected = LocalTime.now(Clock.system(zone)); + LocalTime test = LocalTime.now(zone); + for (int i = 0; i < 100; i++) { + if (expected.equals(test)) { + return; + } + expected = LocalTime.now(Clock.system(zone)); + test = LocalTime.now(zone); + } + assertEquals(test, expected); + } + + //----------------------------------------------------------------------- + // now(Clock) + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_Clock_nullClock() { + LocalTime.now((Clock) null); + } + + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay() { + for (int i = 0; i < (2 * 24 * 60 * 60); i++) { + Instant instant = Instant.ofEpochSecond(i, 8); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + LocalTime test = LocalTime.now(clock); + assertEquals(test.getHour(), (i / (60 * 60)) % 24); + assertEquals(test.getMinute(), (i / 60) % 60); + assertEquals(test.getSecond(), i % 60); + assertEquals(test.getNano(), 8); + } + } + + @Test(groups={"tck"}) + public void now_Clock_beforeEpoch() { + for (int i =-1; i >= -(24 * 60 * 60); i--) { + Instant instant = Instant.ofEpochSecond(i, 8); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + LocalTime test = LocalTime.now(clock); + assertEquals(test.getHour(), ((i + 24 * 60 * 60) / (60 * 60)) % 24); + assertEquals(test.getMinute(), ((i + 24 * 60 * 60) / 60) % 60); + assertEquals(test.getSecond(), (i + 24 * 60 * 60) % 60); + assertEquals(test.getNano(), 8); + } + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now_Clock_max() { + Clock clock = Clock.fixed(Instant.MAX, ZoneOffset.UTC); + LocalTime test = LocalTime.now(clock); + assertEquals(test.getHour(), 23); + assertEquals(test.getMinute(), 59); + assertEquals(test.getSecond(), 59); + assertEquals(test.getNano(), 999_999_999); + } + + @Test(groups={"tck"}) + public void now_Clock_min() { + Clock clock = Clock.fixed(Instant.MIN, ZoneOffset.UTC); + LocalTime test = LocalTime.now(clock); + assertEquals(test.getHour(), 0); + assertEquals(test.getMinute(), 0); + assertEquals(test.getSecond(), 0); + assertEquals(test.getNano(), 0); + } + + //----------------------------------------------------------------------- + // of() factories + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_time_2ints() { + LocalTime test = LocalTime.of(12, 30); + check(test, 12, 30, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_2ints_hourTooLow() { + LocalTime.of(-1, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_2ints_hourTooHigh() { + LocalTime.of(24, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_2ints_minuteTooLow() { + LocalTime.of(0, -1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_2ints_minuteTooHigh() { + LocalTime.of(0, 60); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_time_3ints() { + LocalTime test = LocalTime.of(12, 30, 40); + check(test, 12, 30, 40, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_3ints_hourTooLow() { + LocalTime.of(-1, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_3ints_hourTooHigh() { + LocalTime.of(24, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_3ints_minuteTooLow() { + LocalTime.of(0, -1, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_3ints_minuteTooHigh() { + LocalTime.of(0, 60, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_3ints_secondTooLow() { + LocalTime.of(0, 0, -1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_3ints_secondTooHigh() { + LocalTime.of(0, 0, 60); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_time_4ints() { + LocalTime test = LocalTime.of(12, 30, 40, 987654321); + check(test, 12, 30, 40, 987654321); + test = LocalTime.of(12, 0, 40, 987654321); + check(test, 12, 0, 40, 987654321); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_4ints_hourTooLow() { + LocalTime.of(-1, 0, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_4ints_hourTooHigh() { + LocalTime.of(24, 0, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_4ints_minuteTooLow() { + LocalTime.of(0, -1, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_4ints_minuteTooHigh() { + LocalTime.of(0, 60, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_4ints_secondTooLow() { + LocalTime.of(0, 0, -1, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_4ints_secondTooHigh() { + LocalTime.of(0, 0, 60, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_4ints_nanoTooLow() { + LocalTime.of(0, 0, 0, -1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_time_4ints_nanoTooHigh() { + LocalTime.of(0, 0, 0, 1000000000); + } + + //----------------------------------------------------------------------- + // ofSecondOfDay(long) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_ofSecondOfDay() { + LocalTime localTime = LocalTime.ofSecondOfDay(2 * 60 * 60 + 17 * 60 + 23); + check(localTime, 2, 17, 23, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofSecondOfDay_tooLow() { + LocalTime.ofSecondOfDay(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofSecondOfDay_tooHigh() { + LocalTime.ofSecondOfDay(24 * 60 * 60); + } + + //----------------------------------------------------------------------- + // ofSecondOfDay(long, int) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_ofSecondOfDay_long_int() { + LocalTime localTime = LocalTime.ofSecondOfDay(2 * 60 * 60 + 17 * 60 + 23, 987); + check(localTime, 2, 17, 23, 987); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofSecondOfDay_long_int_tooLowSecs() { + LocalTime.ofSecondOfDay(-1, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofSecondOfDay_long_int_tooHighSecs() { + LocalTime.ofSecondOfDay(24 * 60 * 60, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofSecondOfDay_long_int_tooLowNanos() { + LocalTime.ofSecondOfDay(0, -1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofSecondOfDay_long_int_tooHighNanos() { + LocalTime.ofSecondOfDay(0, 1000000000); + } + + //----------------------------------------------------------------------- + // ofNanoOfDay(long) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_ofNanoOfDay() { + LocalTime localTime = LocalTime.ofNanoOfDay(60 * 60 * 1000000000L + 17); + check(localTime, 1, 0, 0, 17); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofNanoOfDay_tooLow() { + LocalTime.ofNanoOfDay(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofNanoOfDay_tooHigh() { + LocalTime.ofNanoOfDay(24 * 60 * 60 * 1000000000L); + } + + //----------------------------------------------------------------------- + // from() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_from_TemporalAccessor() { + assertEquals(LocalTime.from(LocalTime.of(17, 30)), LocalTime.of(17, 30)); + assertEquals(LocalTime.from(LocalDateTime.of(2012, 5, 1, 17, 30)), LocalTime.of(17, 30)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_from_TemporalAccessor_invalid_noDerive() { + LocalTime.from(LocalDate.of(2007, 7, 15)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_from_TemporalAccessor_null() { + LocalTime.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // parse() + //----------------------------------------------------------------------- + @Test(dataProvider = "sampleToString", groups={"tck"}) + public void factory_parse_validText(int h, int m, int s, int n, String parsable) { + LocalTime t = LocalTime.parse(parsable); + assertNotNull(t, parsable); + assertEquals(t.getHour(), h); + assertEquals(t.getMinute(), m); + assertEquals(t.getSecond(), s); + assertEquals(t.getNano(), n); + } + + @DataProvider(name="sampleBadParse") + Object[][] provider_sampleBadParse() { + return new Object[][]{ + {"00;00"}, + {"12-00"}, + {"-01:00"}, + {"00:00:00-09"}, + {"00:00:00,09"}, + {"00:00:abs"}, + {"11"}, + {"11:30+01:00"}, + {"11:30+01:00[Europe/Paris]"}, + }; + } + + @Test(dataProvider = "sampleBadParse", expectedExceptions={DateTimeParseException.class}, groups={"tck"}) + public void factory_parse_invalidText(String unparsable) { + LocalTime.parse(unparsable); + } + + //-----------------------------------------------------------------------s + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_illegalHour() { + LocalTime.parse("25:00"); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_illegalMinute() { + LocalTime.parse("12:60"); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_illegalSecond() { + LocalTime.parse("12:12:60"); + } + + //-----------------------------------------------------------------------s + @Test(expectedExceptions = {NullPointerException.class}, groups={"tck"}) + public void factory_parse_nullTest() { + LocalTime.parse((String) null); + } + + //----------------------------------------------------------------------- + // parse(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_parse_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("H m s"); + LocalTime test = LocalTime.parse("14 30 40", f); + assertEquals(test, LocalTime.of(14, 30, 40)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullText() { + DateTimeFormatter f = DateTimeFormatters.pattern("H m s"); + LocalTime.parse((String) null, f); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullFormatter() { + LocalTime.parse("ANY", null); + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @Test + public void test_get_TemporalField() { + LocalTime test = TEST_12_30_40_987654321; + assertEquals(test.get(ChronoField.HOUR_OF_DAY), 12); + assertEquals(test.get(ChronoField.MINUTE_OF_HOUR), 30); + assertEquals(test.get(ChronoField.SECOND_OF_MINUTE), 40); + assertEquals(test.get(ChronoField.NANO_OF_SECOND), 987654321); + + assertEquals(test.get(ChronoField.SECOND_OF_DAY), 12 * 3600 + 30 * 60 + 40); + assertEquals(test.get(ChronoField.MINUTE_OF_DAY), 12 * 60 + 30); + assertEquals(test.get(ChronoField.HOUR_OF_AMPM), 0); + assertEquals(test.get(ChronoField.CLOCK_HOUR_OF_AMPM), 12); + assertEquals(test.get(ChronoField.CLOCK_HOUR_OF_DAY), 12); + assertEquals(test.get(ChronoField.AMPM_OF_DAY), 1); + } + + @Test + public void test_getLong_TemporalField() { + LocalTime test = TEST_12_30_40_987654321; + assertEquals(test.getLong(ChronoField.HOUR_OF_DAY), 12); + assertEquals(test.getLong(ChronoField.MINUTE_OF_HOUR), 30); + assertEquals(test.getLong(ChronoField.SECOND_OF_MINUTE), 40); + assertEquals(test.getLong(ChronoField.NANO_OF_SECOND), 987654321); + + assertEquals(test.getLong(ChronoField.SECOND_OF_DAY), 12 * 3600 + 30 * 60 + 40); + assertEquals(test.getLong(ChronoField.MINUTE_OF_DAY), 12 * 60 + 30); + assertEquals(test.getLong(ChronoField.HOUR_OF_AMPM), 0); + assertEquals(test.getLong(ChronoField.CLOCK_HOUR_OF_AMPM), 12); + assertEquals(test.getLong(ChronoField.CLOCK_HOUR_OF_DAY), 12); + assertEquals(test.getLong(ChronoField.AMPM_OF_DAY), 1); + } + + //----------------------------------------------------------------------- + // query(TemporalQuery) + //----------------------------------------------------------------------- + @Test + public void test_query_chrono() { + assertEquals(TEST_12_30_40_987654321.query(Queries.chrono()), null); + assertEquals(Queries.chrono().queryFrom(TEST_12_30_40_987654321), null); + } + + @Test + public void test_query_zoneId() { + assertEquals(TEST_12_30_40_987654321.query(Queries.zoneId()), null); + assertEquals(Queries.zoneId().queryFrom(TEST_12_30_40_987654321), null); + } + + @Test + public void test_query_precision() { + assertEquals(TEST_12_30_40_987654321.query(Queries.precision()), NANOS); + assertEquals(Queries.precision().queryFrom(TEST_12_30_40_987654321), NANOS); + } + + @Test + public void test_query_offset() { + assertEquals(TEST_12_30_40_987654321.query(Queries.offset()), null); + assertEquals(Queries.offset().queryFrom(TEST_12_30_40_987654321), null); + } + + @Test + public void test_query_zone() { + assertEquals(TEST_12_30_40_987654321.query(Queries.zone()), null); + assertEquals(Queries.zone().queryFrom(TEST_12_30_40_987654321), null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_query_null() { + TEST_12_30_40_987654321.query(null); + } + + //----------------------------------------------------------------------- + // get*() + //----------------------------------------------------------------------- + @DataProvider(name="sampleTimes") + Object[][] provider_sampleTimes() { + return new Object[][] { + {0, 0, 0, 0}, + {0, 0, 0, 1}, + {0, 0, 1, 0}, + {0, 0, 1, 1}, + {0, 1, 0, 0}, + {0, 1, 0, 1}, + {0, 1, 1, 0}, + {0, 1, 1, 1}, + {1, 0, 0, 0}, + {1, 0, 0, 1}, + {1, 0, 1, 0}, + {1, 0, 1, 1}, + {1, 1, 0, 0}, + {1, 1, 0, 1}, + {1, 1, 1, 0}, + {1, 1, 1, 1}, + }; + } + + //----------------------------------------------------------------------- + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_get(int h, int m, int s, int ns) { + LocalTime a = LocalTime.of(h, m, s, ns); + assertEquals(a.getHour(), h); + assertEquals(a.getMinute(), m); + assertEquals(a.getSecond(), s); + assertEquals(a.getNano(), ns); + } + + //----------------------------------------------------------------------- + // with() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_with_adjustment() { + final LocalTime sample = LocalTime.of(23, 5); + TemporalAdjuster adjuster = new TemporalAdjuster() { + @Override + public Temporal adjustInto(Temporal dateTime) { + return sample; + } + }; + assertEquals(TEST_12_30_40_987654321.with(adjuster), sample); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_with_adjustment_null() { + TEST_12_30_40_987654321.with((TemporalAdjuster) null); + } + + //----------------------------------------------------------------------- + // withHour() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withHour_normal() { + LocalTime t = TEST_12_30_40_987654321; + for (int i = 0; i < 24; i++) { + t = t.withHour(i); + assertEquals(t.getHour(), i); + } + } + + @Test(groups={"tck"}) + public void test_withHour_noChange_equal() { + LocalTime t = TEST_12_30_40_987654321.withHour(12); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_withHour_toMidnight_equal() { + LocalTime t = LocalTime.of(1, 0).withHour(0); + assertEquals(t, LocalTime.MIDNIGHT); + } + + @Test(groups={"tck"}) + public void test_withHour_toMidday_equal() { + LocalTime t = LocalTime.of(1, 0).withHour(12); + assertEquals(t, LocalTime.NOON); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withHour_hourTooLow() { + TEST_12_30_40_987654321.withHour(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withHour_hourTooHigh() { + TEST_12_30_40_987654321.withHour(24); + } + + //----------------------------------------------------------------------- + // withMinute() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withMinute_normal() { + LocalTime t = TEST_12_30_40_987654321; + for (int i = 0; i < 60; i++) { + t = t.withMinute(i); + assertEquals(t.getMinute(), i); + } + } + + @Test(groups={"tck"}) + public void test_withMinute_noChange_equal() { + LocalTime t = TEST_12_30_40_987654321.withMinute(30); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_withMinute_toMidnight_equal() { + LocalTime t = LocalTime.of(0, 1).withMinute(0); + assertEquals(t, LocalTime.MIDNIGHT); + } + + @Test(groups={"tck"}) + public void test_withMinute_toMidday_equals() { + LocalTime t = LocalTime.of(12, 1).withMinute(0); + assertEquals(t, LocalTime.NOON); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withMinute_minuteTooLow() { + TEST_12_30_40_987654321.withMinute(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withMinute_minuteTooHigh() { + TEST_12_30_40_987654321.withMinute(60); + } + + //----------------------------------------------------------------------- + // withSecond() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withSecond_normal() { + LocalTime t = TEST_12_30_40_987654321; + for (int i = 0; i < 60; i++) { + t = t.withSecond(i); + assertEquals(t.getSecond(), i); + } + } + + @Test(groups={"tck"}) + public void test_withSecond_noChange_equal() { + LocalTime t = TEST_12_30_40_987654321.withSecond(40); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_withSecond_toMidnight_equal() { + LocalTime t = LocalTime.of(0, 0, 1).withSecond(0); + assertEquals(t, LocalTime.MIDNIGHT); + } + + @Test(groups={"tck"}) + public void test_withSecond_toMidday_equal() { + LocalTime t = LocalTime.of(12, 0, 1).withSecond(0); + assertEquals(t, LocalTime.NOON); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withSecond_secondTooLow() { + TEST_12_30_40_987654321.withSecond(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withSecond_secondTooHigh() { + TEST_12_30_40_987654321.withSecond(60); + } + + //----------------------------------------------------------------------- + // withNano() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withNanoOfSecond_normal() { + LocalTime t = TEST_12_30_40_987654321; + t = t.withNano(1); + assertEquals(t.getNano(), 1); + t = t.withNano(10); + assertEquals(t.getNano(), 10); + t = t.withNano(100); + assertEquals(t.getNano(), 100); + t = t.withNano(999999999); + assertEquals(t.getNano(), 999999999); + } + + @Test(groups={"tck"}) + public void test_withNanoOfSecond_noChange_equal() { + LocalTime t = TEST_12_30_40_987654321.withNano(987654321); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_withNanoOfSecond_toMidnight_equal() { + LocalTime t = LocalTime.of(0, 0, 0, 1).withNano(0); + assertEquals(t, LocalTime.MIDNIGHT); + } + + @Test(groups={"tck"}) + public void test_withNanoOfSecond_toMidday_equal() { + LocalTime t = LocalTime.of(12, 0, 0, 1).withNano(0); + assertEquals(t, LocalTime.NOON); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withNanoOfSecond_nanoTooLow() { + TEST_12_30_40_987654321.withNano(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withNanoOfSecond_nanoTooHigh() { + TEST_12_30_40_987654321.withNano(1000000000); + } + + //----------------------------------------------------------------------- + // truncated(TemporalUnit) + //----------------------------------------------------------------------- + @DataProvider(name="truncatedToValid") + Object[][] data_truncatedToValid() { + return new Object[][] { + {LocalTime.of(1, 2, 3, 123_456_789), NANOS, LocalTime.of(1, 2, 3, 123_456_789)}, + {LocalTime.of(1, 2, 3, 123_456_789), MICROS, LocalTime.of(1, 2, 3, 123_456_000)}, + {LocalTime.of(1, 2, 3, 123_456_789), MILLIS, LocalTime.of(1, 2, 3, 1230_00_000)}, + {LocalTime.of(1, 2, 3, 123_456_789), SECONDS, LocalTime.of(1, 2, 3)}, + {LocalTime.of(1, 2, 3, 123_456_789), MINUTES, LocalTime.of(1, 2)}, + {LocalTime.of(1, 2, 3, 123_456_789), HOURS, LocalTime.of(1, 0)}, + {LocalTime.of(1, 2, 3, 123_456_789), DAYS, LocalTime.MIDNIGHT}, + }; + } + + @Test(groups={"tck"}, dataProvider="truncatedToValid") + public void test_truncatedTo_valid(LocalTime input, TemporalUnit unit, LocalTime expected) { + assertEquals(input.truncatedTo(unit), expected); + } + + @DataProvider(name="truncatedToInvalid") + Object[][] data_truncatedToInvalid() { + return new Object[][] { + {LocalTime.of(1, 2, 3, 123_456_789), WEEKS}, + {LocalTime.of(1, 2, 3, 123_456_789), MONTHS}, + {LocalTime.of(1, 2, 3, 123_456_789), YEARS}, + }; + } + + @Test(groups={"tck"}, dataProvider="truncatedToInvalid", expectedExceptions=DateTimeException.class) + public void test_truncatedTo_invalid(LocalTime input, TemporalUnit unit) { + input.truncatedTo(unit); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_truncatedTo_null() { + TEST_12_30_40_987654321.truncatedTo(null); + } + + //----------------------------------------------------------------------- + // plus(PlusAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plus_Adjuster_positiveHours() { + TemporalAdder period = MockSimplePeriod.of(7, ChronoUnit.HOURS); + LocalTime t = TEST_12_30_40_987654321.plus(period); + assertEquals(t, LocalTime.of(19, 30, 40, 987654321)); + } + + @Test(groups={"tck"}) + public void test_plus_Adjuster_negativeMinutes() { + TemporalAdder period = MockSimplePeriod.of(-25, ChronoUnit.MINUTES); + LocalTime t = TEST_12_30_40_987654321.plus(period); + assertEquals(t, LocalTime.of(12, 5, 40, 987654321)); + } + + @Test(groups={"tck"}) + public void test_plus_Adjuster_zero() { + TemporalAdder period = Period.ZERO; + LocalTime t = TEST_12_30_40_987654321.plus(period); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_plus_Adjuster_wrap() { + TemporalAdder p = Period.ofTime(1, 0, 0); + LocalTime t = LocalTime.of(23, 30).plus(p); + assertEquals(t, LocalTime.of(0, 30)); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_plus_Adjuster_dateNotAllowed() { + TemporalAdder period = MockSimplePeriod.of(7, ChronoUnit.MONTHS); + TEST_12_30_40_987654321.plus(period); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_plus_Adjuster_null() { + TEST_12_30_40_987654321.plus((TemporalAdder) null); + } + + //----------------------------------------------------------------------- + // plus(long,TemporalUnit) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plus_longTemporalUnit_positiveHours() { + LocalTime t = TEST_12_30_40_987654321.plus(7, ChronoUnit.HOURS); + assertEquals(t, LocalTime.of(19, 30, 40, 987654321)); + } + + @Test(groups={"tck"}) + public void test_plus_longTemporalUnit_negativeMinutes() { + LocalTime t = TEST_12_30_40_987654321.plus(-25, ChronoUnit.MINUTES); + assertEquals(t, LocalTime.of(12, 5, 40, 987654321)); + } + + @Test(groups={"tck"}) + public void test_plus_longTemporalUnit_zero() { + LocalTime t = TEST_12_30_40_987654321.plus(0, ChronoUnit.MINUTES); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_plus_longTemporalUnit_invalidUnit() { + for (TemporalUnit unit : INVALID_UNITS) { + try { + TEST_12_30_40_987654321.plus(1, unit); + fail("Unit should not be allowed " + unit); + } catch (DateTimeException ex) { + // expected + } + } + } + + @Test(groups={"tck"}) + public void test_plus_longTemporalUnit_multiples() { + assertEquals(TEST_12_30_40_987654321.plus(0, DAYS), TEST_12_30_40_987654321); + assertEquals(TEST_12_30_40_987654321.plus(1, DAYS), TEST_12_30_40_987654321); + assertEquals(TEST_12_30_40_987654321.plus(2, DAYS), TEST_12_30_40_987654321); + assertEquals(TEST_12_30_40_987654321.plus(-3, DAYS), TEST_12_30_40_987654321); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_plus_longTemporalUnit_null() { + TEST_12_30_40_987654321.plus(1, (TemporalUnit) null); + } + + //----------------------------------------------------------------------- + // plus(adjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plus_adjuster() { + Period p = Period.ofTime(0, 0, 62, 3); + LocalTime t = TEST_12_30_40_987654321.plus(p); + assertEquals(t, LocalTime.of(12, 31, 42, 987654324)); + } + + @Test(groups={"tck"}) + public void test_plus_adjuster_big() { + Period p = Period.ofTime(0, 0, 0, Long.MAX_VALUE); + LocalTime t = TEST_12_30_40_987654321.plus(p); + assertEquals(t, TEST_12_30_40_987654321.plusNanos(Long.MAX_VALUE)); + } + + @Test(groups={"tck"}) + public void test_plus_adjuster_zero_equal() { + LocalTime t = TEST_12_30_40_987654321.plus(Period.ZERO); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_plus_adjuster_wrap() { + Period p = Period.ofTime(1, 0, 0); + LocalTime t = LocalTime.of(23, 30).plus(p); + assertEquals(t, LocalTime.of(0, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_plus_adjuster_null() { + TEST_12_30_40_987654321.plus((TemporalAdder) null); + } + + //----------------------------------------------------------------------- + // plusHours() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusHours_one() { + LocalTime t = LocalTime.MIDNIGHT; + for (int i = 0; i < 50; i++) { + t = t.plusHours(1); + assertEquals(t.getHour(), (i + 1) % 24); + } + } + + @Test(groups={"tck"}) + public void test_plusHours_fromZero() { + LocalTime base = LocalTime.MIDNIGHT; + for (int i = -50; i < 50; i++) { + LocalTime t = base.plusHours(i); + assertEquals(t.getHour(), (i + 72) % 24); + } + } + + @Test(groups={"tck"}) + public void test_plusHours_fromOne() { + LocalTime base = LocalTime.of(1, 0); + for (int i = -50; i < 50; i++) { + LocalTime t = base.plusHours(i); + assertEquals(t.getHour(), (1 + i + 72) % 24); + } + } + + @Test(groups={"tck"}) + public void test_plusHours_noChange_equal() { + LocalTime t = TEST_12_30_40_987654321.plusHours(0); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_plusHours_toMidnight_equal() { + LocalTime t = LocalTime.of(23, 0).plusHours(1); + assertEquals(t, LocalTime.MIDNIGHT); + } + + @Test(groups={"tck"}) + public void test_plusHours_toMidday_equal() { + LocalTime t = LocalTime.of(11, 0).plusHours(1); + assertEquals(t, LocalTime.NOON); + } + + @Test(groups={"tck"}) + public void test_plusHours_big() { + LocalTime t = LocalTime.of(2, 30).plusHours(Long.MAX_VALUE); + int hours = (int) (Long.MAX_VALUE % 24L); + assertEquals(t, LocalTime.of(2, 30).plusHours(hours)); + } + + //----------------------------------------------------------------------- + // plusMinutes() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusMinutes_one() { + LocalTime t = LocalTime.MIDNIGHT; + int hour = 0; + int min = 0; + for (int i = 0; i < 70; i++) { + t = t.plusMinutes(1); + min++; + if (min == 60) { + hour++; + min = 0; + } + assertEquals(t.getHour(), hour); + assertEquals(t.getMinute(), min); + } + } + + @Test(groups={"tck"}) + public void test_plusMinutes_fromZero() { + LocalTime base = LocalTime.MIDNIGHT; + int hour; + int min; + for (int i = -70; i < 70; i++) { + LocalTime t = base.plusMinutes(i); + if (i < -60) { + hour = 22; + min = i + 120; + } else if (i < 0) { + hour = 23; + min = i + 60; + } else if (i >= 60) { + hour = 1; + min = i - 60; + } else { + hour = 0; + min = i; + } + assertEquals(t.getHour(), hour); + assertEquals(t.getMinute(), min); + } + } + + @Test(groups={"tck"}) + public void test_plusMinutes_noChange_equal() { + LocalTime t = TEST_12_30_40_987654321.plusMinutes(0); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_plusMinutes_noChange_oneDay_equal() { + LocalTime t = TEST_12_30_40_987654321.plusMinutes(24 * 60); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_plusMinutes_toMidnight_equal() { + LocalTime t = LocalTime.of(23, 59).plusMinutes(1); + assertEquals(t, LocalTime.MIDNIGHT); + } + + @Test(groups={"tck"}) + public void test_plusMinutes_toMidday_equal() { + LocalTime t = LocalTime.of(11, 59).plusMinutes(1); + assertEquals(t, LocalTime.NOON); + } + + @Test(groups={"tck"}) + public void test_plusMinutes_big() { + LocalTime t = LocalTime.of(2, 30).plusMinutes(Long.MAX_VALUE); + int mins = (int) (Long.MAX_VALUE % (24L * 60L)); + assertEquals(t, LocalTime.of(2, 30).plusMinutes(mins)); + } + + //----------------------------------------------------------------------- + // plusSeconds() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusSeconds_one() { + LocalTime t = LocalTime.MIDNIGHT; + int hour = 0; + int min = 0; + int sec = 0; + for (int i = 0; i < 3700; i++) { + t = t.plusSeconds(1); + sec++; + if (sec == 60) { + min++; + sec = 0; + } + if (min == 60) { + hour++; + min = 0; + } + assertEquals(t.getHour(), hour); + assertEquals(t.getMinute(), min); + assertEquals(t.getSecond(), sec); + } + } + + @DataProvider(name="plusSeconds_fromZero") + Iterator plusSeconds_fromZero() { + return new Iterator() { + int delta = 30; + int i = -3660; + int hour = 22; + int min = 59; + int sec = 0; + + public boolean hasNext() { + return i <= 3660; + } + + public Object[] next() { + final Object[] ret = new Object[] {i, hour, min, sec}; + i += delta; + sec += delta; + + if (sec >= 60) { + min++; + sec -= 60; + + if (min == 60) { + hour++; + min = 0; + + if (hour == 24) { + hour = 0; + } + } + } + + return ret; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Test(dataProvider="plusSeconds_fromZero", groups={"tck"}) + public void test_plusSeconds_fromZero(int seconds, int hour, int min, int sec) { + LocalTime base = LocalTime.MIDNIGHT; + LocalTime t = base.plusSeconds(seconds); + + assertEquals(hour, t.getHour()); + assertEquals(min, t.getMinute()); + assertEquals(sec, t.getSecond()); + } + + @Test(groups={"tck"}) + public void test_plusSeconds_noChange_equal() { + LocalTime t = TEST_12_30_40_987654321.plusSeconds(0); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_plusSeconds_noChange_oneDay_equal() { + LocalTime t = TEST_12_30_40_987654321.plusSeconds(24 * 60 * 60); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_plusSeconds_toMidnight_equal() { + LocalTime t = LocalTime.of(23, 59, 59).plusSeconds(1); + assertEquals(t, LocalTime.MIDNIGHT); + } + + @Test(groups={"tck"}) + public void test_plusSeconds_toMidday_equal() { + LocalTime t = LocalTime.of(11, 59, 59).plusSeconds(1); + assertEquals(t, LocalTime.NOON); + } + + //----------------------------------------------------------------------- + // plusNanos() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusNanos_halfABillion() { + LocalTime t = LocalTime.MIDNIGHT; + int hour = 0; + int min = 0; + int sec = 0; + int nanos = 0; + for (long i = 0; i < 3700 * 1000000000L; i+= 500000000) { + t = t.plusNanos(500000000); + nanos += 500000000; + if (nanos == 1000000000) { + sec++; + nanos = 0; + } + if (sec == 60) { + min++; + sec = 0; + } + if (min == 60) { + hour++; + min = 0; + } + assertEquals(t.getHour(), hour); + assertEquals(t.getMinute(), min); + assertEquals(t.getSecond(), sec); + assertEquals(t.getNano(), nanos); + } + } + + @DataProvider(name="plusNanos_fromZero") + Iterator plusNanos_fromZero() { + return new Iterator() { + long delta = 7500000000L; + long i = -3660 * 1000000000L; + int hour = 22; + int min = 59; + int sec = 0; + long nanos = 0; + + public boolean hasNext() { + return i <= 3660 * 1000000000L; + } + + public Object[] next() { + final Object[] ret = new Object[] {i, hour, min, sec, (int)nanos}; + i += delta; + nanos += delta; + + if (nanos >= 1000000000L) { + sec += nanos / 1000000000L; + nanos %= 1000000000L; + + if (sec >= 60) { + min++; + sec %= 60; + + if (min == 60) { + hour++; + min = 0; + + if (hour == 24) { + hour = 0; + } + } + } + } + + return ret; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Test(dataProvider="plusNanos_fromZero", groups={"tck"}) + public void test_plusNanos_fromZero(long nanoseconds, int hour, int min, int sec, int nanos) { + LocalTime base = LocalTime.MIDNIGHT; + LocalTime t = base.plusNanos(nanoseconds); + + assertEquals(hour, t.getHour()); + assertEquals(min, t.getMinute()); + assertEquals(sec, t.getSecond()); + assertEquals(nanos, t.getNano()); + } + + @Test(groups={"tck"}) + public void test_plusNanos_noChange_equal() { + LocalTime t = TEST_12_30_40_987654321.plusNanos(0); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_plusNanos_noChange_oneDay_equal() { + LocalTime t = TEST_12_30_40_987654321.plusNanos(24 * 60 * 60 * 1000000000L); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_plusNanos_toMidnight_equal() { + LocalTime t = LocalTime.of(23, 59, 59, 999999999).plusNanos(1); + assertEquals(t, LocalTime.MIDNIGHT); + } + + @Test(groups={"tck"}) + public void test_plusNanos_toMidday_equal() { + LocalTime t = LocalTime.of(11, 59, 59, 999999999).plusNanos(1); + assertEquals(t, LocalTime.NOON); + } + + //----------------------------------------------------------------------- + // minus(MinusAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minus_Adjuster() { + TemporalSubtractor p = Period.ofTime(0, 0, 62, 3); + LocalTime t = TEST_12_30_40_987654321.minus(p); + assertEquals(t, LocalTime.of(12, 29, 38, 987654318)); + } + + @Test(groups={"tck"}) + public void test_minus_Adjuster_positiveHours() { + TemporalSubtractor period = MockSimplePeriod.of(7, ChronoUnit.HOURS); + LocalTime t = TEST_12_30_40_987654321.minus(period); + assertEquals(t, LocalTime.of(5, 30, 40, 987654321)); + } + + @Test(groups={"tck"}) + public void test_minus_Adjuster_negativeMinutes() { + TemporalSubtractor period = MockSimplePeriod.of(-25, ChronoUnit.MINUTES); + LocalTime t = TEST_12_30_40_987654321.minus(period); + assertEquals(t, LocalTime.of(12, 55, 40, 987654321)); + } + + @Test(groups={"tck"}) + public void test_minus_Adjuster_big1() { + TemporalSubtractor p = Period.ofTime(0, 0, 0, Long.MAX_VALUE); + LocalTime t = TEST_12_30_40_987654321.minus(p); + assertEquals(t, TEST_12_30_40_987654321.minusNanos(Long.MAX_VALUE)); + } + + @Test(groups={"tck"}) + public void test_minus_Adjuster_zero() { + TemporalSubtractor p = Period.ZERO; + LocalTime t = TEST_12_30_40_987654321.minus(p); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_minus_Adjuster_wrap() { + TemporalSubtractor p = Period.ofTime(1, 0, 0); + LocalTime t = LocalTime.of(0, 30).minus(p); + assertEquals(t, LocalTime.of(23, 30)); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_minus_Adjuster_dateNotAllowed() { + TemporalSubtractor period = MockSimplePeriod.of(7, ChronoUnit.MONTHS); + TEST_12_30_40_987654321.minus(period); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_minus_Adjuster_null() { + TEST_12_30_40_987654321.minus((TemporalSubtractor) null); + } + + //----------------------------------------------------------------------- + // minus(long,TemporalUnit) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minus_longTemporalUnit_positiveHours() { + LocalTime t = TEST_12_30_40_987654321.minus(7, ChronoUnit.HOURS); + assertEquals(t, LocalTime.of(5, 30, 40, 987654321)); + } + + @Test(groups={"tck"}) + public void test_minus_longTemporalUnit_negativeMinutes() { + LocalTime t = TEST_12_30_40_987654321.minus(-25, ChronoUnit.MINUTES); + assertEquals(t, LocalTime.of(12, 55, 40, 987654321)); + } + + @Test(groups={"tck"}) + public void test_minus_longTemporalUnit_zero() { + LocalTime t = TEST_12_30_40_987654321.minus(0, ChronoUnit.MINUTES); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_minus_longTemporalUnit_invalidUnit() { + for (TemporalUnit unit : INVALID_UNITS) { + try { + TEST_12_30_40_987654321.minus(1, unit); + fail("Unit should not be allowed " + unit); + } catch (DateTimeException ex) { + // expected + } + } + } + + @Test(groups={"tck"}) + public void test_minus_longTemporalUnit_long_multiples() { + assertEquals(TEST_12_30_40_987654321.minus(0, DAYS), TEST_12_30_40_987654321); + assertEquals(TEST_12_30_40_987654321.minus(1, DAYS), TEST_12_30_40_987654321); + assertEquals(TEST_12_30_40_987654321.minus(2, DAYS), TEST_12_30_40_987654321); + assertEquals(TEST_12_30_40_987654321.minus(-3, DAYS), TEST_12_30_40_987654321); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_minus_longTemporalUnit_null() { + TEST_12_30_40_987654321.minus(1, (TemporalUnit) null); + } + + //----------------------------------------------------------------------- + // minusHours() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusHours_one() { + LocalTime t = LocalTime.MIDNIGHT; + for (int i = 0; i < 50; i++) { + t = t.minusHours(1); + assertEquals(t.getHour(), (((-i + 23) % 24) + 24) % 24, String.valueOf(i)); + } + } + + @Test(groups={"tck"}) + public void test_minusHours_fromZero() { + LocalTime base = LocalTime.MIDNIGHT; + for (int i = -50; i < 50; i++) { + LocalTime t = base.minusHours(i); + assertEquals(t.getHour(), ((-i % 24) + 24) % 24); + } + } + + @Test(groups={"tck"}) + public void test_minusHours_fromOne() { + LocalTime base = LocalTime.of(1, 0); + for (int i = -50; i < 50; i++) { + LocalTime t = base.minusHours(i); + assertEquals(t.getHour(), (1 + (-i % 24) + 24) % 24); + } + } + + @Test(groups={"tck"}) + public void test_minusHours_noChange_equal() { + LocalTime t = TEST_12_30_40_987654321.minusHours(0); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_minusHours_toMidnight_equal() { + LocalTime t = LocalTime.of(1, 0).minusHours(1); + assertEquals(t, LocalTime.MIDNIGHT); + } + + @Test(groups={"tck"}) + public void test_minusHours_toMidday_equal() { + LocalTime t = LocalTime.of(13, 0).minusHours(1); + assertEquals(t, LocalTime.NOON); + } + + @Test(groups={"tck"}) + public void test_minusHours_big() { + LocalTime t = LocalTime.of(2, 30).minusHours(Long.MAX_VALUE); + int hours = (int) (Long.MAX_VALUE % 24L); + assertEquals(t, LocalTime.of(2, 30).minusHours(hours)); + } + + //----------------------------------------------------------------------- + // minusMinutes() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusMinutes_one() { + LocalTime t = LocalTime.MIDNIGHT; + int hour = 0; + int min = 0; + for (int i = 0; i < 70; i++) { + t = t.minusMinutes(1); + min--; + if (min == -1) { + hour--; + min = 59; + + if (hour == -1) { + hour = 23; + } + } + assertEquals(t.getHour(), hour); + assertEquals(t.getMinute(), min); + } + } + + @Test(groups={"tck"}) + public void test_minusMinutes_fromZero() { + LocalTime base = LocalTime.MIDNIGHT; + int hour = 22; + int min = 49; + for (int i = 70; i > -70; i--) { + LocalTime t = base.minusMinutes(i); + min++; + + if (min == 60) { + hour++; + min = 0; + + if (hour == 24) { + hour = 0; + } + } + + assertEquals(t.getHour(), hour); + assertEquals(t.getMinute(), min); + } + } + + @Test(groups={"tck"}) + public void test_minusMinutes_noChange_equal() { + LocalTime t = TEST_12_30_40_987654321.minusMinutes(0); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_minusMinutes_noChange_oneDay_equal() { + LocalTime t = TEST_12_30_40_987654321.minusMinutes(24 * 60); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_minusMinutes_toMidnight_equal() { + LocalTime t = LocalTime.of(0, 1).minusMinutes(1); + assertEquals(t, LocalTime.MIDNIGHT); + } + + @Test(groups={"tck"}) + public void test_minusMinutes_toMidday_equals() { + LocalTime t = LocalTime.of(12, 1).minusMinutes(1); + assertEquals(t, LocalTime.NOON); + } + + @Test(groups={"tck"}) + public void test_minusMinutes_big() { + LocalTime t = LocalTime.of(2, 30).minusMinutes(Long.MAX_VALUE); + int mins = (int) (Long.MAX_VALUE % (24L * 60L)); + assertEquals(t, LocalTime.of(2, 30).minusMinutes(mins)); + } + + //----------------------------------------------------------------------- + // minusSeconds() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusSeconds_one() { + LocalTime t = LocalTime.MIDNIGHT; + int hour = 0; + int min = 0; + int sec = 0; + for (int i = 0; i < 3700; i++) { + t = t.minusSeconds(1); + sec--; + if (sec == -1) { + min--; + sec = 59; + + if (min == -1) { + hour--; + min = 59; + + if (hour == -1) { + hour = 23; + } + } + } + assertEquals(t.getHour(), hour); + assertEquals(t.getMinute(), min); + assertEquals(t.getSecond(), sec); + } + } + + @DataProvider(name="minusSeconds_fromZero") + Iterator minusSeconds_fromZero() { + return new Iterator() { + int delta = 30; + int i = 3660; + int hour = 22; + int min = 59; + int sec = 0; + + public boolean hasNext() { + return i >= -3660; + } + + public Object[] next() { + final Object[] ret = new Object[] {i, hour, min, sec}; + i -= delta; + sec += delta; + + if (sec >= 60) { + min++; + sec -= 60; + + if (min == 60) { + hour++; + min = 0; + + if (hour == 24) { + hour = 0; + } + } + } + + return ret; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Test(dataProvider="minusSeconds_fromZero", groups={"tck"}) + public void test_minusSeconds_fromZero(int seconds, int hour, int min, int sec) { + LocalTime base = LocalTime.MIDNIGHT; + LocalTime t = base.minusSeconds(seconds); + + assertEquals(t.getHour(), hour); + assertEquals(t.getMinute(), min); + assertEquals(t.getSecond(), sec); + } + + @Test(groups={"tck"}) + public void test_minusSeconds_noChange_equal() { + LocalTime t = TEST_12_30_40_987654321.minusSeconds(0); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_minusSeconds_noChange_oneDay_equal() { + LocalTime t = TEST_12_30_40_987654321.minusSeconds(24 * 60 * 60); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_minusSeconds_toMidnight_equal() { + LocalTime t = LocalTime.of(0, 0, 1).minusSeconds(1); + assertEquals(t, LocalTime.MIDNIGHT); + } + + @Test(groups={"tck"}) + public void test_minusSeconds_toMidday_equal() { + LocalTime t = LocalTime.of(12, 0, 1).minusSeconds(1); + assertEquals(t, LocalTime.NOON); + } + + @Test(groups={"tck"}) + public void test_minusSeconds_big() { + LocalTime t = LocalTime.of(2, 30).minusSeconds(Long.MAX_VALUE); + int secs = (int) (Long.MAX_VALUE % (24L * 60L * 60L)); + assertEquals(t, LocalTime.of(2, 30).minusSeconds(secs)); + } + + //----------------------------------------------------------------------- + // minusNanos() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusNanos_halfABillion() { + LocalTime t = LocalTime.MIDNIGHT; + int hour = 0; + int min = 0; + int sec = 0; + int nanos = 0; + for (long i = 0; i < 3700 * 1000000000L; i+= 500000000) { + t = t.minusNanos(500000000); + nanos -= 500000000; + + if (nanos < 0) { + sec--; + nanos += 1000000000; + + if (sec == -1) { + min--; + sec += 60; + + if (min == -1) { + hour--; + min += 60; + + if (hour == -1) { + hour += 24; + } + } + } + } + + assertEquals(t.getHour(), hour); + assertEquals(t.getMinute(), min); + assertEquals(t.getSecond(), sec); + assertEquals(t.getNano(), nanos); + } + } + + @DataProvider(name="minusNanos_fromZero") + Iterator minusNanos_fromZero() { + return new Iterator() { + long delta = 7500000000L; + long i = 3660 * 1000000000L; + int hour = 22; + int min = 59; + int sec = 0; + long nanos = 0; + + public boolean hasNext() { + return i >= -3660 * 1000000000L; + } + + public Object[] next() { + final Object[] ret = new Object[] {i, hour, min, sec, (int)nanos}; + i -= delta; + nanos += delta; + + if (nanos >= 1000000000L) { + sec += nanos / 1000000000L; + nanos %= 1000000000L; + + if (sec >= 60) { + min++; + sec %= 60; + + if (min == 60) { + hour++; + min = 0; + + if (hour == 24) { + hour = 0; + } + } + } + } + + return ret; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Test(dataProvider="minusNanos_fromZero", groups={"tck"}) + public void test_minusNanos_fromZero(long nanoseconds, int hour, int min, int sec, int nanos) { + LocalTime base = LocalTime.MIDNIGHT; + LocalTime t = base.minusNanos(nanoseconds); + + assertEquals(hour, t.getHour()); + assertEquals(min, t.getMinute()); + assertEquals(sec, t.getSecond()); + assertEquals(nanos, t.getNano()); + } + + @Test(groups={"tck"}) + public void test_minusNanos_noChange_equal() { + LocalTime t = TEST_12_30_40_987654321.minusNanos(0); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_minusNanos_noChange_oneDay_equal() { + LocalTime t = TEST_12_30_40_987654321.minusNanos(24 * 60 * 60 * 1000000000L); + assertEquals(t, TEST_12_30_40_987654321); + } + + @Test(groups={"tck"}) + public void test_minusNanos_toMidnight_equal() { + LocalTime t = LocalTime.of(0, 0, 0, 1).minusNanos(1); + assertEquals(t, LocalTime.MIDNIGHT); + } + + @Test(groups={"tck"}) + public void test_minusNanos_toMidday_equal() { + LocalTime t = LocalTime.of(12, 0, 0, 1).minusNanos(1); + assertEquals(t, LocalTime.NOON); + } + + //----------------------------------------------------------------------- + // atDate() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atDate() { + LocalTime t = LocalTime.of(11, 30); + assertEquals(t.atDate(LocalDate.of(2012, 6, 30)), LocalDateTime.of(2012, 6, 30, 11, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_atDate_nullDate() { + TEST_12_30_40_987654321.atDate((LocalDate) null); + } + + //----------------------------------------------------------------------- + // atOffset() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atOffset() { + LocalTime t = LocalTime.of(11, 30); + assertEquals(t.atOffset(OFFSET_PTWO), OffsetTime.of(LocalTime.of(11, 30), OFFSET_PTWO)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_atOffset_nullZoneOffset() { + LocalTime t = LocalTime.of(11, 30); + t.atOffset((ZoneOffset) null); + } + + //----------------------------------------------------------------------- + // toSecondOfDay() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toSecondOfDay() { + LocalTime t = LocalTime.of(0, 0); + for (int i = 0; i < 24 * 60 * 60; i++) { + assertEquals(t.toSecondOfDay(), i); + t = t.plusSeconds(1); + } + } + + @Test(groups={"tck"}) + public void test_toSecondOfDay_fromNanoOfDay_symmetry() { + LocalTime t = LocalTime.of(0, 0); + for (int i = 0; i < 24 * 60 * 60; i++) { + assertEquals(LocalTime.ofSecondOfDay(t.toSecondOfDay()), t); + t = t.plusSeconds(1); + } + } + + //----------------------------------------------------------------------- + // toNanoOfDay() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toNanoOfDay() { + LocalTime t = LocalTime.of(0, 0); + for (int i = 0; i < 1000000; i++) { + assertEquals(t.toNanoOfDay(), i); + t = t.plusNanos(1); + } + t = LocalTime.of(0, 0); + for (int i = 1; i <= 1000000; i++) { + t = t.minusNanos(1); + assertEquals(t.toNanoOfDay(), 24 * 60 * 60 * 1000000000L - i); + } + } + + @Test(groups={"tck"}) + public void test_toNanoOfDay_fromNanoOfDay_symmetry() { + LocalTime t = LocalTime.of(0, 0); + for (int i = 0; i < 1000000; i++) { + assertEquals(LocalTime.ofNanoOfDay(t.toNanoOfDay()), t); + t = t.plusNanos(1); + } + t = LocalTime.of(0, 0); + for (int i = 1; i <= 1000000; i++) { + t = t.minusNanos(1); + assertEquals(LocalTime.ofNanoOfDay(t.toNanoOfDay()), t); + } + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_comparisons() { + doTest_comparisons_LocalTime( + LocalTime.MIDNIGHT, + LocalTime.of(0, 0, 0, 999999999), + LocalTime.of(0, 0, 59, 0), + LocalTime.of(0, 0, 59, 999999999), + LocalTime.of(0, 59, 0, 0), + LocalTime.of(0, 59, 0, 999999999), + LocalTime.of(0, 59, 59, 0), + LocalTime.of(0, 59, 59, 999999999), + LocalTime.NOON, + LocalTime.of(12, 0, 0, 999999999), + LocalTime.of(12, 0, 59, 0), + LocalTime.of(12, 0, 59, 999999999), + LocalTime.of(12, 59, 0, 0), + LocalTime.of(12, 59, 0, 999999999), + LocalTime.of(12, 59, 59, 0), + LocalTime.of(12, 59, 59, 999999999), + LocalTime.of(23, 0, 0, 0), + LocalTime.of(23, 0, 0, 999999999), + LocalTime.of(23, 0, 59, 0), + LocalTime.of(23, 0, 59, 999999999), + LocalTime.of(23, 59, 0, 0), + LocalTime.of(23, 59, 0, 999999999), + LocalTime.of(23, 59, 59, 0), + LocalTime.of(23, 59, 59, 999999999) + ); + } + + void doTest_comparisons_LocalTime(LocalTime... localTimes) { + for (int i = 0; i < localTimes.length; i++) { + LocalTime a = localTimes[i]; + for (int j = 0; j < localTimes.length; j++) { + LocalTime b = localTimes[j]; + if (i < j) { + assertTrue(a.compareTo(b) < 0, a + " <=> " + b); + assertEquals(a.isBefore(b), true, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else if (i > j) { + assertTrue(a.compareTo(b) > 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), true, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else { + assertEquals(a.compareTo(b), 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), true, a + " <=> " + b); + } + } + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_compareTo_ObjectNull() { + TEST_12_30_40_987654321.compareTo(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isBefore_ObjectNull() { + TEST_12_30_40_987654321.isBefore(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isAfter_ObjectNull() { + TEST_12_30_40_987654321.isAfter(null); + } + + @Test(expectedExceptions=ClassCastException.class, groups={"tck"}) + @SuppressWarnings({"unchecked", "rawtypes"}) + public void compareToNonLocalTime() { + Comparable c = TEST_12_30_40_987654321; + c.compareTo(new Object()); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_true(int h, int m, int s, int n) { + LocalTime a = LocalTime.of(h, m, s, n); + LocalTime b = LocalTime.of(h, m, s, n); + assertEquals(a.equals(b), true); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_hour_differs(int h, int m, int s, int n) { + LocalTime a = LocalTime.of(h, m, s, n); + LocalTime b = LocalTime.of(h + 1, m, s, n); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_minute_differs(int h, int m, int s, int n) { + LocalTime a = LocalTime.of(h, m, s, n); + LocalTime b = LocalTime.of(h, m + 1, s, n); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_second_differs(int h, int m, int s, int n) { + LocalTime a = LocalTime.of(h, m, s, n); + LocalTime b = LocalTime.of(h, m, s + 1, n); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_nano_differs(int h, int m, int s, int n) { + LocalTime a = LocalTime.of(h, m, s, n); + LocalTime b = LocalTime.of(h, m, s, n + 1); + assertEquals(a.equals(b), false); + } + + @Test(groups={"tck"}) + public void test_equals_itself_true() { + assertEquals(TEST_12_30_40_987654321.equals(TEST_12_30_40_987654321), true); + } + + @Test(groups={"tck"}) + public void test_equals_string_false() { + assertEquals(TEST_12_30_40_987654321.equals("2007-07-15"), false); + } + + @Test(groups={"tck"}) + public void test_equals_null_false() { + assertEquals(TEST_12_30_40_987654321.equals(null), false); + } + + //----------------------------------------------------------------------- + // hashCode() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_hashCode_same(int h, int m, int s, int n) { + LocalTime a = LocalTime.of(h, m, s, n); + LocalTime b = LocalTime.of(h, m, s, n); + assertEquals(a.hashCode(), b.hashCode()); + } + + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_hashCode_hour_differs(int h, int m, int s, int n) { + LocalTime a = LocalTime.of(h, m, s, n); + LocalTime b = LocalTime.of(h + 1, m, s, n); + assertEquals(a.hashCode() == b.hashCode(), false); + } + + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_hashCode_minute_differs(int h, int m, int s, int n) { + LocalTime a = LocalTime.of(h, m, s, n); + LocalTime b = LocalTime.of(h, m + 1, s, n); + assertEquals(a.hashCode() == b.hashCode(), false); + } + + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_hashCode_second_differs(int h, int m, int s, int n) { + LocalTime a = LocalTime.of(h, m, s, n); + LocalTime b = LocalTime.of(h, m, s + 1, n); + assertEquals(a.hashCode() == b.hashCode(), false); + } + + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_hashCode_nano_differs(int h, int m, int s, int n) { + LocalTime a = LocalTime.of(h, m, s, n); + LocalTime b = LocalTime.of(h, m, s, n + 1); + assertEquals(a.hashCode() == b.hashCode(), false); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="sampleToString") + Object[][] provider_sampleToString() { + return new Object[][] { + {0, 0, 0, 0, "00:00"}, + {1, 0, 0, 0, "01:00"}, + {23, 0, 0, 0, "23:00"}, + {0, 1, 0, 0, "00:01"}, + {12, 30, 0, 0, "12:30"}, + {23, 59, 0, 0, "23:59"}, + {0, 0, 1, 0, "00:00:01"}, + {0, 0, 59, 0, "00:00:59"}, + {0, 0, 0, 100000000, "00:00:00.100"}, + {0, 0, 0, 10000000, "00:00:00.010"}, + {0, 0, 0, 1000000, "00:00:00.001"}, + {0, 0, 0, 100000, "00:00:00.000100"}, + {0, 0, 0, 10000, "00:00:00.000010"}, + {0, 0, 0, 1000, "00:00:00.000001"}, + {0, 0, 0, 100, "00:00:00.000000100"}, + {0, 0, 0, 10, "00:00:00.000000010"}, + {0, 0, 0, 1, "00:00:00.000000001"}, + {0, 0, 0, 999999999, "00:00:00.999999999"}, + {0, 0, 0, 99999999, "00:00:00.099999999"}, + {0, 0, 0, 9999999, "00:00:00.009999999"}, + {0, 0, 0, 999999, "00:00:00.000999999"}, + {0, 0, 0, 99999, "00:00:00.000099999"}, + {0, 0, 0, 9999, "00:00:00.000009999"}, + {0, 0, 0, 999, "00:00:00.000000999"}, + {0, 0, 0, 99, "00:00:00.000000099"}, + {0, 0, 0, 9, "00:00:00.000000009"}, + }; + } + + @Test(dataProvider="sampleToString", groups={"tck"}) + public void test_toString(int h, int m, int s, int n, String expected) { + LocalTime t = LocalTime.of(h, m, s, n); + String str = t.toString(); + assertEquals(str, expected); + } + + //----------------------------------------------------------------------- + // toString(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("H m s"); + String t = LocalTime.of(11, 30, 45).toString(f); + assertEquals(t, "11 30 45"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_toString_formatter_null() { + LocalTime.of(11, 30, 45).toString(null); + } + +} diff --git a/test/java/time/tck/java/time/TCKMonth.java b/test/java/time/tck/java/time/TCKMonth.java new file mode 100644 index 0000000000000000000000000000000000000000..6f6170d06f89675c02b763d9a432553c11d6c4b7 --- /dev/null +++ b/test/java/time/tck/java/time/TCKMonth.java @@ -0,0 +1,486 @@ +/* + * 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. + * + * 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 tck.java.time; + +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static org.testng.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; +import java.time.format.TextStyle; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.ISOChrono; +import java.time.temporal.JulianFields; +import java.time.temporal.Queries; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test Month. + */ +@Test +public class TCKMonth extends AbstractDateTimeTest { + + private static final int MAX_LENGTH = 12; + + //----------------------------------------------------------------------- + @Override + protected List samples() { + TemporalAccessor[] array = {Month.JANUARY, Month.JUNE, Month.DECEMBER, }; + return Arrays.asList(array); + } + + @Override + protected List validFields() { + TemporalField[] array = { + MONTH_OF_YEAR, + }; + return Arrays.asList(array); + } + + @Override + protected List invalidFields() { + List list = new ArrayList<>(Arrays.asList(ChronoField.values())); + list.removeAll(validFields()); + list.add(JulianFields.JULIAN_DAY); + list.add(JulianFields.MODIFIED_JULIAN_DAY); + list.add(JulianFields.RATA_DIE); + return list; + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_int_singleton() { + for (int i = 1; i <= MAX_LENGTH; i++) { + Month test = Month.of(i); + assertEquals(test.getValue(), i); + } + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_tooLow() { + Month.of(0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_tooHigh() { + Month.of(13); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_CalendricalObject() { + assertEquals(Month.from(LocalDate.of(2011, 6, 6)), Month.JUNE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_CalendricalObject_invalid_noDerive() { + Month.from(LocalTime.of(12, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_CalendricalObject_null() { + Month.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @Test + public void test_get_TemporalField() { + assertEquals(Month.JULY.get(ChronoField.MONTH_OF_YEAR), 7); + } + + @Test + public void test_getLong_TemporalField() { + assertEquals(Month.JULY.getLong(ChronoField.MONTH_OF_YEAR), 7); + } + + //----------------------------------------------------------------------- + // query(TemporalQuery) + //----------------------------------------------------------------------- + @Test + public void test_query_chrono() { + assertEquals(Month.JUNE.query(Queries.chrono()), ISOChrono.INSTANCE); + assertEquals(Queries.chrono().queryFrom(Month.JUNE), ISOChrono.INSTANCE); + } + + @Test + public void test_query_zoneId() { + assertEquals(Month.JUNE.query(Queries.zoneId()), null); + assertEquals(Queries.zoneId().queryFrom(Month.JUNE), null); + } + + @Test + public void test_query_precision() { + assertEquals(Month.JUNE.query(Queries.precision()), ChronoUnit.MONTHS); + assertEquals(Queries.precision().queryFrom(Month.JUNE), ChronoUnit.MONTHS); + } + + @Test + public void test_query_offset() { + assertEquals(Month.JUNE.query(Queries.offset()), null); + assertEquals(Queries.offset().queryFrom(Month.JUNE), null); + } + + @Test + public void test_query_zone() { + assertEquals(Month.JUNE.query(Queries.zone()), null); + assertEquals(Queries.zone().queryFrom(Month.JUNE), null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_query_null() { + Month.JUNE.query(null); + } + + //----------------------------------------------------------------------- + // getText() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_getText() { + assertEquals(Month.JANUARY.getText(TextStyle.SHORT, Locale.US), "Jan"); + } + + @Test(expectedExceptions = NullPointerException.class, groups={"tck"}) + public void test_getText_nullStyle() { + Month.JANUARY.getText(null, Locale.US); + } + + @Test(expectedExceptions = NullPointerException.class, groups={"tck"}) + public void test_getText_nullLocale() { + Month.JANUARY.getText(TextStyle.FULL, null); + } + + //----------------------------------------------------------------------- + // plus(long), plus(long,unit) + //----------------------------------------------------------------------- + @DataProvider(name="plus") + Object[][] data_plus() { + return new Object[][] { + {1, -13, 12}, + {1, -12, 1}, + {1, -11, 2}, + {1, -10, 3}, + {1, -9, 4}, + {1, -8, 5}, + {1, -7, 6}, + {1, -6, 7}, + {1, -5, 8}, + {1, -4, 9}, + {1, -3, 10}, + {1, -2, 11}, + {1, -1, 12}, + {1, 0, 1}, + {1, 1, 2}, + {1, 2, 3}, + {1, 3, 4}, + {1, 4, 5}, + {1, 5, 6}, + {1, 6, 7}, + {1, 7, 8}, + {1, 8, 9}, + {1, 9, 10}, + {1, 10, 11}, + {1, 11, 12}, + {1, 12, 1}, + {1, 13, 2}, + + {1, 1, 2}, + {2, 1, 3}, + {3, 1, 4}, + {4, 1, 5}, + {5, 1, 6}, + {6, 1, 7}, + {7, 1, 8}, + {8, 1, 9}, + {9, 1, 10}, + {10, 1, 11}, + {11, 1, 12}, + {12, 1, 1}, + + {1, -1, 12}, + {2, -1, 1}, + {3, -1, 2}, + {4, -1, 3}, + {5, -1, 4}, + {6, -1, 5}, + {7, -1, 6}, + {8, -1, 7}, + {9, -1, 8}, + {10, -1, 9}, + {11, -1, 10}, + {12, -1, 11}, + }; + } + + @Test(dataProvider="plus", groups={"tck"}) + public void test_plus_long(int base, long amount, int expected) { + assertEquals(Month.of(base).plus(amount), Month.of(expected)); + } + + //----------------------------------------------------------------------- + // minus(long), minus(long,unit) + //----------------------------------------------------------------------- + @DataProvider(name="minus") + Object[][] data_minus() { + return new Object[][] { + {1, -13, 2}, + {1, -12, 1}, + {1, -11, 12}, + {1, -10, 11}, + {1, -9, 10}, + {1, -8, 9}, + {1, -7, 8}, + {1, -6, 7}, + {1, -5, 6}, + {1, -4, 5}, + {1, -3, 4}, + {1, -2, 3}, + {1, -1, 2}, + {1, 0, 1}, + {1, 1, 12}, + {1, 2, 11}, + {1, 3, 10}, + {1, 4, 9}, + {1, 5, 8}, + {1, 6, 7}, + {1, 7, 6}, + {1, 8, 5}, + {1, 9, 4}, + {1, 10, 3}, + {1, 11, 2}, + {1, 12, 1}, + {1, 13, 12}, + }; + } + + @Test(dataProvider="minus", groups={"tck"}) + public void test_minus_long(int base, long amount, int expected) { + assertEquals(Month.of(base).minus(amount), Month.of(expected)); + } + + //----------------------------------------------------------------------- + // length(boolean) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_length_boolean_notLeapYear() { + assertEquals(Month.JANUARY.length(false), 31); + assertEquals(Month.FEBRUARY.length(false), 28); + assertEquals(Month.MARCH.length(false), 31); + assertEquals(Month.APRIL.length(false), 30); + assertEquals(Month.MAY.length(false), 31); + assertEquals(Month.JUNE.length(false), 30); + assertEquals(Month.JULY.length(false), 31); + assertEquals(Month.AUGUST.length(false), 31); + assertEquals(Month.SEPTEMBER.length(false), 30); + assertEquals(Month.OCTOBER.length(false), 31); + assertEquals(Month.NOVEMBER.length(false), 30); + assertEquals(Month.DECEMBER.length(false), 31); + } + + @Test(groups={"tck"}) + public void test_length_boolean_leapYear() { + assertEquals(Month.JANUARY.length(true), 31); + assertEquals(Month.FEBRUARY.length(true), 29); + assertEquals(Month.MARCH.length(true), 31); + assertEquals(Month.APRIL.length(true), 30); + assertEquals(Month.MAY.length(true), 31); + assertEquals(Month.JUNE.length(true), 30); + assertEquals(Month.JULY.length(true), 31); + assertEquals(Month.AUGUST.length(true), 31); + assertEquals(Month.SEPTEMBER.length(true), 30); + assertEquals(Month.OCTOBER.length(true), 31); + assertEquals(Month.NOVEMBER.length(true), 30); + assertEquals(Month.DECEMBER.length(true), 31); + } + + //----------------------------------------------------------------------- + // minLength() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minLength() { + assertEquals(Month.JANUARY.minLength(), 31); + assertEquals(Month.FEBRUARY.minLength(), 28); + assertEquals(Month.MARCH.minLength(), 31); + assertEquals(Month.APRIL.minLength(), 30); + assertEquals(Month.MAY.minLength(), 31); + assertEquals(Month.JUNE.minLength(), 30); + assertEquals(Month.JULY.minLength(), 31); + assertEquals(Month.AUGUST.minLength(), 31); + assertEquals(Month.SEPTEMBER.minLength(), 30); + assertEquals(Month.OCTOBER.minLength(), 31); + assertEquals(Month.NOVEMBER.minLength(), 30); + assertEquals(Month.DECEMBER.minLength(), 31); + } + + //----------------------------------------------------------------------- + // maxLength() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_maxLength() { + assertEquals(Month.JANUARY.maxLength(), 31); + assertEquals(Month.FEBRUARY.maxLength(), 29); + assertEquals(Month.MARCH.maxLength(), 31); + assertEquals(Month.APRIL.maxLength(), 30); + assertEquals(Month.MAY.maxLength(), 31); + assertEquals(Month.JUNE.maxLength(), 30); + assertEquals(Month.JULY.maxLength(), 31); + assertEquals(Month.AUGUST.maxLength(), 31); + assertEquals(Month.SEPTEMBER.maxLength(), 30); + assertEquals(Month.OCTOBER.maxLength(), 31); + assertEquals(Month.NOVEMBER.maxLength(), 30); + assertEquals(Month.DECEMBER.maxLength(), 31); + } + + //----------------------------------------------------------------------- + // firstDayOfYear(boolean) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_firstDayOfYear_notLeapYear() { + assertEquals(Month.JANUARY.firstDayOfYear(false), 1); + assertEquals(Month.FEBRUARY.firstDayOfYear(false), 1 + 31); + assertEquals(Month.MARCH.firstDayOfYear(false), 1 + 31 + 28); + assertEquals(Month.APRIL.firstDayOfYear(false), 1 + 31 + 28 + 31); + assertEquals(Month.MAY.firstDayOfYear(false), 1 + 31 + 28 + 31 + 30); + assertEquals(Month.JUNE.firstDayOfYear(false), 1 + 31 + 28 + 31 + 30 + 31); + assertEquals(Month.JULY.firstDayOfYear(false), 1 + 31 + 28 + 31 + 30 + 31 + 30); + assertEquals(Month.AUGUST.firstDayOfYear(false), 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31); + assertEquals(Month.SEPTEMBER.firstDayOfYear(false), 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31); + assertEquals(Month.OCTOBER.firstDayOfYear(false), 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30); + assertEquals(Month.NOVEMBER.firstDayOfYear(false), 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31); + assertEquals(Month.DECEMBER.firstDayOfYear(false), 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30); + } + + @Test(groups={"tck"}) + public void test_firstDayOfYear_leapYear() { + assertEquals(Month.JANUARY.firstDayOfYear(true), 1); + assertEquals(Month.FEBRUARY.firstDayOfYear(true), 1 + 31); + assertEquals(Month.MARCH.firstDayOfYear(true), 1 + 31 + 29); + assertEquals(Month.APRIL.firstDayOfYear(true), 1 + 31 + 29 + 31); + assertEquals(Month.MAY.firstDayOfYear(true), 1 + 31 + 29 + 31 + 30); + assertEquals(Month.JUNE.firstDayOfYear(true), 1 + 31 + 29 + 31 + 30 + 31); + assertEquals(Month.JULY.firstDayOfYear(true), 1 + 31 + 29 + 31 + 30 + 31 + 30); + assertEquals(Month.AUGUST.firstDayOfYear(true), 1 + 31 + 29 + 31 + 30 + 31 + 30 + 31); + assertEquals(Month.SEPTEMBER.firstDayOfYear(true), 1 + 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31); + assertEquals(Month.OCTOBER.firstDayOfYear(true), 1 + 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30); + assertEquals(Month.NOVEMBER.firstDayOfYear(true), 1 + 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31); + assertEquals(Month.DECEMBER.firstDayOfYear(true), 1 + 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30); + } + + //----------------------------------------------------------------------- + // firstMonthOfQuarter() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_firstMonthOfQuarter() { + assertEquals(Month.JANUARY.firstMonthOfQuarter(), Month.JANUARY); + assertEquals(Month.FEBRUARY.firstMonthOfQuarter(), Month.JANUARY); + assertEquals(Month.MARCH.firstMonthOfQuarter(), Month.JANUARY); + assertEquals(Month.APRIL.firstMonthOfQuarter(), Month.APRIL); + assertEquals(Month.MAY.firstMonthOfQuarter(), Month.APRIL); + assertEquals(Month.JUNE.firstMonthOfQuarter(), Month.APRIL); + assertEquals(Month.JULY.firstMonthOfQuarter(), Month.JULY); + assertEquals(Month.AUGUST.firstMonthOfQuarter(), Month.JULY); + assertEquals(Month.SEPTEMBER.firstMonthOfQuarter(), Month.JULY); + assertEquals(Month.OCTOBER.firstMonthOfQuarter(), Month.OCTOBER); + assertEquals(Month.NOVEMBER.firstMonthOfQuarter(), Month.OCTOBER); + assertEquals(Month.DECEMBER.firstMonthOfQuarter(), Month.OCTOBER); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString() { + assertEquals(Month.JANUARY.toString(), "JANUARY"); + assertEquals(Month.FEBRUARY.toString(), "FEBRUARY"); + assertEquals(Month.MARCH.toString(), "MARCH"); + assertEquals(Month.APRIL.toString(), "APRIL"); + assertEquals(Month.MAY.toString(), "MAY"); + assertEquals(Month.JUNE.toString(), "JUNE"); + assertEquals(Month.JULY.toString(), "JULY"); + assertEquals(Month.AUGUST.toString(), "AUGUST"); + assertEquals(Month.SEPTEMBER.toString(), "SEPTEMBER"); + assertEquals(Month.OCTOBER.toString(), "OCTOBER"); + assertEquals(Month.NOVEMBER.toString(), "NOVEMBER"); + assertEquals(Month.DECEMBER.toString(), "DECEMBER"); + } + + //----------------------------------------------------------------------- + // generated methods + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_enum() { + assertEquals(Month.valueOf("JANUARY"), Month.JANUARY); + assertEquals(Month.values()[0], Month.JANUARY); + } + +} diff --git a/test/java/time/tck/java/time/TCKZoneId.java b/test/java/time/tck/java/time/TCKZoneId.java new file mode 100644 index 0000000000000000000000000000000000000000..adc2bb6ee2f123b04359ec242b09ea9158a83235 --- /dev/null +++ b/test/java/time/tck/java/time/TCKZoneId.java @@ -0,0 +1,92 @@ +/* + * 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. + * + * 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 tck.java.time; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; + +import java.time.ZoneId; + +import org.testng.annotations.Test; + +/** + * Test ZoneId. + */ +@Test +public class TCKZoneId extends AbstractTCKTest { + + //----------------------------------------------------------------------- + @Test + public void test_serialization() throws Exception { + assertSerializable(ZoneId.of("Europe/London")); + } + + @Test + public void test_serialization_format() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(7); + dos.writeUTF("Europe/London"); + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(ZoneId.of("Europe/London"), bytes); + } + +} diff --git a/test/java/time/tck/java/time/TCKZoneOffset.java b/test/java/time/tck/java/time/TCKZoneOffset.java new file mode 100644 index 0000000000000000000000000000000000000000..4cfd40d91156746920c7f63320465ee7f557d4f6 --- /dev/null +++ b/test/java/time/tck/java/time/TCKZoneOffset.java @@ -0,0 +1,680 @@ +/* + * 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. + * + * 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 tck.java.time; + +import static java.time.temporal.ChronoField.OFFSET_SECONDS; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import java.time.DateTimeException; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoField; +import java.time.temporal.JulianFields; +import java.time.temporal.OffsetDate; +import java.time.temporal.Queries; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; + +import org.testng.annotations.Test; + +/** + * Test ZoneOffset. + */ +@Test +public class TCKZoneOffset extends AbstractDateTimeTest { + + //----------------------------------------------------------------------- + @Override + protected List samples() { + TemporalAccessor[] array = {ZoneOffset.ofHours(1), ZoneOffset.ofHoursMinutesSeconds(-5, -6, -30) }; + return Arrays.asList(array); + } + + @Override + protected List validFields() { + TemporalField[] array = { + OFFSET_SECONDS, + }; + return Arrays.asList(array); + } + + @Override + protected List invalidFields() { + List list = new ArrayList<>(Arrays.asList(ChronoField.values())); + list.removeAll(validFields()); + list.add(JulianFields.JULIAN_DAY); + list.add(JulianFields.MODIFIED_JULIAN_DAY); + list.add(JulianFields.RATA_DIE); + return list; + } + + //----------------------------------------------------------------------- + @Test + public void test_serialization() throws Exception { + assertSerializable(ZoneOffset.of("+01:30")); + } + + @Test + public void test_serialization_format_quarterPositive() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(8); + dos.writeByte(6); // stored as quarter hours + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(ZoneOffset.ofHoursMinutes(1, 30), bytes); + } + + @Test + public void test_serialization_format_quarterNegative() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(8); + dos.writeByte(-10); // stored as quarter hours + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(ZoneOffset.ofHoursMinutes(-2, -30), bytes); + } + + @Test + public void test_serialization_format_full() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(8); + dos.writeByte(127); + dos.writeInt(53265); + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(ZoneOffset.ofTotalSeconds(53265), bytes); + } + + //----------------------------------------------------------------------- + // constants + //----------------------------------------------------------------------- + @Test + public void test_constant_UTC() { + ZoneOffset test = ZoneOffset.UTC; + doTestOffset(test, 0, 0, 0); + } + + @Test + public void test_constant_MIN() { + ZoneOffset test = ZoneOffset.MIN; + doTestOffset(test, -18, 0, 0); + } + + @Test + public void test_constant_MAX() { + ZoneOffset test = ZoneOffset.MAX; + doTestOffset(test, 18, 0, 0); + } + + //----------------------------------------------------------------------- + // of(String) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_string_UTC() { + String[] values = new String[] { + "Z", "+0", + "+00","+0000","+00:00","+000000","+00:00:00", + "-00","-0000","-00:00","-000000","-00:00:00", + }; + for (int i = 0; i < values.length; i++) { + ZoneOffset test = ZoneOffset.of(values[i]); + assertSame(test, ZoneOffset.UTC); + } + } + + @Test(groups={"tck"}) + public void test_factory_string_invalid() { + String[] values = new String[] { + "","A","B","C","D","E","F","G","H","I","J","K","L","M", + "N","O","P","Q","R","S","T","U","V","W","X","Y","ZZ", + "0", "+0:00","+00:0","+0:0", + "+000","+00000", + "+0:00:00","+00:0:00","+00:00:0","+0:0:0","+0:0:00","+00:0:0","+0:00:0", + "1", "+01_00","+01;00","+01@00","+01:AA", + "+19","+19:00","+18:01","+18:00:01","+1801","+180001", + "-0:00","-00:0","-0:0", + "-000","-00000", + "-0:00:00","-00:0:00","-00:00:0","-0:0:0","-0:0:00","-00:0:0","-0:00:0", + "-19","-19:00","-18:01","-18:00:01","-1801","-180001", + "-01_00","-01;00","-01@00","-01:AA", + "@01:00", + }; + for (int i = 0; i < values.length; i++) { + try { + ZoneOffset.of(values[i]); + fail("Should have failed:" + values[i]); + } catch (DateTimeException ex) { + // expected + } + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_string_null() { + ZoneOffset.of((String) null); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_string_singleDigitHours() { + for (int i = -9; i <= 9; i++) { + String str = (i < 0 ? "-" : "+") + Math.abs(i); + ZoneOffset test = ZoneOffset.of(str); + doTestOffset(test, i, 0, 0); + } + } + + @Test(groups={"tck"}) + public void test_factory_string_hours() { + for (int i = -18; i <= 18; i++) { + String str = (i < 0 ? "-" : "+") + Integer.toString(Math.abs(i) + 100).substring(1); + ZoneOffset test = ZoneOffset.of(str); + doTestOffset(test, i, 0, 0); + } + } + + @Test(groups={"tck"}) + public void test_factory_string_hours_minutes_noColon() { + for (int i = -17; i <= 17; i++) { + for (int j = -59; j <= 59; j++) { + if ((i < 0 && j <= 0) || (i > 0 && j >= 0) || i == 0) { + String str = (i < 0 || j < 0 ? "-" : "+") + + Integer.toString(Math.abs(i) + 100).substring(1) + + Integer.toString(Math.abs(j) + 100).substring(1); + ZoneOffset test = ZoneOffset.of(str); + doTestOffset(test, i, j, 0); + } + } + } + ZoneOffset test1 = ZoneOffset.of("-1800"); + doTestOffset(test1, -18, 0, 0); + ZoneOffset test2 = ZoneOffset.of("+1800"); + doTestOffset(test2, 18, 0, 0); + } + + @Test(groups={"tck"}) + public void test_factory_string_hours_minutes_colon() { + for (int i = -17; i <= 17; i++) { + for (int j = -59; j <= 59; j++) { + if ((i < 0 && j <= 0) || (i > 0 && j >= 0) || i == 0) { + String str = (i < 0 || j < 0 ? "-" : "+") + + Integer.toString(Math.abs(i) + 100).substring(1) + ":" + + Integer.toString(Math.abs(j) + 100).substring(1); + ZoneOffset test = ZoneOffset.of(str); + doTestOffset(test, i, j, 0); + } + } + } + ZoneOffset test1 = ZoneOffset.of("-18:00"); + doTestOffset(test1, -18, 0, 0); + ZoneOffset test2 = ZoneOffset.of("+18:00"); + doTestOffset(test2, 18, 0, 0); + } + + @Test(groups={"tck"}) + public void test_factory_string_hours_minutes_seconds_noColon() { + for (int i = -17; i <= 17; i++) { + for (int j = -59; j <= 59; j++) { + for (int k = -59; k <= 59; k++) { + if ((i < 0 && j <= 0 && k <= 0) || (i > 0 && j >= 0 && k >= 0) || + (i == 0 && ((j < 0 && k <= 0) || (j > 0 && k >= 0) || j == 0))) { + String str = (i < 0 || j < 0 || k < 0 ? "-" : "+") + + Integer.toString(Math.abs(i) + 100).substring(1) + + Integer.toString(Math.abs(j) + 100).substring(1) + + Integer.toString(Math.abs(k) + 100).substring(1); + ZoneOffset test = ZoneOffset.of(str); + doTestOffset(test, i, j, k); + } + } + } + } + ZoneOffset test1 = ZoneOffset.of("-180000"); + doTestOffset(test1, -18, 0, 0); + ZoneOffset test2 = ZoneOffset.of("+180000"); + doTestOffset(test2, 18, 0, 0); + } + + @Test(groups={"tck"}) + public void test_factory_string_hours_minutes_seconds_colon() { + for (int i = -17; i <= 17; i++) { + for (int j = -59; j <= 59; j++) { + for (int k = -59; k <= 59; k++) { + if ((i < 0 && j <= 0 && k <= 0) || (i > 0 && j >= 0 && k >= 0) || + (i == 0 && ((j < 0 && k <= 0) || (j > 0 && k >= 0) || j == 0))) { + String str = (i < 0 || j < 0 || k < 0 ? "-" : "+") + + Integer.toString(Math.abs(i) + 100).substring(1) + ":" + + Integer.toString(Math.abs(j) + 100).substring(1) + ":" + + Integer.toString(Math.abs(k) + 100).substring(1); + ZoneOffset test = ZoneOffset.of(str); + doTestOffset(test, i, j, k); + } + } + } + } + ZoneOffset test1 = ZoneOffset.of("-18:00:00"); + doTestOffset(test1, -18, 0, 0); + ZoneOffset test2 = ZoneOffset.of("+18:00:00"); + doTestOffset(test2, 18, 0, 0); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_int_hours() { + for (int i = -18; i <= 18; i++) { + ZoneOffset test = ZoneOffset.ofHours(i); + doTestOffset(test, i, 0, 0); + } + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_factory_int_hours_tooBig() { + ZoneOffset.ofHours(19); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_factory_int_hours_tooSmall() { + ZoneOffset.ofHours(-19); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_int_hours_minutes() { + for (int i = -17; i <= 17; i++) { + for (int j = -59; j <= 59; j++) { + if ((i < 0 && j <= 0) || (i > 0 && j >= 0) || i == 0) { + ZoneOffset test = ZoneOffset.ofHoursMinutes(i, j); + doTestOffset(test, i, j, 0); + } + } + } + ZoneOffset test1 = ZoneOffset.ofHoursMinutes(-18, 0); + doTestOffset(test1, -18, 0, 0); + ZoneOffset test2 = ZoneOffset.ofHoursMinutes(18, 0); + doTestOffset(test2, 18, 0, 0); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_factory_int_hours_minutes_tooBig() { + ZoneOffset.ofHoursMinutes(19, 0); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_factory_int_hours_minutes_tooSmall() { + ZoneOffset.ofHoursMinutes(-19, 0); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_int_hours_minutes_seconds() { + for (int i = -17; i <= 17; i++) { + for (int j = -59; j <= 59; j++) { + for (int k = -59; k <= 59; k++) { + if ((i < 0 && j <= 0 && k <= 0) || (i > 0 && j >= 0 && k >= 0) || + (i == 0 && ((j < 0 && k <= 0) || (j > 0 && k >= 0) || j == 0))) { + ZoneOffset test = ZoneOffset.ofHoursMinutesSeconds(i, j, k); + doTestOffset(test, i, j, k); + } + } + } + } + ZoneOffset test1 = ZoneOffset.ofHoursMinutesSeconds(-18, 0, 0); + doTestOffset(test1, -18, 0, 0); + ZoneOffset test2 = ZoneOffset.ofHoursMinutesSeconds(18, 0, 0); + doTestOffset(test2, 18, 0, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_hours_minutes_seconds_plusHoursMinusMinutes() { + ZoneOffset.ofHoursMinutesSeconds(1, -1, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_hours_minutes_seconds_plusHoursMinusSeconds() { + ZoneOffset.ofHoursMinutesSeconds(1, 0, -1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_hours_minutes_seconds_minusHoursPlusMinutes() { + ZoneOffset.ofHoursMinutesSeconds(-1, 1, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_hours_minutes_seconds_minusHoursPlusSeconds() { + ZoneOffset.ofHoursMinutesSeconds(-1, 0, 1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_hours_minutes_seconds_zeroHoursMinusMinutesPlusSeconds() { + ZoneOffset.ofHoursMinutesSeconds(0, -1, 1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_hours_minutes_seconds_zeroHoursPlusMinutesMinusSeconds() { + ZoneOffset.ofHoursMinutesSeconds(0, 1, -1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_hours_minutes_seconds_minutesTooLarge() { + ZoneOffset.ofHoursMinutesSeconds(0, 60, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_hours_minutes_seconds_minutesTooSmall() { + ZoneOffset.ofHoursMinutesSeconds(0, -60, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_hours_minutes_seconds_secondsTooLarge() { + ZoneOffset.ofHoursMinutesSeconds(0, 0, 60); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_hours_minutes_seconds_secondsTooSmall() { + ZoneOffset.ofHoursMinutesSeconds(0, 0, 60); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_factory_int_hours_minutes_seconds_hoursTooBig() { + ZoneOffset.ofHoursMinutesSeconds(19, 0, 0); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_factory_int_hours_minutes_seconds_hoursTooSmall() { + ZoneOffset.ofHoursMinutesSeconds(-19, 0, 0); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_ofTotalSeconds() { + assertEquals(ZoneOffset.ofTotalSeconds(60 * 60 + 1), ZoneOffset.ofHoursMinutesSeconds(1, 0, 1)); + assertEquals(ZoneOffset.ofTotalSeconds(18 * 60 * 60), ZoneOffset.ofHours(18)); + assertEquals(ZoneOffset.ofTotalSeconds(-18 * 60 * 60), ZoneOffset.ofHours(-18)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_ofTotalSeconds_tooLarge() { + ZoneOffset.ofTotalSeconds(18 * 60 * 60 + 1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_ofTotalSeconds_tooSmall() { + ZoneOffset.ofTotalSeconds(-18 * 60 * 60 - 1); + } + + //----------------------------------------------------------------------- + // from() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_CalendricalObject() { + assertEquals(ZoneOffset.from(OffsetDate.of(LocalDate.of(2012, 5, 2), ZoneOffset.ofHours(6))), ZoneOffset.ofHours(6)); + assertEquals(ZoneOffset.from(ZonedDateTime.of(LocalDateTime.of(LocalDate.of(2007, 7, 15), + LocalTime.of(17, 30)), ZoneOffset.ofHours(2))), ZoneOffset.ofHours(2)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_CalendricalObject_invalid_noDerive() { + ZoneOffset.from(LocalTime.of(12, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_CalendricalObject_null() { + ZoneOffset.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // getTotalSeconds() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_getTotalSeconds() { + ZoneOffset offset = ZoneOffset.ofTotalSeconds(60 * 60 + 1); + assertEquals(offset.getTotalSeconds(), 60 * 60 + 1); + } + + //----------------------------------------------------------------------- + // getId() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_getId() { + ZoneOffset offset = ZoneOffset.ofHoursMinutesSeconds(1, 0, 0); + assertEquals(offset.getId(), "+01:00"); + offset = ZoneOffset.ofHoursMinutesSeconds(1, 2, 3); + assertEquals(offset.getId(), "+01:02:03"); + offset = ZoneOffset.UTC; + assertEquals(offset.getId(), "Z"); + } + + //----------------------------------------------------------------------- + // getRules() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_getRules() { + ZoneOffset offset = ZoneOffset.ofHoursMinutesSeconds(1, 2, 3); + assertEquals(offset.getRules().isFixedOffset(), true); + assertEquals(offset.getRules().getOffset((Instant) null), offset); + assertEquals(offset.getRules().getDaylightSavings((Instant) null), Duration.ZERO); + assertEquals(offset.getRules().getStandardOffset((Instant) null), offset); + assertEquals(offset.getRules().nextTransition((Instant) null), null); + assertEquals(offset.getRules().previousTransition((Instant) null), null); + + assertEquals(offset.getRules().isValidOffset((LocalDateTime) null, offset), true); + assertEquals(offset.getRules().isValidOffset((LocalDateTime) null, ZoneOffset.UTC), false); + assertEquals(offset.getRules().isValidOffset((LocalDateTime) null, null), false); + assertEquals(offset.getRules().getOffset((LocalDateTime) null), offset); + assertEquals(offset.getRules().getValidOffsets((LocalDateTime) null), Arrays.asList(offset)); + assertEquals(offset.getRules().getTransition((LocalDateTime) null), null); + assertEquals(offset.getRules().getTransitions().size(), 0); + assertEquals(offset.getRules().getTransitionRules().size(), 0); + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @Test + public void test_get_TemporalField() { + assertEquals(ZoneOffset.UTC.get(OFFSET_SECONDS), 0); + assertEquals(ZoneOffset.ofHours(-2).get(OFFSET_SECONDS), -7200); + assertEquals(ZoneOffset.ofHoursMinutesSeconds(0, 1, 5).get(OFFSET_SECONDS), 65); + } + + @Test + public void test_getLong_TemporalField() { + assertEquals(ZoneOffset.UTC.getLong(OFFSET_SECONDS), 0); + assertEquals(ZoneOffset.ofHours(-2).getLong(OFFSET_SECONDS), -7200); + assertEquals(ZoneOffset.ofHoursMinutesSeconds(0, 1, 5).getLong(OFFSET_SECONDS), 65); + } + + //----------------------------------------------------------------------- + // query(TemporalQuery) + //----------------------------------------------------------------------- + @Test + public void test_query_chrono() { + ZoneOffset test = ZoneOffset.ofHoursMinutes(1, 30); + assertEquals(test.query(Queries.chrono()), null); + assertEquals(Queries.chrono().queryFrom(test), null); + } + + @Test + public void test_query_zoneId() { + ZoneOffset test = ZoneOffset.ofHoursMinutes(1, 30); + assertEquals(test.query(Queries.zoneId()), null); + assertEquals(Queries.zoneId().queryFrom(test), null); + } + + @Test + public void test_query_precision() { + ZoneOffset test = ZoneOffset.ofHoursMinutes(1, 30); + assertEquals(test.query(Queries.precision()), null); + assertEquals(Queries.precision().queryFrom(test), null); + } + + @Test + public void test_query_offset() { + ZoneOffset test = ZoneOffset.ofHoursMinutes(1, 30); + assertEquals(test.query(Queries.offset()), test); + assertEquals(Queries.offset().queryFrom(test), test); + } + + @Test + public void test_query_zone() { + ZoneOffset test = ZoneOffset.ofHoursMinutes(1, 30); + assertEquals(test.query(Queries.zone()), test); + assertEquals(Queries.zone().queryFrom(test), test); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_query_null() { + ZoneOffset.ofHoursMinutes(1, 30).query(null); + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_compareTo() { + ZoneOffset offset1 = ZoneOffset.ofHoursMinutesSeconds(1, 2, 3); + ZoneOffset offset2 = ZoneOffset.ofHoursMinutesSeconds(2, 3, 4); + assertTrue(offset1.compareTo(offset2) > 0); + assertTrue(offset2.compareTo(offset1) < 0); + assertTrue(offset1.compareTo(offset1) == 0); + assertTrue(offset2.compareTo(offset2) == 0); + } + + //----------------------------------------------------------------------- + // equals() / hashCode() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_equals() { + ZoneOffset offset1 = ZoneOffset.ofHoursMinutesSeconds(1, 2, 3); + ZoneOffset offset2 = ZoneOffset.ofHoursMinutesSeconds(2, 3, 4); + ZoneOffset offset2b = ZoneOffset.ofHoursMinutesSeconds(2, 3, 4); + assertEquals(offset1.equals(offset2), false); + assertEquals(offset2.equals(offset1), false); + + assertEquals(offset1.equals(offset1), true); + assertEquals(offset2.equals(offset2), true); + assertEquals(offset2.equals(offset2b), true); + + assertEquals(offset1.hashCode() == offset1.hashCode(), true); + assertEquals(offset2.hashCode() == offset2.hashCode(), true); + assertEquals(offset2.hashCode() == offset2b.hashCode(), true); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString() { + ZoneOffset offset = ZoneOffset.ofHoursMinutesSeconds(1, 0, 0); + assertEquals(offset.toString(), "+01:00"); + offset = ZoneOffset.ofHoursMinutesSeconds(1, 2, 3); + assertEquals(offset.toString(), "+01:02:03"); + offset = ZoneOffset.UTC; + assertEquals(offset.toString(), "Z"); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + private void doTestOffset(ZoneOffset offset, int hours, int minutes, int seconds) { + assertEquals(offset.getTotalSeconds(), hours * 60 * 60 + minutes * 60 + seconds); + final String id; + if (hours == 0 && minutes == 0 && seconds == 0) { + id = "Z"; + } else { + String str = (hours < 0 || minutes < 0 || seconds < 0) ? "-" : "+"; + str += Integer.toString(Math.abs(hours) + 100).substring(1); + str += ":"; + str += Integer.toString(Math.abs(minutes) + 100).substring(1); + if (seconds != 0) { + str += ":"; + str += Integer.toString(Math.abs(seconds) + 100).substring(1); + } + id = str; + } + assertEquals(offset.getId(), id); + assertEquals(offset, ZoneOffset.ofHoursMinutesSeconds(hours, minutes, seconds)); + if (seconds == 0) { + assertEquals(offset, ZoneOffset.ofHoursMinutes(hours, minutes)); + if (minutes == 0) { + assertEquals(offset, ZoneOffset.ofHours(hours)); + } + } + assertEquals(ZoneOffset.of(id), offset); + assertEquals(offset.toString(), id); + } + +} diff --git a/test/java/time/tck/java/time/TCKZonedDateTime.java b/test/java/time/tck/java/time/TCKZonedDateTime.java new file mode 100644 index 0000000000000000000000000000000000000000..df18b4f246f73bd75af357dbf065ad36aac73b79 --- /dev/null +++ b/test/java/time/tck/java/time/TCKZonedDateTime.java @@ -0,0 +1,2166 @@ +/* + * 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. + * + * 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 tck.java.time; + +import java.time.*; +import test.java.time.MockSimplePeriod; + +import static java.time.Month.JANUARY; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; +import static java.time.temporal.ChronoField.AMPM_OF_DAY; +import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM; +import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.EPOCH_MONTH; +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoField.HOUR_OF_AMPM; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.INSTANT_SECONDS; +import static java.time.temporal.ChronoField.MICRO_OF_DAY; +import static java.time.temporal.ChronoField.MICRO_OF_SECOND; +import static java.time.temporal.ChronoField.MILLI_OF_DAY; +import static java.time.temporal.ChronoField.MILLI_OF_SECOND; +import static java.time.temporal.ChronoField.MINUTE_OF_DAY; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.NANO_OF_DAY; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.OFFSET_SECONDS; +import static java.time.temporal.ChronoField.SECOND_OF_DAY; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Queries; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQuery; +import java.time.temporal.TemporalField; +import java.time.temporal.ISOChrono; +import java.time.temporal.JulianFields; +import test.java.time.temporal.MockFieldNoValue; + +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.temporal.OffsetDateTime; +import java.time.temporal.Year; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test ZonedDateTime. + */ +@Test +public class TCKZonedDateTime extends AbstractDateTimeTest { + + private static final ZoneOffset OFFSET_0100 = ZoneOffset.ofHours(1); + private static final ZoneOffset OFFSET_0200 = ZoneOffset.ofHours(2); + private static final ZoneOffset OFFSET_0130 = ZoneOffset.of("+01:30"); + private static final ZoneOffset OFFSET_MAX = ZoneOffset.MAX; + private static final ZoneOffset OFFSET_MIN = ZoneOffset.MIN; + + private static final ZoneId ZONE_0100 = OFFSET_0100; + private static final ZoneId ZONE_0200 = OFFSET_0200; + private static final ZoneId ZONE_M0100 = ZoneOffset.ofHours(-1); + private static final ZoneId ZONE_LONDON = ZoneId.of("Europe/London"); + private static final ZoneId ZONE_PARIS = ZoneId.of("Europe/Paris"); + private LocalDateTime TEST_PARIS_GAP_2008_03_30_02_30; + private LocalDateTime TEST_PARIS_OVERLAP_2008_10_26_02_30; + private LocalDateTime TEST_LOCAL_2008_06_30_11_30_59_500; + private ZonedDateTime TEST_DATE_TIME; + private ZonedDateTime TEST_DATE_TIME_PARIS; + + @BeforeMethod(groups={"tck","implementation"}) + public void setUp() { + TEST_LOCAL_2008_06_30_11_30_59_500 = LocalDateTime.of(2008, 6, 30, 11, 30, 59, 500); + TEST_DATE_TIME = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + TEST_DATE_TIME_PARIS = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_PARIS); + TEST_PARIS_OVERLAP_2008_10_26_02_30 = LocalDateTime.of(2008, 10, 26, 2, 30); + TEST_PARIS_GAP_2008_03_30_02_30 = LocalDateTime.of(2008, 3, 30, 2, 30); + } + + //----------------------------------------------------------------------- + @Override + protected List samples() { + TemporalAccessor[] array = {TEST_DATE_TIME, }; + return Arrays.asList(array); + } + + @Override + protected List validFields() { + TemporalField[] array = { + NANO_OF_SECOND, + NANO_OF_DAY, + MICRO_OF_SECOND, + MICRO_OF_DAY, + MILLI_OF_SECOND, + MILLI_OF_DAY, + SECOND_OF_MINUTE, + SECOND_OF_DAY, + MINUTE_OF_HOUR, + MINUTE_OF_DAY, + CLOCK_HOUR_OF_AMPM, + HOUR_OF_AMPM, + CLOCK_HOUR_OF_DAY, + HOUR_OF_DAY, + AMPM_OF_DAY, + DAY_OF_WEEK, + ALIGNED_DAY_OF_WEEK_IN_MONTH, + ALIGNED_DAY_OF_WEEK_IN_YEAR, + DAY_OF_MONTH, + DAY_OF_YEAR, + EPOCH_DAY, + ALIGNED_WEEK_OF_MONTH, + ALIGNED_WEEK_OF_YEAR, + MONTH_OF_YEAR, + EPOCH_MONTH, + YEAR_OF_ERA, + YEAR, + ERA, + OFFSET_SECONDS, + INSTANT_SECONDS, + JulianFields.JULIAN_DAY, + JulianFields.MODIFIED_JULIAN_DAY, + JulianFields.RATA_DIE, + }; + return Arrays.asList(array); + } + + @Override + protected List invalidFields() { + List list = new ArrayList<>(Arrays.asList(ChronoField.values())); + list.removeAll(validFields()); + return list; + } + + //----------------------------------------------------------------------- + @Test + public void test_serialization() throws ClassNotFoundException, IOException { + assertSerializable(TEST_DATE_TIME); + } + + @Test + public void test_serialization_format_zoneId() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(6); + dos.writeInt(2012); // date + dos.writeByte(9); + dos.writeByte(16); + dos.writeByte(22); // time + dos.writeByte(17); + dos.writeByte(59); + dos.writeInt(470_000_000); + dos.writeByte(4); // offset + dos.writeByte(7); // zoneId + dos.writeUTF("Europe/London"); + } + byte[] bytes = baos.toByteArray(); + ZonedDateTime zdt = LocalDateTime.of(2012, 9, 16, 22, 17, 59, 470_000_000).atZone(ZoneId.of("Europe/London")); + assertSerializedBySer(zdt, bytes); + } + + @Test + public void test_serialization_format_zoneOffset() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(6); + dos.writeInt(2012); // date + dos.writeByte(9); + dos.writeByte(16); + dos.writeByte(22); // time + dos.writeByte(17); + dos.writeByte(59); + dos.writeInt(470_000_000); + dos.writeByte(4); // offset + dos.writeByte(8); // zoneId + dos.writeByte(4); + } + byte[] bytes = baos.toByteArray(); + ZonedDateTime zdt = LocalDateTime.of(2012, 9, 16, 22, 17, 59, 470_000_000).atZone(ZoneOffset.ofHours(1)); + assertSerializedBySer(zdt, bytes); + } + + //----------------------------------------------------------------------- + // now() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now() { + ZonedDateTime expected = ZonedDateTime.now(Clock.systemDefaultZone()); + ZonedDateTime test = ZonedDateTime.now(); + long diff = Math.abs(test.getTime().toNanoOfDay() - expected.getTime().toNanoOfDay()); + if (diff >= 100000000) { + // may be date change + expected = ZonedDateTime.now(Clock.systemDefaultZone()); + test = ZonedDateTime.now(); + diff = Math.abs(test.getTime().toNanoOfDay() - expected.getTime().toNanoOfDay()); + } + assertTrue(diff < 100000000); // less than 0.1 secs + } + + //----------------------------------------------------------------------- + // now(ZoneId) + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_ZoneId_nullZoneId() { + ZonedDateTime.now((ZoneId) null); + } + + @Test(groups={"tck"}) + public void now_ZoneId() { + ZoneId zone = ZoneId.of("UTC+01:02:03"); + ZonedDateTime expected = ZonedDateTime.now(Clock.system(zone)); + ZonedDateTime test = ZonedDateTime.now(zone); + for (int i = 0; i < 100; i++) { + if (expected.equals(test)) { + return; + } + expected = ZonedDateTime.now(Clock.system(zone)); + test = ZonedDateTime.now(zone); + } + assertEquals(test, expected); + } + + //----------------------------------------------------------------------- + // now(Clock) + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_Clock_nullClock() { + ZonedDateTime.now((Clock)null); + } + + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay_utc() { + for (int i = 0; i < (2 * 24 * 60 * 60); i++) { + Instant instant = Instant.ofEpochSecond(i).plusNanos(123456789L); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + ZonedDateTime test = ZonedDateTime.now(clock); + assertEquals(test.getYear(), 1970); + assertEquals(test.getMonth(), Month.JANUARY); + assertEquals(test.getDayOfMonth(), (i < 24 * 60 * 60 ? 1 : 2)); + assertEquals(test.getHour(), (i / (60 * 60)) % 24); + assertEquals(test.getMinute(), (i / 60) % 60); + assertEquals(test.getSecond(), i % 60); + assertEquals(test.getNano(), 123456789); + assertEquals(test.getOffset(), ZoneOffset.UTC); + assertEquals(test.getZone(), ZoneOffset.UTC); + } + } + + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay_zone() { + ZoneId zone = ZoneId.of("Europe/London"); + for (int i = 0; i < (2 * 24 * 60 * 60); i++) { + Instant instant = Instant.ofEpochSecond(i).plusNanos(123456789L); + ZonedDateTime expected = ZonedDateTime.ofInstant(instant, zone); + Clock clock = Clock.fixed(expected.toInstant(), zone); + ZonedDateTime test = ZonedDateTime.now(clock); + assertEquals(test, expected); + } + } + + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay_beforeEpoch() { + LocalTime expected = LocalTime.MIDNIGHT.plusNanos(123456789L); + for (int i =-1; i >= -(24 * 60 * 60); i--) { + Instant instant = Instant.ofEpochSecond(i).plusNanos(123456789L); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + ZonedDateTime test = ZonedDateTime.now(clock); + assertEquals(test.getYear(), 1969); + assertEquals(test.getMonth(), Month.DECEMBER); + assertEquals(test.getDayOfMonth(), 31); + expected = expected.minusSeconds(1); + assertEquals(test.getTime(), expected); + assertEquals(test.getOffset(), ZoneOffset.UTC); + assertEquals(test.getZone(), ZoneOffset.UTC); + } + } + + @Test(groups={"tck"}) + public void now_Clock_offsets() { + ZonedDateTime base = ZonedDateTime.of(LocalDateTime.of(1970, 1, 1, 12, 0), ZoneOffset.UTC); + for (int i = -9; i < 15; i++) { + ZoneOffset offset = ZoneOffset.ofHours(i); + Clock clock = Clock.fixed(base.toInstant(), offset); + ZonedDateTime test = ZonedDateTime.now(clock); + assertEquals(test.getHour(), (12 + i) % 24); + assertEquals(test.getMinute(), 0); + assertEquals(test.getSecond(), 0); + assertEquals(test.getNano(), 0); + assertEquals(test.getOffset(), offset); + assertEquals(test.getZone(), offset); + } + } + + //----------------------------------------------------------------------- + // dateTime factories + //----------------------------------------------------------------------- + void check(ZonedDateTime test, int y, int m, int d, int h, int min, int s, int n, ZoneOffset offset, ZoneId zone) { + assertEquals(test.getYear(), y); + assertEquals(test.getMonth().getValue(), m); + assertEquals(test.getDayOfMonth(), d); + assertEquals(test.getHour(), h); + assertEquals(test.getMinute(), min); + assertEquals(test.getSecond(), s); + assertEquals(test.getNano(), n); + assertEquals(test.getOffset(), offset); + assertEquals(test.getZone(), zone); + } + + //----------------------------------------------------------------------- + // of(LocalDateTime, ZoneId) + //----------------------------------------------------------------------- + // TODO: tests of overlap/gap + + @Test(groups={"tck"}) + public void factory_of_LocalDateTime() { + LocalDateTime base = LocalDateTime.of(2008, 6, 30, 11, 30, 10, 500); + ZonedDateTime test = ZonedDateTime.of(base, ZONE_PARIS); + check(test, 2008, 6, 30, 11, 30, 10, 500, OFFSET_0200, ZONE_PARIS); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_LocalDateTime_nullDateTime() { + ZonedDateTime.of((LocalDateTime) null, ZONE_PARIS); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_LocalDateTime_nullZone() { + LocalDateTime base = LocalDateTime.of(2008, 6, 30, 11, 30, 10, 500); + ZonedDateTime.of(base, null); + } + + //----------------------------------------------------------------------- + // ofInstant(Instant, ZoneId) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_ofInstant_Instant_ZR() { + Instant instant = LocalDateTime.of(2008, 6, 30, 11, 30, 10, 35).toInstant(OFFSET_0200); + ZonedDateTime test = ZonedDateTime.ofInstant(instant, ZONE_PARIS); + check(test, 2008, 6, 30, 11, 30, 10, 35, OFFSET_0200, ZONE_PARIS); + } + + @Test(groups={"tck"}) + public void factory_ofInstant_Instant_ZO() { + Instant instant = LocalDateTime.of(2008, 6, 30, 11, 30, 10, 45).toInstant(OFFSET_0200); + ZonedDateTime test = ZonedDateTime.ofInstant(instant, OFFSET_0200); + check(test, 2008, 6, 30, 11, 30, 10, 45, OFFSET_0200, OFFSET_0200); + } + + @Test(groups={"tck"}) + public void factory_ofInstant_Instant_inGap() { + Instant instant = TEST_PARIS_GAP_2008_03_30_02_30.toInstant(OFFSET_0100); + ZonedDateTime test = ZonedDateTime.ofInstant(instant, ZONE_PARIS); + check(test, 2008, 3, 30, 3, 30, 0, 0, OFFSET_0200, ZONE_PARIS); // one hour later in summer offset + } + + @Test(groups={"tck"}) + public void factory_ofInstant_Instant_inOverlap_earlier() { + Instant instant = TEST_PARIS_OVERLAP_2008_10_26_02_30.toInstant(OFFSET_0200); + ZonedDateTime test = ZonedDateTime.ofInstant(instant, ZONE_PARIS); + check(test, 2008, 10, 26, 2, 30, 0, 0, OFFSET_0200, ZONE_PARIS); // same time and offset + } + + @Test(groups={"tck"}) + public void factory_ofInstant_Instant_inOverlap_later() { + Instant instant = TEST_PARIS_OVERLAP_2008_10_26_02_30.toInstant(OFFSET_0100); + ZonedDateTime test = ZonedDateTime.ofInstant(instant, ZONE_PARIS); + check(test, 2008, 10, 26, 2, 30, 0, 0, OFFSET_0100, ZONE_PARIS); // same time and offset + } + + @Test(groups={"tck"}) + public void factory_ofInstant_Instant_invalidOffset() { + Instant instant = LocalDateTime.of(2008, 6, 30, 11, 30, 10, 500).toInstant(OFFSET_0130); + ZonedDateTime test = ZonedDateTime.ofInstant(instant, ZONE_PARIS); + check(test, 2008, 6, 30, 12, 0, 10, 500, OFFSET_0200, ZONE_PARIS); // corrected offset, thus altered time + } + + @Test(groups={"tck"}) + public void factory_ofInstant_allSecsInDay() { + for (int i = 0; i < (24 * 60 * 60); i++) { + Instant instant = Instant.ofEpochSecond(i); + ZonedDateTime test = ZonedDateTime.ofInstant(instant, OFFSET_0100); + assertEquals(test.getYear(), 1970); + assertEquals(test.getMonth(), Month.JANUARY); + assertEquals(test.getDayOfMonth(), 1 + (i >= 23 * 60 * 60 ? 1 : 0)); + assertEquals(test.getHour(), ((i / (60 * 60)) + 1) % 24); + assertEquals(test.getMinute(), (i / 60) % 60); + assertEquals(test.getSecond(), i % 60); + } + } + + @Test(groups={"tck"}) + public void factory_ofInstant_allDaysInCycle() { + // sanity check using different algorithm + ZonedDateTime expected = LocalDateTime.of(1970, 1, 1, 0, 0, 0, 0).atZone(ZoneOffset.UTC); + for (long i = 0; i < 146097; i++) { + Instant instant = Instant.ofEpochSecond(i * 24L * 60L * 60L); + ZonedDateTime test = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC); + assertEquals(test, expected); + expected = expected.plusDays(1); + } + } + + @Test(groups={"tck"}) + public void factory_ofInstant_minWithMinOffset() { + long days_0000_to_1970 = (146097 * 5) - (30 * 365 + 7); + int year = Year.MIN_VALUE; + long days = (year * 365L + (year / 4 - year / 100 + year / 400)) - days_0000_to_1970; + Instant instant = Instant.ofEpochSecond(days * 24L * 60L * 60L - OFFSET_MIN.getTotalSeconds()); + ZonedDateTime test = ZonedDateTime.ofInstant(instant, OFFSET_MIN); + assertEquals(test.getYear(), Year.MIN_VALUE); + assertEquals(test.getMonth().getValue(), 1); + assertEquals(test.getDayOfMonth(), 1); + assertEquals(test.getOffset(), OFFSET_MIN); + assertEquals(test.getHour(), 0); + assertEquals(test.getMinute(), 0); + assertEquals(test.getSecond(), 0); + assertEquals(test.getNano(), 0); + } + + @Test(groups={"tck"}) + public void factory_ofInstant_minWithMaxOffset() { + long days_0000_to_1970 = (146097 * 5) - (30 * 365 + 7); + int year = Year.MIN_VALUE; + long days = (year * 365L + (year / 4 - year / 100 + year / 400)) - days_0000_to_1970; + Instant instant = Instant.ofEpochSecond(days * 24L * 60L * 60L - OFFSET_MAX.getTotalSeconds()); + ZonedDateTime test = ZonedDateTime.ofInstant(instant, OFFSET_MAX); + assertEquals(test.getYear(), Year.MIN_VALUE); + assertEquals(test.getMonth().getValue(), 1); + assertEquals(test.getDayOfMonth(), 1); + assertEquals(test.getOffset(), OFFSET_MAX); + assertEquals(test.getHour(), 0); + assertEquals(test.getMinute(), 0); + assertEquals(test.getSecond(), 0); + assertEquals(test.getNano(), 0); + } + + @Test(groups={"tck"}) + public void factory_ofInstant_maxWithMinOffset() { + long days_0000_to_1970 = (146097 * 5) - (30 * 365 + 7); + int year = Year.MAX_VALUE; + long days = (year * 365L + (year / 4 - year / 100 + year / 400)) + 365 - days_0000_to_1970; + Instant instant = Instant.ofEpochSecond((days + 1) * 24L * 60L * 60L - 1 - OFFSET_MIN.getTotalSeconds()); + ZonedDateTime test = ZonedDateTime.ofInstant(instant, OFFSET_MIN); + assertEquals(test.getYear(), Year.MAX_VALUE); + assertEquals(test.getMonth().getValue(), 12); + assertEquals(test.getDayOfMonth(), 31); + assertEquals(test.getOffset(), OFFSET_MIN); + assertEquals(test.getHour(), 23); + assertEquals(test.getMinute(), 59); + assertEquals(test.getSecond(), 59); + assertEquals(test.getNano(), 0); + } + + @Test(groups={"tck"}) + public void factory_ofInstant_maxWithMaxOffset() { + long days_0000_to_1970 = (146097 * 5) - (30 * 365 + 7); + int year = Year.MAX_VALUE; + long days = (year * 365L + (year / 4 - year / 100 + year / 400)) + 365 - days_0000_to_1970; + Instant instant = Instant.ofEpochSecond((days + 1) * 24L * 60L * 60L - 1 - OFFSET_MAX.getTotalSeconds()); + ZonedDateTime test = ZonedDateTime.ofInstant(instant, OFFSET_MAX); + assertEquals(test.getYear(), Year.MAX_VALUE); + assertEquals(test.getMonth().getValue(), 12); + assertEquals(test.getDayOfMonth(), 31); + assertEquals(test.getOffset(), OFFSET_MAX); + assertEquals(test.getHour(), 23); + assertEquals(test.getMinute(), 59); + assertEquals(test.getSecond(), 59); + assertEquals(test.getNano(), 0); + } + + //----------------------------------------------------------------------- + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofInstant_maxInstantWithMaxOffset() { + Instant instant = Instant.ofEpochSecond(Long.MAX_VALUE); + ZonedDateTime.ofInstant(instant, OFFSET_MAX); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofInstant_maxInstantWithMinOffset() { + Instant instant = Instant.ofEpochSecond(Long.MAX_VALUE); + ZonedDateTime.ofInstant(instant, OFFSET_MIN); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofInstant_tooBig() { + long days_0000_to_1970 = (146097 * 5) - (30 * 365 + 7); + long year = Year.MAX_VALUE + 1L; + long days = (year * 365L + (year / 4 - year / 100 + year / 400)) - days_0000_to_1970; + Instant instant = Instant.ofEpochSecond(days * 24L * 60L * 60L); + ZonedDateTime.ofInstant(instant, ZoneOffset.UTC); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofInstant_tooLow() { + long days_0000_to_1970 = (146097 * 5) - (30 * 365 + 7); + int year = Year.MIN_VALUE - 1; + long days = (year * 365L + (year / 4 - year / 100 + year / 400)) - days_0000_to_1970; + Instant instant = Instant.ofEpochSecond(days * 24L * 60L * 60L); + ZonedDateTime.ofInstant(instant, ZoneOffset.UTC); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_ofInstant_Instant_nullInstant() { + ZonedDateTime.ofInstant((Instant) null, ZONE_0100); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_ofInstant_Instant_nullZone() { + ZonedDateTime.ofInstant(Instant.EPOCH, null); + } + + //----------------------------------------------------------------------- + // ofStrict(LocalDateTime, ZoneId, ZoneOffset) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_ofStrict_LDT_ZI_ZO() { + LocalDateTime normal = LocalDateTime.of(2008, 6, 30, 11, 30, 10, 500); + ZonedDateTime test = ZonedDateTime.ofStrict(normal, OFFSET_0200, ZONE_PARIS); + check(test, 2008, 6, 30, 11, 30, 10, 500, OFFSET_0200, ZONE_PARIS); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofStrict_LDT_ZI_ZO_inGap() { + try { + ZonedDateTime.ofStrict(TEST_PARIS_GAP_2008_03_30_02_30, OFFSET_0100, ZONE_PARIS); + } catch (DateTimeException ex) { + assertEquals(ex.getMessage().contains(" gap"), true); + throw ex; + } + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofStrict_LDT_ZI_ZO_inOverlap_invalidOfset() { + try { + ZonedDateTime.ofStrict(TEST_PARIS_OVERLAP_2008_10_26_02_30, OFFSET_0130, ZONE_PARIS); + } catch (DateTimeException ex) { + assertEquals(ex.getMessage().contains(" is not valid for "), true); + throw ex; + } + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_ofStrict_LDT_ZI_ZO_invalidOffset() { + try { + ZonedDateTime.ofStrict(TEST_LOCAL_2008_06_30_11_30_59_500, OFFSET_0130, ZONE_PARIS); + } catch (DateTimeException ex) { + assertEquals(ex.getMessage().contains(" is not valid for "), true); + throw ex; + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_ofStrict_LDT_ZI_ZO_nullLDT() { + ZonedDateTime.ofStrict((LocalDateTime) null, OFFSET_0100, ZONE_PARIS); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_ofStrict_LDT_ZI_ZO_nullZO() { + ZonedDateTime.ofStrict(TEST_LOCAL_2008_06_30_11_30_59_500, null, ZONE_PARIS); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_ofStrict_LDT_ZI_ZO_nullZI() { + ZonedDateTime.ofStrict(TEST_LOCAL_2008_06_30_11_30_59_500, OFFSET_0100, null); + } + + //----------------------------------------------------------------------- + // from(TemporalAccessor) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_from_TemporalAccessor_ZDT() { + assertEquals(ZonedDateTime.from(TEST_DATE_TIME_PARIS), TEST_DATE_TIME_PARIS); + } + + @Test(groups={"tck"}) + public void factory_from_TemporalAccessor_LDT_ZoneId() { + assertEquals(ZonedDateTime.from(new TemporalAccessor() { + @Override + public boolean isSupported(TemporalField field) { + return TEST_DATE_TIME_PARIS.getDateTime().isSupported(field); + } + @Override + public long getLong(TemporalField field) { + return TEST_DATE_TIME_PARIS.getDateTime().getLong(field); + } + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.zoneId()) { + return (R) TEST_DATE_TIME_PARIS.getZone(); + } + return TemporalAccessor.super.query(query); + } + }), TEST_DATE_TIME_PARIS); + } + + @Test(groups={"tck"}) + public void factory_from_TemporalAccessor_Instant_ZoneId() { + assertEquals(ZonedDateTime.from(new TemporalAccessor() { + @Override + public boolean isSupported(TemporalField field) { + return field == INSTANT_SECONDS || field == NANO_OF_SECOND; + } + + @Override + public long getLong(TemporalField field) { + return TEST_DATE_TIME_PARIS.toInstant().getLong(field); + } + + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.zoneId()) { + return (R) TEST_DATE_TIME_PARIS.getZone(); + } + return TemporalAccessor.super.query(query); + } + }), TEST_DATE_TIME_PARIS); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_from_TemporalAccessor_invalid_noDerive() { + ZonedDateTime.from(LocalTime.of(12, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_from_TemporalAccessor_null() { + ZonedDateTime.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // parse() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleToString", groups={"tck"}) + public void test_parse(int y, int month, int d, int h, int m, int s, int n, String zoneId, String text) { + ZonedDateTime t = ZonedDateTime.parse(text); + assertEquals(t.getYear(), y); + assertEquals(t.getMonth().getValue(), month); + assertEquals(t.getDayOfMonth(), d); + assertEquals(t.getHour(), h); + assertEquals(t.getMinute(), m); + assertEquals(t.getSecond(), s); + assertEquals(t.getNano(), n); + assertEquals(t.getZone().getId(), zoneId); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_illegalValue() { + ZonedDateTime.parse("2008-06-32T11:15+01:00[Europe/Paris]"); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_invalidValue() { + ZonedDateTime.parse("2008-06-31T11:15+01:00[Europe/Paris]"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_nullText() { + ZonedDateTime.parse((String) null); + } + + //----------------------------------------------------------------------- + // parse(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_parse_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d H m s I"); + ZonedDateTime test = ZonedDateTime.parse("2010 12 3 11 30 0 Europe/London", f); + assertEquals(test, ZonedDateTime.of(LocalDateTime.of(2010, 12, 3, 11, 30), ZoneId.of("Europe/London"))); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullText() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d H m s"); + ZonedDateTime.parse((String) null, f); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullFormatter() { + ZonedDateTime.parse("ANY", null); + } + + //----------------------------------------------------------------------- + // basics + //----------------------------------------------------------------------- + @DataProvider(name="sampleTimes") + Object[][] provider_sampleTimes() { + return new Object[][] { + {2008, 6, 30, 11, 30, 20, 500, ZONE_0100}, + {2008, 6, 30, 11, 0, 0, 0, ZONE_0100}, + {2008, 6, 30, 11, 30, 20, 500, ZONE_PARIS}, + {2008, 6, 30, 11, 0, 0, 0, ZONE_PARIS}, + {2008, 6, 30, 23, 59, 59, 999999999, ZONE_0100}, + {-1, 1, 1, 0, 0, 0, 0, ZONE_0100}, + }; + } + + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_get(int y, int o, int d, int h, int m, int s, int n, ZoneId zone) { + LocalDate localDate = LocalDate.of(y, o, d); + LocalTime localTime = LocalTime.of(h, m, s, n); + LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime); + ZoneOffset offset = zone.getRules().getOffset(localDateTime); + ZonedDateTime a = ZonedDateTime.of(localDateTime, zone); + + assertEquals(a.getYear(), localDate.getYear()); + assertEquals(a.getMonth(), localDate.getMonth()); + assertEquals(a.getDayOfMonth(), localDate.getDayOfMonth()); + assertEquals(a.getDayOfYear(), localDate.getDayOfYear()); + assertEquals(a.getDayOfWeek(), localDate.getDayOfWeek()); + + assertEquals(a.getHour(), localTime.getHour()); + assertEquals(a.getMinute(), localTime.getMinute()); + assertEquals(a.getSecond(), localTime.getSecond()); + assertEquals(a.getNano(), localTime.getNano()); + + assertEquals(a.getDate(), localDate); + assertEquals(a.getTime(), localTime); + assertEquals(a.getDateTime(), localDateTime); + if (zone instanceof ZoneOffset) { + assertEquals(a.toString(), localDateTime.toString() + offset.toString()); + } else { + assertEquals(a.toString(), localDateTime.toString() + offset.toString() + "[" + zone.toString() + "]"); + } + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @Test + public void test_get_TemporalField() { + ZonedDateTime test = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 12, 30, 40, 987654321), ZONE_0100); + assertEquals(test.get(ChronoField.YEAR), 2008); + assertEquals(test.get(ChronoField.MONTH_OF_YEAR), 6); + assertEquals(test.get(ChronoField.DAY_OF_MONTH), 30); + assertEquals(test.get(ChronoField.DAY_OF_WEEK), 1); + assertEquals(test.get(ChronoField.DAY_OF_YEAR), 182); + + assertEquals(test.get(ChronoField.HOUR_OF_DAY), 12); + assertEquals(test.get(ChronoField.MINUTE_OF_HOUR), 30); + assertEquals(test.get(ChronoField.SECOND_OF_MINUTE), 40); + assertEquals(test.get(ChronoField.NANO_OF_SECOND), 987654321); + assertEquals(test.get(ChronoField.HOUR_OF_AMPM), 0); + assertEquals(test.get(ChronoField.AMPM_OF_DAY), 1); + + assertEquals(test.get(ChronoField.OFFSET_SECONDS), 3600); + } + + @Test + public void test_getLong_TemporalField() { + ZonedDateTime test = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 12, 30, 40, 987654321), ZONE_0100); + assertEquals(test.getLong(ChronoField.YEAR), 2008); + assertEquals(test.getLong(ChronoField.MONTH_OF_YEAR), 6); + assertEquals(test.getLong(ChronoField.DAY_OF_MONTH), 30); + assertEquals(test.getLong(ChronoField.DAY_OF_WEEK), 1); + assertEquals(test.getLong(ChronoField.DAY_OF_YEAR), 182); + + assertEquals(test.getLong(ChronoField.HOUR_OF_DAY), 12); + assertEquals(test.getLong(ChronoField.MINUTE_OF_HOUR), 30); + assertEquals(test.getLong(ChronoField.SECOND_OF_MINUTE), 40); + assertEquals(test.getLong(ChronoField.NANO_OF_SECOND), 987654321); + assertEquals(test.getLong(ChronoField.HOUR_OF_AMPM), 0); + assertEquals(test.getLong(ChronoField.AMPM_OF_DAY), 1); + + assertEquals(test.getLong(ChronoField.OFFSET_SECONDS), 3600); + assertEquals(test.getLong(ChronoField.INSTANT_SECONDS), test.toEpochSecond()); + } + + //----------------------------------------------------------------------- + // query(TemporalQuery) + //----------------------------------------------------------------------- + @Test + public void test_query_chrono() { + assertEquals(TEST_DATE_TIME.query(Queries.chrono()), ISOChrono.INSTANCE); + assertEquals(Queries.chrono().queryFrom(TEST_DATE_TIME), ISOChrono.INSTANCE); + } + + @Test + public void test_query_zoneId() { + assertEquals(TEST_DATE_TIME.query(Queries.zoneId()), TEST_DATE_TIME.getZone()); + assertEquals(Queries.zoneId().queryFrom(TEST_DATE_TIME), TEST_DATE_TIME.getZone()); + } + + @Test + public void test_query_precision() { + assertEquals(TEST_DATE_TIME.query(Queries.precision()), NANOS); + assertEquals(Queries.precision().queryFrom(TEST_DATE_TIME), NANOS); + } + + @Test + public void test_query_offset() { + assertEquals(TEST_DATE_TIME.query(Queries.offset()), TEST_DATE_TIME.getOffset()); + assertEquals(Queries.offset().queryFrom(TEST_DATE_TIME), TEST_DATE_TIME.getOffset()); + } + + @Test + public void test_query_zone() { + assertEquals(TEST_DATE_TIME.query(Queries.zone()), TEST_DATE_TIME.getZone()); + assertEquals(Queries.zone().queryFrom(TEST_DATE_TIME), TEST_DATE_TIME.getZone()); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_query_null() { + TEST_DATE_TIME.query(null); + } + + //----------------------------------------------------------------------- + // withEarlierOffsetAtOverlap() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withEarlierOffsetAtOverlap_notAtOverlap() { + ZonedDateTime base = ZonedDateTime.ofStrict(TEST_LOCAL_2008_06_30_11_30_59_500, OFFSET_0200, ZONE_PARIS); + ZonedDateTime test = base.withEarlierOffsetAtOverlap(); + assertEquals(test, base); // not changed + } + + @Test(groups={"tck"}) + public void test_withEarlierOffsetAtOverlap_atOverlap() { + ZonedDateTime base = ZonedDateTime.ofStrict(TEST_PARIS_OVERLAP_2008_10_26_02_30, OFFSET_0100, ZONE_PARIS); + ZonedDateTime test = base.withEarlierOffsetAtOverlap(); + assertEquals(test.getOffset(), OFFSET_0200); // offset changed to earlier + assertEquals(test.getDateTime(), base.getDateTime()); // date-time not changed + } + + @Test(groups={"tck"}) + public void test_withEarlierOffsetAtOverlap_atOverlap_noChange() { + ZonedDateTime base = ZonedDateTime.ofStrict(TEST_PARIS_OVERLAP_2008_10_26_02_30, OFFSET_0200, ZONE_PARIS); + ZonedDateTime test = base.withEarlierOffsetAtOverlap(); + assertEquals(test, base); // not changed + } + + //----------------------------------------------------------------------- + // withLaterOffsetAtOverlap() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withLaterOffsetAtOverlap_notAtOverlap() { + ZonedDateTime base = ZonedDateTime.ofStrict(TEST_LOCAL_2008_06_30_11_30_59_500, OFFSET_0200, ZONE_PARIS); + ZonedDateTime test = base.withLaterOffsetAtOverlap(); + assertEquals(test, base); // not changed + } + + @Test(groups={"tck"}) + public void test_withLaterOffsetAtOverlap_atOverlap() { + ZonedDateTime base = ZonedDateTime.ofStrict(TEST_PARIS_OVERLAP_2008_10_26_02_30, OFFSET_0200, ZONE_PARIS); + ZonedDateTime test = base.withLaterOffsetAtOverlap(); + assertEquals(test.getOffset(), OFFSET_0100); // offset changed to later + assertEquals(test.getDateTime(), base.getDateTime()); // date-time not changed + } + + @Test(groups={"tck"}) + public void test_withLaterOffsetAtOverlap_atOverlap_noChange() { + ZonedDateTime base = ZonedDateTime.ofStrict(TEST_PARIS_OVERLAP_2008_10_26_02_30, OFFSET_0100, ZONE_PARIS); + ZonedDateTime test = base.withLaterOffsetAtOverlap(); + assertEquals(test, base); // not changed + } + + //----------------------------------------------------------------------- + // withZoneSameLocal(ZoneId) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withZoneSameLocal() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.withZoneSameLocal(ZONE_0200); + assertEquals(test.getDateTime(), base.getDateTime()); + } + + @Test(groups={"implementation"}) + public void test_withZoneSameLocal_noChange() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.withZoneSameLocal(ZONE_0100); + assertEquals(test, base); + } + + @Test(groups={"tck"}) + public void test_withZoneSameLocal_retainOffset1() { + LocalDateTime ldt = LocalDateTime.of(2008, 11, 2, 1, 30, 59, 0); // overlap + ZonedDateTime base = ZonedDateTime.of(ldt, ZoneId.of("UTC-04:00") ); + ZonedDateTime test = base.withZoneSameLocal(ZoneId.of("America/New_York")); + assertEquals(base.getOffset(), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(), ZoneOffset.ofHours(-4)); + } + + @Test(groups={"tck"}) + public void test_withZoneSameLocal_retainOffset2() { + LocalDateTime ldt = LocalDateTime.of(2008, 11, 2, 1, 30, 59, 0); // overlap + ZonedDateTime base = ZonedDateTime.of(ldt, ZoneId.of("UTC-05:00") ); + ZonedDateTime test = base.withZoneSameLocal(ZoneId.of("America/New_York")); + assertEquals(base.getOffset(), ZoneOffset.ofHours(-5)); + assertEquals(test.getOffset(), ZoneOffset.ofHours(-5)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_withZoneSameLocal_null() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + base.withZoneSameLocal(null); + } + + //----------------------------------------------------------------------- + // withZoneSameInstant() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withZoneSameInstant() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withZoneSameInstant(ZONE_0200); + ZonedDateTime expected = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500.plusHours(1), ZONE_0200); + assertEquals(test, expected); + } + + @Test(groups={"tck"}) + public void test_withZoneSameInstant_noChange() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withZoneSameInstant(ZONE_0100); + assertEquals(test, base); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_withZoneSameInstant_null() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + base.withZoneSameInstant(null); + } + + //----------------------------------------------------------------------- + // withFixedOffsetZone() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withZoneLocked() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_PARIS); + ZonedDateTime test = base.withFixedOffsetZone(); + ZonedDateTime expected = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0200); + assertEquals(test, expected); + } + + //----------------------------------------------------------------------- + // with(WithAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_with_WithAdjuster_LocalDateTime_sameOffset() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_PARIS); + ZonedDateTime test = base.with(LocalDateTime.of(2012, 7, 15, 14, 30)); + check(test, 2012, 7, 15, 14, 30, 0, 0, OFFSET_0200, ZONE_PARIS); + } + + @Test(groups={"tck"}) + public void test_with_WithAdjuster_LocalDateTime_adjustedOffset() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_PARIS); + ZonedDateTime test = base.with(LocalDateTime.of(2012, 1, 15, 14, 30)); + check(test, 2012, 1, 15, 14, 30, 0, 0, OFFSET_0100, ZONE_PARIS); + } + + @Test(groups={"tck"}) + public void test_with_WithAdjuster_LocalDate() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_PARIS); + ZonedDateTime test = base.with(LocalDate.of(2012, 7, 28)); + check(test, 2012, 7, 28, 11, 30, 59, 500, OFFSET_0200, ZONE_PARIS); + } + + @Test(groups={"tck"}) + public void test_with_WithAdjuster_LocalTime() { + ZonedDateTime base = ZonedDateTime.of(TEST_PARIS_OVERLAP_2008_10_26_02_30, ZONE_PARIS); + ZonedDateTime test = base.with(LocalTime.of(2, 29)); + check(test, 2008, 10, 26, 2, 29, 0, 0, OFFSET_0200, ZONE_PARIS); + } + + @Test(groups={"tck"}) + public void test_with_WithAdjuster_Year() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.with(Year.of(2007)); + assertEquals(test, ZonedDateTime.of(ldt.withYear(2007), ZONE_0100)); + } + + @Test(groups={"tck"}) + public void test_with_WithAdjuster_Month_adjustedDayOfMonth() { + ZonedDateTime base = ZonedDateTime.of(LocalDateTime.of(2012, 7, 31, 0, 0), ZONE_PARIS); + ZonedDateTime test = base.with(Month.JUNE); + check(test, 2012, 6, 30, 0, 0, 0, 0, OFFSET_0200, ZONE_PARIS); + } + + @Test(groups={"tck"}) + public void test_with_WithAdjuster_Offset_same() { + ZonedDateTime base = ZonedDateTime.of(LocalDateTime.of(2012, 7, 31, 0, 0), ZONE_PARIS); + ZonedDateTime test = base.with(ZoneOffset.ofHours(2)); + check(test, 2012, 7, 31, 0, 0, 0, 0, OFFSET_0200, ZONE_PARIS); + } + + @Test(groups={"tck"}) + public void test_with_WithAdjuster_Offset_timeAdjust() { + ZonedDateTime base = ZonedDateTime.of(LocalDateTime.of(2012, 7, 31, 0, 0), ZONE_PARIS); + ZonedDateTime test = base.with(ZoneOffset.ofHours(1)); + check(test, 2012, 7, 31, 1, 0, 0, 0, OFFSET_0200, ZONE_PARIS); // time adjusted + } + + @Test(groups={"tck"}) + public void test_with_WithAdjuster_LocalDate_retainOffset1() { + ZoneId newYork = ZoneId.of("America/New_York"); + LocalDateTime ldt = LocalDateTime.of(2008, 11, 1, 1, 30); + ZonedDateTime base = ZonedDateTime.of(ldt, newYork); + assertEquals(base.getOffset(), ZoneOffset.ofHours(-4)); + ZonedDateTime test = base.with(LocalDate.of(2008, 11, 2)); + assertEquals(test.getOffset(), ZoneOffset.ofHours(-4)); + } + + @Test(groups={"tck"}) + public void test_with_WithAdjuster_LocalDate_retainOffset2() { + ZoneId newYork = ZoneId.of("America/New_York"); + LocalDateTime ldt = LocalDateTime.of(2008, 11, 3, 1, 30); + ZonedDateTime base = ZonedDateTime.of(ldt, newYork); + assertEquals(base.getOffset(), ZoneOffset.ofHours(-5)); + ZonedDateTime test = base.with(LocalDate.of(2008, 11, 2)); + assertEquals(test.getOffset(), ZoneOffset.ofHours(-5)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_with_WithAdjuster_null() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + base.with((TemporalAdjuster) null); + } + + //----------------------------------------------------------------------- + // withYear() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withYear_normal() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withYear(2007); + assertEquals(test, ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500.withYear(2007), ZONE_0100)); + } + + @Test(groups={"tck"}) + public void test_withYear_noChange() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withYear(2008); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // with(Month) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withMonth_Month_normal() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.with(JANUARY); + assertEquals(test, ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500.withMonth(1), ZONE_0100)); + } + + @Test(expectedExceptions = NullPointerException.class, groups={"tck"}) + public void test_withMonth_Month_null() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + base.with((Month) null); + } + + //----------------------------------------------------------------------- + // withMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withMonth_normal() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withMonth(1); + assertEquals(test, ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500.withMonth(1), ZONE_0100)); + } + + @Test(groups={"tck"}) + public void test_withMonth_noChange() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withMonth(6); + assertEquals(test, base); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withMonth_tooBig() { + TEST_DATE_TIME.withMonth(13); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withMonth_tooSmall() { + TEST_DATE_TIME.withMonth(0); + } + + //----------------------------------------------------------------------- + // withDayOfMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withDayOfMonth_normal() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withDayOfMonth(15); + assertEquals(test, ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500.withDayOfMonth(15), ZONE_0100)); + } + + @Test(groups={"tck"}) + public void test_withDayOfMonth_noChange() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withDayOfMonth(30); + assertEquals(test, base); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfMonth_tooBig() { + LocalDateTime.of(2007, 7, 2, 11, 30).atZone(ZONE_PARIS).withDayOfMonth(32); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfMonth_tooSmall() { + TEST_DATE_TIME.withDayOfMonth(0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfMonth_invalid31() { + LocalDateTime.of(2007, 6, 2, 11, 30).atZone(ZONE_PARIS).withDayOfMonth(31); + } + + //----------------------------------------------------------------------- + // withDayOfYear() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withDayOfYear_normal() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withDayOfYear(33); + assertEquals(test, ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500.withDayOfYear(33), ZONE_0100)); + } + + @Test(groups={"tck"}) + public void test_withDayOfYear_noChange() { + LocalDateTime ldt = LocalDateTime.of(2008, 2, 5, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.withDayOfYear(36); + assertEquals(test, base); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfYear_tooBig() { + TEST_DATE_TIME.withDayOfYear(367); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfYear_tooSmall() { + TEST_DATE_TIME.withDayOfYear(0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfYear_invalid366() { + LocalDateTime.of(2007, 2, 2, 11, 30).atZone(ZONE_PARIS).withDayOfYear(366); + } + + //----------------------------------------------------------------------- + // withHour() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withHour_normal() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withHour(15); + assertEquals(test, ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500.withHour(15), ZONE_0100)); + } + + @Test(groups={"tck"}) + public void test_withHour_noChange() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withHour(11); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // withMinute() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withMinute_normal() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withMinute(15); + assertEquals(test, ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500.withMinute(15), ZONE_0100)); + } + + @Test(groups={"tck"}) + public void test_withMinute_noChange() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withMinute(30); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // withSecond() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withSecond_normal() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withSecond(12); + assertEquals(test, ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500.withSecond(12), ZONE_0100)); + } + + @Test(groups={"tck"}) + public void test_withSecond_noChange() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withSecond(59); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // withNano() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withNanoOfSecond_normal() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withNano(15); + assertEquals(test, ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500.withNano(15), ZONE_0100)); + } + + @Test(groups={"tck"}) + public void test_withNanoOfSecond_noChange() { + ZonedDateTime base = ZonedDateTime.of(TEST_LOCAL_2008_06_30_11_30_59_500, ZONE_0100); + ZonedDateTime test = base.withNano(500); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // truncatedTo(TemporalUnit) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_truncatedTo_normal() { + assertEquals(TEST_DATE_TIME.truncatedTo(NANOS), TEST_DATE_TIME); + assertEquals(TEST_DATE_TIME.truncatedTo(SECONDS), TEST_DATE_TIME.withNano(0)); + assertEquals(TEST_DATE_TIME.truncatedTo(DAYS), TEST_DATE_TIME.with(LocalTime.MIDNIGHT)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_truncatedTo_null() { + TEST_DATE_TIME.truncatedTo(null); + } + + //----------------------------------------------------------------------- + // plus/minus + //----------------------------------------------------------------------- + @DataProvider(name="plusDays") + Object[][] data_plusDays() { + return new Object[][] { + // normal + {dateTime(2008, 6, 30, 23, 30, 59, 0, OFFSET_0100, ZONE_0100), 0, dateTime(2008, 6, 30, 23, 30, 59, 0, OFFSET_0100, ZONE_0100)}, + {dateTime(2008, 6, 30, 23, 30, 59, 0, OFFSET_0100, ZONE_0100), 1, dateTime(2008, 7, 1, 23, 30, 59, 0, OFFSET_0100, ZONE_0100)}, + {dateTime(2008, 6, 30, 23, 30, 59, 0, OFFSET_0100, ZONE_0100), -1, dateTime(2008, 6, 29, 23, 30, 59, 0, OFFSET_0100, ZONE_0100)}, + // skip over gap + {dateTime(2008, 3, 30, 1, 30, 0, 0, OFFSET_0100, ZONE_PARIS), 1, dateTime(2008, 3, 31, 1, 30, 0, 0, OFFSET_0200, ZONE_PARIS)}, + {dateTime(2008, 3, 30, 3, 30, 0, 0, OFFSET_0200, ZONE_PARIS), -1, dateTime(2008, 3, 29, 3, 30, 0, 0, OFFSET_0100, ZONE_PARIS)}, + // land in gap + {dateTime(2008, 3, 29, 2, 30, 0, 0, OFFSET_0100, ZONE_PARIS), 1, dateTime(2008, 3, 30, 3, 30, 0, 0, OFFSET_0200, ZONE_PARIS)}, + {dateTime(2008, 3, 31, 2, 30, 0, 0, OFFSET_0200, ZONE_PARIS), -1, dateTime(2008, 3, 30, 3, 30, 0, 0, OFFSET_0200, ZONE_PARIS)}, + // skip over overlap + {dateTime(2008, 10, 26, 1, 30, 0, 0, OFFSET_0200, ZONE_PARIS), 1, dateTime(2008, 10, 27, 1, 30, 0, 0, OFFSET_0100, ZONE_PARIS)}, + {dateTime(2008, 10, 25, 3, 30, 0, 0, OFFSET_0200, ZONE_PARIS), 1, dateTime(2008, 10, 26, 3, 30, 0, 0, OFFSET_0100, ZONE_PARIS)}, + // land in overlap + {dateTime(2008, 10, 25, 2, 30, 0, 0, OFFSET_0200, ZONE_PARIS), 1, dateTime(2008, 10, 26, 2, 30, 0, 0, OFFSET_0200, ZONE_PARIS)}, + {dateTime(2008, 10, 27, 2, 30, 0, 0, OFFSET_0100, ZONE_PARIS), -1, dateTime(2008, 10, 26, 2, 30, 0, 0, OFFSET_0100, ZONE_PARIS)}, + }; + } + + @DataProvider(name="plusTime") + Object[][] data_plusTime() { + return new Object[][] { + // normal + {dateTime(2008, 6, 30, 23, 30, 59, 0, OFFSET_0100, ZONE_0100), 0, dateTime(2008, 6, 30, 23, 30, 59, 0, OFFSET_0100, ZONE_0100)}, + {dateTime(2008, 6, 30, 23, 30, 59, 0, OFFSET_0100, ZONE_0100), 1, dateTime(2008, 7, 1, 0, 30, 59, 0, OFFSET_0100, ZONE_0100)}, + {dateTime(2008, 6, 30, 23, 30, 59, 0, OFFSET_0100, ZONE_0100), -1, dateTime(2008, 6, 30, 22, 30, 59, 0, OFFSET_0100, ZONE_0100)}, + // gap + {dateTime(2008, 3, 30, 1, 30, 0, 0, OFFSET_0100, ZONE_PARIS), 1, dateTime(2008, 3, 30, 3, 30, 0, 0, OFFSET_0200, ZONE_PARIS)}, + {dateTime(2008, 3, 30, 3, 30, 0, 0, OFFSET_0200, ZONE_PARIS), -1, dateTime(2008, 3, 30, 1, 30, 0, 0, OFFSET_0100, ZONE_PARIS)}, + // overlap + {dateTime(2008, 10, 26, 1, 30, 0, 0, OFFSET_0200, ZONE_PARIS), 1, dateTime(2008, 10, 26, 2, 30, 0, 0, OFFSET_0200, ZONE_PARIS)}, + {dateTime(2008, 10, 26, 1, 30, 0, 0, OFFSET_0200, ZONE_PARIS), 2, dateTime(2008, 10, 26, 2, 30, 0, 0, OFFSET_0100, ZONE_PARIS)}, + {dateTime(2008, 10, 26, 1, 30, 0, 0, OFFSET_0200, ZONE_PARIS), 3, dateTime(2008, 10, 26, 3, 30, 0, 0, OFFSET_0100, ZONE_PARIS)}, + {dateTime(2008, 10, 26, 2, 30, 0, 0, OFFSET_0200, ZONE_PARIS), 1, dateTime(2008, 10, 26, 2, 30, 0, 0, OFFSET_0100, ZONE_PARIS)}, + {dateTime(2008, 10, 26, 2, 30, 0, 0, OFFSET_0200, ZONE_PARIS), 2, dateTime(2008, 10, 26, 3, 30, 0, 0, OFFSET_0100, ZONE_PARIS)}, + }; + } + + //----------------------------------------------------------------------- + // plus(adjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="plusDays") + public void test_plus_adjuster_Period_days(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.plus(Period.of(amount, DAYS)), expected); + } + + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_plus_adjuster_Period_hours(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.plus(Period.of(amount, HOURS)), expected); + } + + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_plus_adjuster_Duration_hours(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.plus(Duration.ofHours(amount)), expected); + } + + @Test(groups={"tck"}) + public void test_plus_adjuster() { + MockSimplePeriod period = MockSimplePeriod.of(7, ChronoUnit.MONTHS); + ZonedDateTime t = ZonedDateTime.of(LocalDateTime.of(2008, 6, 1, 12, 30, 59, 500), ZONE_0100); + ZonedDateTime expected = ZonedDateTime.of(LocalDateTime.of(2009, 1, 1, 12, 30, 59, 500), ZONE_0100); + assertEquals(t.plus(period), expected); + } + + @Test(groups={"tck"}) + public void test_plus_adjuster_Duration() { + Duration duration = Duration.ofSeconds(4L * 60 * 60 + 5L * 60 + 6L); + ZonedDateTime t = ZonedDateTime.of(LocalDateTime.of(2008, 6, 1, 12, 30, 59, 500), ZONE_0100); + ZonedDateTime expected = ZonedDateTime.of(LocalDateTime.of(2008, 6, 1, 16, 36, 5, 500), ZONE_0100); + assertEquals(t.plus(duration), expected); + } + + @Test(groups={"tck"}) + public void test_plus_adjuster_Period_zero() { + ZonedDateTime t = TEST_DATE_TIME.plus(MockSimplePeriod.ZERO_DAYS); + assertEquals(t, TEST_DATE_TIME); + } + + @Test(groups={"tck"}) + public void test_plus_adjuster_Duration_zero() { + ZonedDateTime t = TEST_DATE_TIME.plus(Duration.ZERO); + assertEquals(t, TEST_DATE_TIME); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_plus_adjuster_null() { + TEST_DATE_TIME.plus((TemporalAdder) null); + } + + //----------------------------------------------------------------------- + // plus(long,TemporalUnit) + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="plusDays") + public void test_plus_longUnit_days(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.plus(amount, DAYS), expected); + } + + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_plus_longUnit_hours(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.plus(amount, HOURS), expected); + } + + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_plus_longUnit_minutes(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.plus(amount * 60, MINUTES), expected); + } + + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_plus_longUnit_seconds(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.plus(amount * 3600, SECONDS), expected); + } + + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_plus_longUnit_nanos(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.plus(amount * 3600_000_000_000L, NANOS), expected); + } + + @Test(groups={"tck"}, expectedExceptions=NullPointerException.class) + public void test_plus_longUnit_null() { + TEST_DATE_TIME_PARIS.plus(0, null); + } + + //----------------------------------------------------------------------- + // plusYears() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusYears() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.plusYears(1); + assertEquals(test, ZonedDateTime.of(ldt.plusYears(1), ZONE_0100)); + } + + @Test(groups={"tck"}) + public void test_plusYears_zero() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.plusYears(0); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // plusMonths() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusMonths() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.plusMonths(1); + assertEquals(test, ZonedDateTime.of(ldt.plusMonths(1), ZONE_0100)); + } + + @Test(groups={"tck"}) + public void test_plusMonths_zero() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.plusMonths(0); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // plusWeeks() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusWeeks() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.plusWeeks(1); + assertEquals(test, ZonedDateTime.of(ldt.plusWeeks(1), ZONE_0100)); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_zero() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.plusWeeks(0); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // plusDays() + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="plusDays") + public void test_plusDays(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.plusDays(amount), expected); + } + + //----------------------------------------------------------------------- + // plusHours() + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_plusHours(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.plusHours(amount), expected); + } + + //----------------------------------------------------------------------- + // plusMinutes() + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_plusMinutes(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.plusMinutes(amount * 60), expected); + } + + @Test(groups={"tck"}) + public void test_plusMinutes_minutes() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.plusMinutes(30); + assertEquals(test, ZonedDateTime.of(ldt.plusMinutes(30), ZONE_0100)); + } + + //----------------------------------------------------------------------- + // plusSeconds() + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_plusSeconds(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.plusSeconds(amount * 3600), expected); + } + + @Test(groups={"tck"}) + public void test_plusSeconds_seconds() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.plusSeconds(1); + assertEquals(test, ZonedDateTime.of(ldt.plusSeconds(1), ZONE_0100)); + } + + //----------------------------------------------------------------------- + // plusNanos() + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_plusNanos(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.plusNanos(amount * 3600_000_000_000L), expected); + } + + @Test(groups={"tck"}) + public void test_plusNanos_nanos() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.plusNanos(1); + assertEquals(test, ZonedDateTime.of(ldt.plusNanos(1), ZONE_0100)); + } + + //----------------------------------------------------------------------- + // minus(adjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="plusDays") + public void test_minus_adjuster_Period_days(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.minus(Period.of(-amount, DAYS)), expected); + } + + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_minus_adjuster_Period_hours(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.minus(Period.of(-amount, HOURS)), expected); + } + + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_minus_adjuster_Duration_hours(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.minus(Duration.ofHours(-amount)), expected); + } + + @Test(groups={"tck"}) + public void test_minus_adjuster() { + MockSimplePeriod period = MockSimplePeriod.of(7, ChronoUnit.MONTHS); + ZonedDateTime t = ZonedDateTime.of(LocalDateTime.of(2008, 6, 1, 12, 30, 59, 500), ZONE_0100); + ZonedDateTime expected = ZonedDateTime.of(LocalDateTime.of(2007, 11, 1, 12, 30, 59, 500), ZONE_0100); + assertEquals(t.minus(period), expected); + } + + @Test(groups={"tck"}) + public void test_minus_adjuster_Duration() { + Duration duration = Duration.ofSeconds(4L * 60 * 60 + 5L * 60 + 6L); + ZonedDateTime t = ZonedDateTime.of(LocalDateTime.of(2008, 6, 1, 12, 30, 59, 500), ZONE_0100); + ZonedDateTime expected = ZonedDateTime.of(LocalDateTime.of(2008, 6, 1, 8, 25, 53, 500), ZONE_0100); + assertEquals(t.minus(duration), expected); + } + + @Test(groups={"tck"}) + public void test_minus_adjuster_Period_zero() { + ZonedDateTime t = TEST_DATE_TIME.minus(MockSimplePeriod.ZERO_DAYS); + assertEquals(t, TEST_DATE_TIME); + } + + @Test(groups={"tck"}) + public void test_minus_adjuster_Duration_zero() { + ZonedDateTime t = TEST_DATE_TIME.minus(Duration.ZERO); + assertEquals(t, TEST_DATE_TIME); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_minus_adjuster_null() { + TEST_DATE_TIME.minus((TemporalSubtractor) null); + } + + //----------------------------------------------------------------------- + // minusYears() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusYears() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.minusYears(1); + assertEquals(test, ZonedDateTime.of(ldt.minusYears(1), ZONE_0100)); + } + + @Test(groups={"tck"}) + public void test_minusYears_zero() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.minusYears(0); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // minusMonths() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusMonths() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.minusMonths(1); + assertEquals(test, ZonedDateTime.of(ldt.minusMonths(1), ZONE_0100)); + } + + @Test(groups={"tck"}) + public void test_minusMonths_zero() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.minusMonths(0); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // minusWeeks() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusWeeks() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.minusWeeks(1); + assertEquals(test, ZonedDateTime.of(ldt.minusWeeks(1), ZONE_0100)); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_zero() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.minusWeeks(0); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // minusDays() + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="plusDays") + public void test_minusDays(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.minusDays(-amount), expected); + } + + //----------------------------------------------------------------------- + // minusHours() + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_minusHours(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.minusHours(-amount), expected); + } + + //----------------------------------------------------------------------- + // minusMinutes() + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_minusMinutes(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.minusMinutes(-amount * 60), expected); + } + + @Test(groups={"tck"}) + public void test_minusMinutes_minutes() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.minusMinutes(30); + assertEquals(test, ZonedDateTime.of(ldt.minusMinutes(30), ZONE_0100)); + } + + //----------------------------------------------------------------------- + // minusSeconds() + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_minusSeconds(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.minusSeconds(-amount * 3600), expected); + } + + @Test(groups={"tck"}) + public void test_minusSeconds_seconds() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.minusSeconds(1); + assertEquals(test, ZonedDateTime.of(ldt.minusSeconds(1), ZONE_0100)); + } + + //----------------------------------------------------------------------- + // minusNanos() + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_minusNanos(ZonedDateTime base, long amount, ZonedDateTime expected) { + assertEquals(base.minusNanos(-amount * 3600_000_000_000L), expected); + } + + @Test(groups={"tck"}) + public void test_minusNanos_nanos() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime base = ZonedDateTime.of(ldt, ZONE_0100); + ZonedDateTime test = base.minusNanos(1); + assertEquals(test, ZonedDateTime.of(ldt.minusNanos(1), ZONE_0100)); + } + + //----------------------------------------------------------------------- + // periodUntil(Temporal,TemporalUnit) + //----------------------------------------------------------------------- + // TODO: more tests for period between two different zones + // compare results to OffsetDateTime.periodUntil, especially wrt dates + + @Test(groups={"tck"}, dataProvider="plusDays") + public void test_periodUntil_days(ZonedDateTime base, long expected, ZonedDateTime end) { + assertEquals(base.periodUntil(end, DAYS), expected); + } + + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_periodUntil_hours(ZonedDateTime base, long expected, ZonedDateTime end) { + assertEquals(base.periodUntil(end, HOURS), expected); + } + + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_periodUntil_minutes(ZonedDateTime base, long expected, ZonedDateTime end) { + assertEquals(base.periodUntil(end, MINUTES), expected * 60); + } + + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_periodUntil_seconds(ZonedDateTime base, long expected, ZonedDateTime end) { + assertEquals(base.periodUntil(end, SECONDS), expected * 3600); + } + + @Test(groups={"tck"}, dataProvider="plusTime") + public void test_periodUntil_nanos(ZonedDateTime base, long expected, ZonedDateTime end) { + assertEquals(base.periodUntil(end, NANOS), expected * 3600_000_000_000L); + } + + @Test(groups={"tck"}) + public void test_periodUntil_parisLondon() { + ZonedDateTime midnightLondon = LocalDate.of(2012, 6, 28).atStartOfDay(ZONE_LONDON); + ZonedDateTime midnightParis1 = LocalDate.of(2012, 6, 29).atStartOfDay(ZONE_PARIS); + ZonedDateTime oneAm1 = LocalDateTime.of(2012, 6, 29, 1, 0).atZone(ZONE_PARIS); + ZonedDateTime midnightParis2 = LocalDate.of(2012, 6, 30).atStartOfDay(ZONE_PARIS); + + assertEquals(midnightLondon.periodUntil(midnightParis1, HOURS), 23); + assertEquals(midnightLondon.periodUntil(oneAm1, HOURS), 24); + assertEquals(midnightLondon.periodUntil(midnightParis2, HOURS), 23 + 24); + + assertEquals(midnightLondon.periodUntil(midnightParis1, DAYS), 0); + assertEquals(midnightLondon.periodUntil(oneAm1, DAYS), 1); + assertEquals(midnightLondon.periodUntil(midnightParis2, DAYS), 1); + } + + @Test(groups={"tck"}) + public void test_periodUntil_gap() { + ZonedDateTime before = TEST_PARIS_GAP_2008_03_30_02_30.withHour(0).withMinute(0).atZone(ZONE_PARIS); + ZonedDateTime after = TEST_PARIS_GAP_2008_03_30_02_30.withHour(0).withMinute(0).plusDays(1).atZone(ZONE_PARIS); + + assertEquals(before.periodUntil(after, HOURS), 23); + assertEquals(before.periodUntil(after, DAYS), 1); + } + + @Test(groups={"tck"}) + public void test_periodUntil_overlap() { + ZonedDateTime before = TEST_PARIS_OVERLAP_2008_10_26_02_30.withHour(0).withMinute(0).atZone(ZONE_PARIS); + ZonedDateTime after = TEST_PARIS_OVERLAP_2008_10_26_02_30.withHour(0).withMinute(0).plusDays(1).atZone(ZONE_PARIS); + + assertEquals(before.periodUntil(after, HOURS), 25); + assertEquals(before.periodUntil(after, DAYS), 1); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_periodUntil_differentType() { + TEST_DATE_TIME_PARIS.periodUntil(TEST_LOCAL_2008_06_30_11_30_59_500, DAYS); + } + + @Test(groups={"tck"}, expectedExceptions=NullPointerException.class) + public void test_periodUntil_nullTemporal() { + TEST_DATE_TIME_PARIS.periodUntil(null, DAYS); + } + + @Test(groups={"tck"}, expectedExceptions=NullPointerException.class) + public void test_periodUntil_nullUnit() { + TEST_DATE_TIME_PARIS.periodUntil(TEST_DATE_TIME_PARIS, null); + } + + //----------------------------------------------------------------------- + // toOffsetDateTime() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toOffsetDateTime() { + assertEquals(TEST_DATE_TIME.toOffsetDateTime(), OffsetDateTime.of(TEST_DATE_TIME.getDateTime(), TEST_DATE_TIME.getOffset())); + } + + //----------------------------------------------------------------------- + // toInstant() + //----------------------------------------------------------------------- + @DataProvider(name="toInstant") + Object[][] data_toInstant() { + return new Object[][] { + {LocalDateTime.of(1970, 1, 1, 0, 0, 0, 0), 0L, 0}, + {LocalDateTime.of(1970, 1, 1, 0, 0, 0, 1), 0L, 1}, + {LocalDateTime.of(1970, 1, 1, 0, 0, 0, 999_999_999), 0L, 999_999_999}, + {LocalDateTime.of(1970, 1, 1, 0, 0, 1, 0), 1L, 0}, + {LocalDateTime.of(1970, 1, 1, 0, 0, 1, 1), 1L, 1}, + {LocalDateTime.of(1969, 12, 31, 23, 59, 59, 999999999), -1L, 999_999_999}, + {LocalDateTime.of(1970, 1, 2, 0, 0), 24L * 60L * 60L, 0}, + {LocalDateTime.of(1969, 12, 31, 0, 0), -24L * 60L * 60L, 0}, + }; + } + + @Test(groups={"tck"}, dataProvider="toInstant") + public void test_toInstant_UTC(LocalDateTime ldt, long expectedEpSec, int expectedNos) { + ZonedDateTime dt = ldt.atZone(ZoneOffset.UTC); + Instant test = dt.toInstant(); + assertEquals(test.getEpochSecond(), expectedEpSec); + assertEquals(test.getNano(), expectedNos); + } + + @Test(groups={"tck"}, dataProvider="toInstant") + public void test_toInstant_P0100(LocalDateTime ldt, long expectedEpSec, int expectedNos) { + ZonedDateTime dt = ldt.atZone(ZONE_0100); + Instant test = dt.toInstant(); + assertEquals(test.getEpochSecond(), expectedEpSec - 3600); + assertEquals(test.getNano(), expectedNos); + } + + @Test(groups={"tck"}, dataProvider="toInstant") + public void test_toInstant_M0100(LocalDateTime ldt, long expectedEpSec, int expectedNos) { + ZonedDateTime dt = ldt.atZone(ZONE_M0100); + Instant test = dt.toInstant(); + assertEquals(test.getEpochSecond(), expectedEpSec + 3600); + assertEquals(test.getNano(), expectedNos); + } + + //----------------------------------------------------------------------- + // toEpochSecond() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toEpochSecond_afterEpoch() { + LocalDateTime ldt = LocalDateTime.of(1970, 1, 1, 0, 0).plusHours(1); + for (int i = 0; i < 100000; i++) { + ZonedDateTime a = ZonedDateTime.of(ldt, ZONE_PARIS); + assertEquals(a.toEpochSecond(), i); + ldt = ldt.plusSeconds(1); + } + } + + @Test(groups={"tck"}) + public void test_toEpochSecond_beforeEpoch() { + LocalDateTime ldt = LocalDateTime.of(1970, 1, 1, 0, 0).plusHours(1); + for (int i = 0; i < 100000; i++) { + ZonedDateTime a = ZonedDateTime.of(ldt, ZONE_PARIS); + assertEquals(a.toEpochSecond(), -i); + ldt = ldt.minusSeconds(1); + } + } + + @Test(groups={"tck"}, dataProvider="toInstant") + public void test_toEpochSecond_UTC(LocalDateTime ldt, long expectedEpSec, int expectedNos) { + ZonedDateTime dt = ldt.atZone(ZoneOffset.UTC); + assertEquals(dt.toEpochSecond(), expectedEpSec); + } + + @Test(groups={"tck"}, dataProvider="toInstant") + public void test_toEpochSecond_P0100(LocalDateTime ldt, long expectedEpSec, int expectedNos) { + ZonedDateTime dt = ldt.atZone(ZONE_0100); + assertEquals(dt.toEpochSecond(), expectedEpSec - 3600); + } + + @Test(groups={"tck"}, dataProvider="toInstant") + public void test_toEpochSecond_M0100(LocalDateTime ldt, long expectedEpSec, int expectedNos) { + ZonedDateTime dt = ldt.atZone(ZONE_M0100); + assertEquals(dt.toEpochSecond(), expectedEpSec + 3600); + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_compareTo_time1() { + ZonedDateTime a = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 30, 39), ZONE_0100); + ZonedDateTime b = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 30, 41), ZONE_0100); // a is before b due to time + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_time2() { + ZonedDateTime a = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 30, 40, 4), ZONE_0100); + ZonedDateTime b = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 30, 40, 5), ZONE_0100); // a is before b due to time + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_offset1() { + ZonedDateTime a = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 30, 41), ZONE_0200); + ZonedDateTime b = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 30, 39), ZONE_0100); // a is before b due to offset + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_offset2() { + ZonedDateTime a = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 30, 40, 5), ZoneId.of("UTC+01:01")); + ZonedDateTime b = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 30, 40, 4), ZONE_0100); // a is before b due to offset + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_both() { + ZonedDateTime a = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 50), ZONE_0200); + ZonedDateTime b = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 20), ZONE_0100); // a is before b on instant scale + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_bothNanos() { + ZonedDateTime a = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 20, 40, 5), ZONE_0200); + ZonedDateTime b = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 10, 20, 40, 6), ZONE_0100); // a is before b on instant scale + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_hourDifference() { + ZonedDateTime a = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 10, 0), ZONE_0100); + ZonedDateTime b = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 0), ZONE_0200); // a is before b despite being same time-line time + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_compareTo_null() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime a = ZonedDateTime.of(ldt, ZONE_0100); + a.compareTo(null); + } + + //----------------------------------------------------------------------- + // isBefore() + //----------------------------------------------------------------------- + @DataProvider(name="IsBefore") + Object[][] data_isBefore() { + return new Object[][] { + {11, 30, ZONE_0100, 11, 31, ZONE_0100, true}, // a is before b due to time + {11, 30, ZONE_0200, 11, 30, ZONE_0100, true}, // a is before b due to offset + {11, 30, ZONE_0200, 10, 30, ZONE_0100, false}, // a is equal b due to same instant + }; + } + + @Test(dataProvider="IsBefore", groups={"tck"}) + public void test_isBefore(int hour1, int minute1, ZoneId zone1, int hour2, int minute2, ZoneId zone2, boolean expected) { + ZonedDateTime a = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, hour1, minute1), zone1); + ZonedDateTime b = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, hour2, minute2), zone2); + assertEquals(a.isBefore(b), expected); + assertEquals(b.isBefore(a), false); + assertEquals(a.isBefore(a), false); + assertEquals(b.isBefore(b), false); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isBefore_null() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime a = ZonedDateTime.of(ldt, ZONE_0100); + a.isBefore(null); + } + + //----------------------------------------------------------------------- + // isAfter() + //----------------------------------------------------------------------- + @DataProvider(name="IsAfter") + Object[][] data_isAfter() { + return new Object[][] { + {11, 31, ZONE_0100, 11, 30, ZONE_0100, true}, // a is after b due to time + {11, 30, ZONE_0100, 11, 30, ZONE_0200, true}, // a is after b due to offset + {11, 30, ZONE_0200, 10, 30, ZONE_0100, false}, // a is equal b due to same instant + }; + } + + @Test(dataProvider="IsAfter", groups={"tck"}) + public void test_isAfter(int hour1, int minute1, ZoneId zone1, int hour2, int minute2, ZoneId zone2, boolean expected) { + ZonedDateTime a = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, hour1, minute1), zone1); + ZonedDateTime b = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, hour2, minute2), zone2); + assertEquals(a.isAfter(b), expected); + assertEquals(b.isAfter(a), false); + assertEquals(a.isAfter(a), false); + assertEquals(b.isAfter(b), false); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isAfter_null() { + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 23, 30, 59, 0); + ZonedDateTime a = ZonedDateTime.of(ldt, ZONE_0100); + a.isAfter(null); + } + + //----------------------------------------------------------------------- + // equals() / hashCode() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_true(int y, int o, int d, int h, int m, int s, int n, ZoneId ignored) { + ZonedDateTime a = ZonedDateTime.of(dateTime(y, o, d, h, m, s, n), ZONE_0100); + ZonedDateTime b = ZonedDateTime.of(dateTime(y, o, d, h, m, s, n), ZONE_0100); + assertEquals(a.equals(b), true); + assertEquals(a.hashCode() == b.hashCode(), true); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_year_differs(int y, int o, int d, int h, int m, int s, int n, ZoneId ignored) { + ZonedDateTime a = ZonedDateTime.of(dateTime(y, o, d, h, m, s, n), ZONE_0100); + ZonedDateTime b = ZonedDateTime.of(dateTime(y + 1, o, d, h, m, s, n), ZONE_0100); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_hour_differs(int y, int o, int d, int h, int m, int s, int n, ZoneId ignored) { + h = (h == 23 ? 22 : h); + ZonedDateTime a = ZonedDateTime.of(dateTime(y, o, d, h, m, s, n), ZONE_0100); + ZonedDateTime b = ZonedDateTime.of(dateTime(y, o, d, h + 1, m, s, n), ZONE_0100); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_minute_differs(int y, int o, int d, int h, int m, int s, int n, ZoneId ignored) { + m = (m == 59 ? 58 : m); + ZonedDateTime a = ZonedDateTime.of(dateTime(y, o, d, h, m, s, n), ZONE_0100); + ZonedDateTime b = ZonedDateTime.of(dateTime(y, o, d, h, m + 1, s, n), ZONE_0100); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_second_differs(int y, int o, int d, int h, int m, int s, int n, ZoneId ignored) { + s = (s == 59 ? 58 : s); + ZonedDateTime a = ZonedDateTime.of(dateTime(y, o, d, h, m, s, n), ZONE_0100); + ZonedDateTime b = ZonedDateTime.of(dateTime(y, o, d, h, m, s + 1, n), ZONE_0100); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_nano_differs(int y, int o, int d, int h, int m, int s, int n, ZoneId ignored) { + n = (n == 999999999 ? 999999998 : n); + ZonedDateTime a = ZonedDateTime.of(dateTime(y, o, d, h, m, s, n), ZONE_0100); + ZonedDateTime b = ZonedDateTime.of(dateTime(y, o, d, h, m, s, n + 1), ZONE_0100); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_offset_differs(int y, int o, int d, int h, int m, int s, int n, ZoneId ignored) { + ZonedDateTime a = ZonedDateTime.of(dateTime(y, o, d, h, m, s, n), ZONE_0100); + ZonedDateTime b = ZonedDateTime.of(dateTime(y, o, d, h, m, s, n), ZONE_0200); + assertEquals(a.equals(b), false); + } + + @Test(groups={"tck"}) + public void test_equals_itself_true() { + assertEquals(TEST_DATE_TIME.equals(TEST_DATE_TIME), true); + } + + @Test(groups={"tck"}) + public void test_equals_string_false() { + assertEquals(TEST_DATE_TIME.equals("2007-07-15"), false); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="sampleToString") + Object[][] provider_sampleToString() { + return new Object[][] { + {2008, 6, 30, 11, 30, 59, 0, "Z", "2008-06-30T11:30:59Z"}, + {2008, 6, 30, 11, 30, 59, 0, "+01:00", "2008-06-30T11:30:59+01:00"}, + {2008, 6, 30, 11, 30, 59, 999000000, "Z", "2008-06-30T11:30:59.999Z"}, + {2008, 6, 30, 11, 30, 59, 999000000, "+01:00", "2008-06-30T11:30:59.999+01:00"}, + {2008, 6, 30, 11, 30, 59, 999000, "Z", "2008-06-30T11:30:59.000999Z"}, + {2008, 6, 30, 11, 30, 59, 999000, "+01:00", "2008-06-30T11:30:59.000999+01:00"}, + {2008, 6, 30, 11, 30, 59, 999, "Z", "2008-06-30T11:30:59.000000999Z"}, + {2008, 6, 30, 11, 30, 59, 999, "+01:00", "2008-06-30T11:30:59.000000999+01:00"}, + + {2008, 6, 30, 11, 30, 59, 999, "Europe/London", "2008-06-30T11:30:59.000000999+01:00[Europe/London]"}, + {2008, 6, 30, 11, 30, 59, 999, "Europe/Paris", "2008-06-30T11:30:59.000000999+02:00[Europe/Paris]"}, + }; + } + + @Test(dataProvider="sampleToString", groups={"tck"}) + public void test_toString(int y, int o, int d, int h, int m, int s, int n, String zoneId, String expected) { + ZonedDateTime t = ZonedDateTime.of(dateTime(y, o, d, h, m, s, n), ZoneId.of(zoneId)); + String str = t.toString(); + assertEquals(str, expected); + } + + //----------------------------------------------------------------------- + // toString(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d H m s"); + String t = ZonedDateTime.of(dateTime(2010, 12, 3, 11, 30), ZONE_PARIS).toString(f); + assertEquals(t, "2010 12 3 11 30 0"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_toString_formatter_null() { + ZonedDateTime.of(dateTime(2010, 12, 3, 11, 30), ZONE_PARIS).toString(null); + } + + //------------------------------------------------------------------------- + private static LocalDateTime dateTime( + int year, int month, int dayOfMonth, + int hour, int minute) { + return LocalDateTime.of(year, month, dayOfMonth, hour, minute); + } + + private static LocalDateTime dateTime( + int year, int month, int dayOfMonth, + int hour, int minute, int second, int nanoOfSecond) { + return LocalDateTime.of(year, month, dayOfMonth, hour, minute, second, nanoOfSecond); + } + + private static ZonedDateTime dateTime( + int year, int month, int dayOfMonth, + int hour, int minute, int second, int nanoOfSecond, ZoneOffset offset, ZoneId zoneId) { + return ZonedDateTime.ofStrict(LocalDateTime.of(year, month, dayOfMonth, hour, minute, second, nanoOfSecond), offset, zoneId); + } + +} diff --git a/test/java/time/tck/java/time/calendar/CopticChrono.java b/test/java/time/tck/java/time/calendar/CopticChrono.java new file mode 100644 index 0000000000000000000000000000000000000000..c3dc941e491a2076d07dca1d7ffd6ad94abfb9a6 --- /dev/null +++ b/test/java/time/tck/java/time/calendar/CopticChrono.java @@ -0,0 +1,251 @@ +/* + * 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 tck.java.time.calendar; + +import static java.time.temporal.ChronoField.EPOCH_DAY; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +import java.time.DateTimeException; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.ValueRange; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.Era; + +/** + * The Coptic calendar system. + *

    + * This chronology defines the rules of the Coptic calendar system. + * This calendar system is primarily used in Christian Egypt. + * Dates are aligned such that {@code 0001AM-01-01 (Coptic)} is {@code 0284-08-29 (ISO)}. + *

    + * The fields are defined as follows: + *

      + *
    • era - There are two eras, the current 'Era of the Martyrs' (AM) and the previous era (ERA_ERA_BEFORE_AM). + *
    • year-of-era - The year-of-era for the current era increases uniformly from the epoch at year one. + * For the previous era the year increases from one as time goes backwards. + *
    • proleptic-year - The proleptic year is the same as the year-of-era for the + * current era. For the previous era, years have zero, then negative values. + *
    • month-of-year - There are 13 months in a Coptic year, numbered from 1 to 13. + *
    • day-of-month - There are 30 days in each of the first 12 Coptic months, numbered 1 to 30. + * The 13th month has 5 days, or 6 in a leap year, numbered 1 to 5 or 1 to 6. + *
    • day-of-year - There are 365 days in a standard Coptic year and 366 in a leap year. + * The days are numbered from 1 to 365 or 1 to 366. + *
    • leap-year - Leap years occur every 4 years. + *

    + * + *

    Implementation notes

    + * This class is immutable and thread-safe. + */ +public final class CopticChrono extends Chrono implements Serializable { + + /** + * Singleton instance of the Coptic chronology. + */ + public static final CopticChrono INSTANCE = new CopticChrono(); + /** + * The singleton instance for the era BEFORE_AM. + * This has the numeric value of {@code 0}. + */ + public static final Era ERA_BEFORE_AM = CopticEra.BEFORE_AM; + /** + * The singleton instance for the era AM - 'Era of the Martyrs'. + * This has the numeric value of {@code 1}. + */ + public static final Era ERA_AM = CopticEra.AM; + + /** + * Serialization version. + */ + private static final long serialVersionUID = 7291205177830286973L; + /** + * Range of months. + */ + static final ValueRange MOY_RANGE = ValueRange.of(1, 13); + /** + * Range of days. + */ + static final ValueRange DOM_RANGE = ValueRange.of(1, 5, 30); + /** + * Range of days. + */ + static final ValueRange DOM_RANGE_NONLEAP = ValueRange.of(1, 5); + /** + * Range of days. + */ + static final ValueRange DOM_RANGE_LEAP = ValueRange.of(1, 6); + + /** + * Public Constructor to be instantiated by the ServiceLoader + */ + public CopticChrono() { + } + + /** + * Resolve singleton. + * + * @return the singleton instance, not null + */ + private Object readResolve() { + return INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Gets the ID of the chronology - 'Coptic'. + *

    + * The ID uniquely identifies the {@code Chrono}. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * + * @return the chronology ID - 'Coptic' + * @see #getCalendarType() + */ + @Override + public String getId() { + return "Coptic"; + } + + /** + * Gets the calendar type of the underlying calendar system - 'coptic'. + *

    + * The calendar type is an identifier defined by the + * Unicode Locale Data Markup Language (LDML) specification. + * It can be used to lookup the {@code Chrono} using {@link #of(String)}. + * It can also be used as part of a locale, accessible via + * {@link Locale#getUnicodeLocaleType(String)} with the key 'ca'. + * + * @return the calendar system type - 'coptic' + * @see #getId() + */ + @Override + public String getCalendarType() { + return "coptic"; + } + + //----------------------------------------------------------------------- + @Override + public ChronoLocalDate date(int prolepticYear, int month, int dayOfMonth) { + return new CopticDate(prolepticYear, month, dayOfMonth); + } + + @Override + public ChronoLocalDate dateYearDay(int prolepticYear, int dayOfYear) { + return new CopticDate(prolepticYear, (dayOfYear - 1) / 30 + 1, (dayOfYear - 1) % 30 + 1); + } + + @Override + public ChronoLocalDate date(TemporalAccessor dateTime) { + if (dateTime instanceof CopticDate) { + return (CopticDate) dateTime; + } + return CopticDate.ofEpochDay(dateTime.getLong(EPOCH_DAY)); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified year is a leap year. + *

    + * A Coptic proleptic-year is leap if the remainder after division by four equals three. + * This method does not validate the year passed in, and only has a + * well-defined result for years in the supported range. + * + * @param prolepticYear the proleptic-year to check, not validated for range + * @return true if the year is a leap year + */ + @Override + public boolean isLeapYear(long prolepticYear) { + return Math.floorMod(prolepticYear, 4) == 3; + } + + @Override + public int prolepticYear(Era era, int yearOfEra) { + if (era instanceof CopticEra == false) { + throw new DateTimeException("Era must be CopticEra"); + } + return (era == CopticEra.AM ? yearOfEra : 1 - yearOfEra); + } + + @Override + public Era eraOf(int eraValue) { + return CopticEra.of(eraValue); + } + + @Override + public List> eras() { + return Arrays.>asList(CopticEra.values()); + } + + //----------------------------------------------------------------------- + @Override + public ValueRange range(ChronoField field) { + switch (field) { + case DAY_OF_MONTH: return ValueRange.of(1, 5, 30); + case ALIGNED_WEEK_OF_MONTH: return ValueRange.of(1, 1, 5); + case MONTH_OF_YEAR: return ValueRange.of(1, 13); + case EPOCH_MONTH: return ValueRange.of(-1000, 1000); // TODO + case YEAR_OF_ERA: return ValueRange.of(1, 999, 1000); // TODO + case YEAR: return ValueRange.of(-1000, 1000); // TODO + } + return field.range(); + } + +} diff --git a/test/java/time/tck/java/time/calendar/CopticDate.java b/test/java/time/tck/java/time/calendar/CopticDate.java new file mode 100644 index 0000000000000000000000000000000000000000..30f1664dd0eb8e551aee3a0044dd5749e30e7d4d --- /dev/null +++ b/test/java/time/tck/java/time/calendar/CopticDate.java @@ -0,0 +1,340 @@ +/* + * 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 tck.java.time.calendar; + +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; + +import java.io.Serializable; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Era; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalUnit; +import java.time.temporal.ValueRange; +import java.time.temporal.Year; + +/** + * A date in the Coptic calendar system. + *

    + * This implements {@code ChronoLocalDate} for the {@link CopticChrono Coptic calendar}. + * + *

    Implementation notes

    + * This class is immutable and thread-safe. + */ +final class CopticDate + implements ChronoLocalDate, Serializable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = -7920528871688876868L; + /** + * The difference between the Coptic and Coptic epoch day count. + */ + private static final int EPOCH_DAY_DIFFERENCE = 574971 + 40587; + + /** + * The proleptic year. + */ + private final int prolepticYear; + /** + * The month. + */ + private final short month; + /** + * The day. + */ + private final short day; + + //----------------------------------------------------------------------- + /** + * Creates an instance. + * + * @param epochDay the epoch day to convert based on 1970-01-01 (ISO) + * @return the Coptic date, not null + * @throws DateTimeException if the date is invalid + */ + static CopticDate ofEpochDay(long epochDay) { + epochDay += EPOCH_DAY_DIFFERENCE; + int prolepticYear = (int) (((epochDay * 4) + 1463) / 1461); + int startYearEpochDay = (prolepticYear - 1) * 365 + (prolepticYear / 4); + int doy0 = (int) (epochDay - startYearEpochDay); + int month = doy0 / 30 + 1; + int dom = doy0 % 30 + 1; + return new CopticDate(prolepticYear, month, dom); + } + + private static CopticDate resolvePreviousValid(int prolepticYear, int month, int day) { + if (month == 13 && day > 5) { + day = CopticChrono.INSTANCE.isLeapYear(prolepticYear) ? 6 : 5; + } + return new CopticDate(prolepticYear, month, day); + } + + //----------------------------------------------------------------------- + /** + * Creates an instance. + * + * @param prolepticYear the Coptic proleptic-year + * @param month the Coptic month, from 1 to 13 + * @param dayOfMonth the Coptic day-of-month, from 1 to 30 + * @throws DateTimeException if the date is invalid + */ + CopticDate(int prolepticYear, int month, int dayOfMonth) { + CopticChrono.MOY_RANGE.checkValidValue(month, MONTH_OF_YEAR); + ValueRange range; + if (month == 13) { + range = CopticChrono.INSTANCE.isLeapYear(prolepticYear) ? CopticChrono.DOM_RANGE_LEAP : CopticChrono.DOM_RANGE_NONLEAP; + } else { + range = CopticChrono.DOM_RANGE; + } + range.checkValidValue(dayOfMonth, DAY_OF_MONTH); + + this.prolepticYear = prolepticYear; + this.month = (short) month; + this.day = (short) dayOfMonth; + } + + /** + * Validates the object. + * + * @return the resolved date, not null + */ + private Object readResolve() { + // TODO: validate + return this; + } + + //----------------------------------------------------------------------- + @Override + public CopticChrono getChrono() { + return CopticChrono.INSTANCE; + } + + //----------------------------------------------------------------------- + @Override + public int lengthOfMonth() { + switch (month) { + case 13: + return (isLeapYear() ? 6 : 5); + default: + return 30; + } + } + + @Override + public ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + if (isSupported(field)) { + ChronoField f = (ChronoField) field; + switch (f) { + case DAY_OF_MONTH: return ValueRange.of(1, lengthOfMonth()); + case DAY_OF_YEAR: return ValueRange.of(1, lengthOfYear()); + case ALIGNED_WEEK_OF_MONTH: return ValueRange.of(1, month == 13 ? 1 : 5); + case YEAR: + case YEAR_OF_ERA: return (prolepticYear <= 0 ? + ValueRange.of(1, Year.MAX_VALUE + 1) : ValueRange.of(1, Year.MAX_VALUE)); // TODO + } + return getChrono().range(f); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doRange(this); + } + + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case DAY_OF_WEEK: return Math.floorMod(toEpochDay() + 3, 7) + 1; + case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((day - 1) % 7) + 1; + case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((get(ChronoField.DAY_OF_YEAR) - 1) % 7) + 1; + case DAY_OF_MONTH: return day; + case DAY_OF_YEAR: return (month - 1) * 30 + day; + case EPOCH_DAY: return toEpochDay(); + case ALIGNED_WEEK_OF_MONTH: return ((day - 1) / 7) + 1; + case ALIGNED_WEEK_OF_YEAR: return ((get(ChronoField.DAY_OF_YEAR) - 1) / 7) + 1; + case MONTH_OF_YEAR: return month; + case YEAR_OF_ERA: return (prolepticYear >= 1 ? prolepticYear : 1 - prolepticYear); + case YEAR: return prolepticYear; + case ERA: return (prolepticYear >= 1 ? 1 : 0); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doGet(this); + } + + @Override + public CopticDate with(TemporalField field, long newValue) { + if (field instanceof ChronoField) { + ChronoField f = (ChronoField) field; + f.checkValidValue(newValue); // TODO: validate value + int nvalue = (int) newValue; + switch (f) { + case DAY_OF_WEEK: return plusDays(newValue - get(ChronoField.DAY_OF_WEEK)); + case ALIGNED_DAY_OF_WEEK_IN_MONTH: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_MONTH)); + case ALIGNED_DAY_OF_WEEK_IN_YEAR: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_YEAR)); + case DAY_OF_MONTH: return resolvePreviousValid(prolepticYear, month, nvalue); + case DAY_OF_YEAR: return resolvePreviousValid(prolepticYear, ((nvalue - 1) / 30) + 1, ((nvalue - 1) % 30) + 1); + case EPOCH_DAY: return ofEpochDay(nvalue); + case ALIGNED_WEEK_OF_MONTH: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_MONTH)) * 7); + case ALIGNED_WEEK_OF_YEAR: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_YEAR)) * 7); + case MONTH_OF_YEAR: return resolvePreviousValid(prolepticYear, nvalue, day); + case YEAR_OF_ERA: return resolvePreviousValid(prolepticYear >= 1 ? nvalue : 1 - nvalue, month, day); + case YEAR: return resolvePreviousValid(nvalue, month, day); + case ERA: return resolvePreviousValid(1 - prolepticYear, month, day); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doWith(this, newValue); + } + + //----------------------------------------------------------------------- + @Override + public CopticDate plus(long amountToAdd, TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + ChronoUnit f = (ChronoUnit) unit; + switch (f) { + case DAYS: return plusDays(amountToAdd); + case WEEKS: return plusDays(Math.multiplyExact(amountToAdd, 7)); + case MONTHS: return plusMonths(amountToAdd); + case YEARS: return plusYears(amountToAdd); + case DECADES: return plusYears(Math.multiplyExact(amountToAdd, 10)); + case CENTURIES: return plusYears(Math.multiplyExact(amountToAdd, 100)); + case MILLENNIA: return plusYears(Math.multiplyExact(amountToAdd, 1000)); + } + throw new DateTimeException(unit.getName() + " not valid for CopticDate"); + } + return unit.doPlus(this, amountToAdd); + } + + //----------------------------------------------------------------------- + private CopticDate plusYears(long years) { + return plusMonths(Math.multiplyExact(years, 13)); + } + + private CopticDate plusMonths(long months) { + if (months == 0) { + return this; + } + long curEm = prolepticYear * 13L + (month - 1); + long calcEm = Math.addExact(curEm, months); + int newYear = Math.toIntExact(Math.floorDiv(calcEm, 13)); + int newMonth = (int)Math.floorMod(calcEm, 13) + 1; + return resolvePreviousValid(newYear, newMonth, day); + } + + private CopticDate plusDays(long days) { + if (days == 0) { + return this; + } + return CopticDate.ofEpochDay(Math.addExact(toEpochDay(), days)); + } + + @Override + public long periodUntil(Temporal endDateTime, TemporalUnit unit) { + if (endDateTime instanceof ChronoLocalDate == false) { + throw new DateTimeException("Unable to calculate period between objects of two different types"); + } + ChronoLocalDate end = (ChronoLocalDate) endDateTime; + if (getChrono().equals(end.getChrono()) == false) { + throw new DateTimeException("Unable to calculate period between two different chronologies"); + } + if (unit instanceof ChronoUnit) { + return LocalDate.from(this).periodUntil(end, unit); // TODO: this is wrong + } + return unit.between(this, endDateTime).getAmount(); + } + + //----------------------------------------------------------------------- + @Override + public long toEpochDay() { + long year = (long) prolepticYear; + long copticEpochDay = ((year - 1) * 365) + Math.floorDiv(year, 4) + (get(ChronoField.DAY_OF_YEAR) - 1); + return copticEpochDay - EPOCH_DAY_DIFFERENCE; + } + + @Override + public String toString() { + // getLong() reduces chances of exceptions in toString() + long yoe = getLong(YEAR_OF_ERA); + long moy = getLong(MONTH_OF_YEAR); + long dom = getLong(DAY_OF_MONTH); + StringBuilder buf = new StringBuilder(30); + buf.append(getChrono().toString()) + .append(" ") + .append(getEra()) + .append(" ") + .append(yoe) + .append(moy < 10 ? "-0" : "-").append(moy) + .append(dom < 10 ? "-0" : "-").append(dom); + return buf.toString(); + } +} diff --git a/test/java/time/tck/java/time/calendar/CopticEra.java b/test/java/time/tck/java/time/calendar/CopticEra.java new file mode 100644 index 0000000000000000000000000000000000000000..2526530ed3bc20d17704367b4f76ddfee0239d54 --- /dev/null +++ b/test/java/time/tck/java/time/calendar/CopticEra.java @@ -0,0 +1,210 @@ +/* + * 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 tck.java.time.calendar; + +import static java.time.temporal.ChronoField.ERA; + +import java.util.Locale; + +import java.time.DateTimeException; +import java.time.temporal.ChronoField; +import java.time.temporal.Queries; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.ValueRange; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.Era; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.TextStyle; + +/** + * An era in the Coptic calendar system. + *

    + * The Coptic calendar system uses the 'Era of the Martyrs'. + * The start of the Coptic epoch {@code 0001-01-01 (Coptic)} is {@code 0284-08-29 (ISO)}. + *

    + * Do not use {@code ordinal()} to obtain the numeric representation of {@code CopticEra}. + * Use {@code getValue()} instead. + * + *

    Implementation notes

    + * This is an immutable and thread-safe enum. + */ +enum CopticEra implements Era { + + /** + * The singleton instance for the era BEFORE_AM, 'Before Era of the Martyrs'. + * This has the numeric value of {@code 0}. + */ + BEFORE_AM, + /** + * The singleton instance for the era AM, 'Era of the Martyrs'. + * This has the numeric value of {@code 1}. + */ + AM; + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code CopticEra} from an {@code int} value. + *

    + * {@code CopticEra} is an enum representing the Coptic eras of BEFORE_AM/AM. + * This factory allows the enum to be obtained from the {@code int} value. + * + * @param era the BEFORE_AM/AM value to represent, from 0 (BEFORE_AM) to 1 (AM) + * @return the era singleton, not null + * @throws DateTimeException if the value is invalid + */ + public static CopticEra of(int era) { + switch (era) { + case 0: + return BEFORE_AM; + case 1: + return AM; + default: + throw new DateTimeException("Invalid era: " + era); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the numeric era {@code int} value. + *

    + * The era BEFORE_AM has the value 0, while the era AM has the value 1. + * + * @return the era value, from 0 (BEFORE_AM) to 1 (AM) + */ + public int getValue() { + return ordinal(); + } + + @Override + public CopticChrono getChrono() { + return CopticChrono.INSTANCE; + } + + // JDK8 default methods: + //----------------------------------------------------------------------- + @Override + public ChronoLocalDate date(int year, int month, int day) { + return getChrono().date(this, year, month, day); + } + + @Override + public ChronoLocalDate dateYearDay(int year, int dayOfYear) { + return getChrono().dateYearDay(this, year, dayOfYear); + } + + //----------------------------------------------------------------------- + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == ERA; + } + return field != null && field.doIsSupported(this); + } + + @Override + public ValueRange range(TemporalField field) { + if (field == ERA) { + return field.range(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doRange(this); + } + + @Override + public int get(TemporalField field) { + if (field == ERA) { + return getValue(); + } + return range(field).checkValidIntValue(getLong(field), field); + } + + @Override + public long getLong(TemporalField field) { + if (field == ERA) { + return getValue(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doGet(this); + } + + //------------------------------------------------------------------------- + @Override + public Temporal adjustInto(Temporal dateTime) { + return dateTime.with(ERA, getValue()); + } + + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.zoneId()) { + return null; + } else if (query == Queries.chrono()) { + return (R) getChrono(); + } + return query.queryFrom(this); + } + + //----------------------------------------------------------------------- + @Override + public String getText(TextStyle style, Locale locale) { + return new DateTimeFormatterBuilder().appendText(ERA, style).toFormatter(locale).print(this); + } + +} diff --git a/test/java/time/tck/java/time/calendar/TestChronoLocalDate.java b/test/java/time/tck/java/time/calendar/TestChronoLocalDate.java new file mode 100644 index 0000000000000000000000000000000000000000..67175c0b06cd6c80dc7958dd085deb4efd0e9a41 --- /dev/null +++ b/test/java/time/tck/java/time/calendar/TestChronoLocalDate.java @@ -0,0 +1,461 @@ +/* + * 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) 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 tck.java.time.calendar; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.List; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.calendar.HijrahChrono; +import java.time.calendar.JapaneseChrono; +import java.time.calendar.MinguoChrono; +import java.time.calendar.ThaiBuddhistChrono; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.ChronoUnit; +import java.time.temporal.SimplePeriod; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.format.DateTimeBuilder; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.ValueRange; +import java.time.temporal.TemporalUnit; +import java.time.temporal.ISOChrono; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test assertions that must be true for all built-in chronologies. + */ +@Test +public class TestChronoLocalDate { + + //----------------------------------------------------------------------- + // regular data factory for names and descriptions of available calendars + //----------------------------------------------------------------------- + @DataProvider(name = "calendars") + Chrono[][] data_of_calendars() { + return new Chrono[][]{ + {HijrahChrono.INSTANCE}, + {ISOChrono.INSTANCE}, + {JapaneseChrono.INSTANCE}, + {MinguoChrono.INSTANCE}, + {ThaiBuddhistChrono.INSTANCE}}; + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badWithAdjusterChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDate date = chrono.date(refDate); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDate date2 = chrono2.date(refDate); + TemporalAdjuster adjuster = new FixedAdjuster(date2); + if (chrono != chrono2) { + try { + date.with(adjuster); + Assert.fail("WithAdjuster should have thrown a ClassCastException"); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDate result = date.with(adjuster); + assertEquals(result, date2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badPlusAdjusterChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDate date = chrono.date(refDate); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDate date2 = chrono2.date(refDate); + TemporalAdder adjuster = new FixedAdjuster(date2); + if (chrono != chrono2) { + try { + date.plus(adjuster); + Assert.fail("WithAdjuster should have thrown a ClassCastException"); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDate result = date.plus(adjuster); + assertEquals(result, date2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badMinusAdjusterChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDate date = chrono.date(refDate); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDate date2 = chrono2.date(refDate); + TemporalSubtractor adjuster = new FixedAdjuster(date2); + if (chrono != chrono2) { + try { + date.minus(adjuster); + Assert.fail("WithAdjuster should have thrown a ClassCastException"); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDate result = date.minus(adjuster); + assertEquals(result, date2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badPlusTemporalUnitChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDate date = chrono.date(refDate); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDate date2 = chrono2.date(refDate); + TemporalUnit adjuster = new FixedTemporalUnit(date2); + if (chrono != chrono2) { + try { + date.plus(1, adjuster); + Assert.fail("TemporalUnit.doAdd plus should have thrown a ClassCastException" + date.getClass() + + ", can not be cast to " + date2.getClass()); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDate result = date.plus(1, adjuster); + assertEquals(result, date2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badMinusTemporalUnitChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDate date = chrono.date(refDate); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDate date2 = chrono2.date(refDate); + TemporalUnit adjuster = new FixedTemporalUnit(date2); + if (chrono != chrono2) { + try { + date.minus(1, adjuster); + Assert.fail("TemporalUnit.doAdd minus should have thrown a ClassCastException" + date.getClass() + + ", can not be cast to " + date2.getClass()); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDate result = date.minus(1, adjuster); + assertEquals(result, date2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badTemporalFieldChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDate date = chrono.date(refDate); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDate date2 = chrono2.date(refDate); + TemporalField adjuster = new FixedTemporalField(date2); + if (chrono != chrono2) { + try { + date.with(adjuster, 1); + Assert.fail("TemporalField doSet should have thrown a ClassCastException" + date.getClass() + + ", can not be cast to " + date2.getClass()); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDate result = date.with(adjuster, 1); + assertEquals(result, date2, "TemporalField doSet failed to replace date"); + } + } + } + + //----------------------------------------------------------------------- + // isBefore, isAfter, isEqual, DATE_COMPARATOR + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="calendars") + public void test_date_comparisons(Chrono chrono) { + List> dates = new ArrayList<>(); + + ChronoLocalDate date = chrono.date(LocalDate.of(1900, 1, 1)); + + // Insert dates in order, no duplicates + dates.add(date.minus(1000, ChronoUnit.YEARS)); + dates.add(date.minus(100, ChronoUnit.YEARS)); + dates.add(date.minus(10, ChronoUnit.YEARS)); + dates.add(date.minus(1, ChronoUnit.YEARS)); + dates.add(date.minus(1, ChronoUnit.MONTHS)); + dates.add(date.minus(1, ChronoUnit.WEEKS)); + dates.add(date.minus(1, ChronoUnit.DAYS)); + dates.add(date); + dates.add(date.plus(1, ChronoUnit.DAYS)); + dates.add(date.plus(1, ChronoUnit.WEEKS)); + dates.add(date.plus(1, ChronoUnit.MONTHS)); + dates.add(date.plus(1, ChronoUnit.YEARS)); + dates.add(date.plus(10, ChronoUnit.YEARS)); + dates.add(date.plus(100, ChronoUnit.YEARS)); + dates.add(date.plus(1000, ChronoUnit.YEARS)); + + // Check these dates against the corresponding dates for every calendar + for (Chrono[] clist : data_of_calendars()) { + List> otherDates = new ArrayList<>(); + Chrono chrono2 = clist[0]; + for (ChronoLocalDate d : dates) { + otherDates.add(chrono2.date(d)); + } + + // Now compare the sequence of original dates with the sequence of converted dates + for (int i = 0; i < dates.size(); i++) { + ChronoLocalDate a = dates.get(i); + for (int j = 0; j < otherDates.size(); j++) { + ChronoLocalDate b = otherDates.get(j); + int cmp = ChronoLocalDate.DATE_COMPARATOR.compare(a, b); + if (i < j) { + assertTrue(cmp < 0, a + " compare " + b); + assertEquals(a.isBefore(b), true, a + " isBefore " + b); + assertEquals(a.isAfter(b), false, a + " isAfter " + b); + assertEquals(a.isEqual(b), false, a + " isEqual " + b); + } else if (i > j) { + assertTrue(cmp > 0, a + " compare " + b); + assertEquals(a.isBefore(b), false, a + " isBefore " + b); + assertEquals(a.isAfter(b), true, a + " isAfter " + b); + assertEquals(a.isEqual(b), false, a + " isEqual " + b); + } else { + assertTrue(cmp == 0, a + " compare " + b); + assertEquals(a.isBefore(b), false, a + " isBefore " + b); + assertEquals(a.isAfter(b), false, a + " isAfter " + b); + assertEquals(a.isEqual(b), true, a + " isEqual " + b); + } + } + } + } + } + + //----------------------------------------------------------------------- + // Test Serialization of Calendars + //----------------------------------------------------------------------- + @Test( groups={"tck"}, dataProvider="calendars") + public > void test_ChronoSerialization(C chrono) throws Exception { + LocalDate ref = LocalDate.of(1900, 1, 5); + ChronoLocalDate orginal = chrono.date(ref); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(orginal); + out.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + @SuppressWarnings("unchecked") + ChronoLocalDate ser = (ChronoLocalDate) in.readObject(); + assertEquals(ser, orginal, "deserialized date is wrong"); + } + + /** + * FixedAdjusted returns a fixed Temporal in all adjustments. + * Construct an adjuster with the Temporal that should be returned from adjust. + */ + static class FixedAdjuster implements TemporalAdjuster, TemporalAdder, TemporalSubtractor { + private Temporal datetime; + + FixedAdjuster(Temporal datetime) { + this.datetime = datetime; + } + + @Override + public Temporal adjustInto(Temporal ignore) { + return datetime; + } + + @Override + public Temporal addTo(Temporal ignore) { + return datetime; + } + + @Override + public Temporal subtractFrom(Temporal ignore) { + return datetime; + } + + } + + /** + * FixedTemporalUnit returns a fixed Temporal in all adjustments. + * Construct an FixedTemporalUnit with the Temporal that should be returned from doAdd. + */ + static class FixedTemporalUnit implements TemporalUnit { + private Temporal temporal; + + FixedTemporalUnit(Temporal temporal) { + this.temporal = temporal; + } + + @Override + public String getName() { + return "FixedTemporalUnit"; + } + + @Override + public Duration getDuration() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isDurationEstimated() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isSupported(Temporal temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @SuppressWarnings("unchecked") + @Override + public R doPlus(R dateTime, long periodToAdd) { + return (R) this.temporal; + } + + @Override + public SimplePeriod between(R dateTime1, R dateTime2) { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + /** + * FixedTemporalField returns a fixed Temporal in all adjustments. + * Construct an FixedTemporalField with the Temporal that should be returned from doSet. + */ + static class FixedTemporalField implements TemporalField { + private Temporal temporal; + FixedTemporalField(Temporal temporal) { + this.temporal = temporal; + } + + @Override + public String getName() { + return "FixedTemporalField"; + } + + @Override + public TemporalUnit getBaseUnit() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public TemporalUnit getRangeUnit() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ValueRange range() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean doIsSupported(TemporalAccessor temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ValueRange doRange(TemporalAccessor temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public long doGet(TemporalAccessor temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @SuppressWarnings("unchecked") + @Override + public R doWith(R temporal, long newValue) { + return (R) this.temporal; + } + + @Override + public boolean resolve(DateTimeBuilder builder, long value) { + throw new UnsupportedOperationException("Not supported yet."); + } + + } +} diff --git a/test/java/time/tck/java/time/calendar/TestChronoLocalDateTime.java b/test/java/time/tck/java/time/calendar/TestChronoLocalDateTime.java new file mode 100644 index 0000000000000000000000000000000000000000..e514fb82e0257ed4dbec737133edf3532eff1aaf --- /dev/null +++ b/test/java/time/tck/java/time/calendar/TestChronoLocalDateTime.java @@ -0,0 +1,469 @@ +/* + * 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) 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 tck.java.time.calendar; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.List; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.calendar.HijrahChrono; +import java.time.calendar.JapaneseChrono; +import java.time.calendar.MinguoChrono; +import java.time.calendar.ThaiBuddhistChrono; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoLocalDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.SimplePeriod; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.format.DateTimeBuilder; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.ValueRange; +import java.time.temporal.ISOChrono; +import java.time.temporal.TemporalUnit; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test assertions that must be true for all built-in chronologies. + */ +@Test +public class TestChronoLocalDateTime { + //----------------------------------------------------------------------- + // regular data factory for names and descriptions of available calendars + //----------------------------------------------------------------------- + @DataProvider(name = "calendars") + Chrono[][] data_of_calendars() { + return new Chrono[][]{ + {HijrahChrono.INSTANCE}, + {ISOChrono.INSTANCE}, + {JapaneseChrono.INSTANCE}, + {MinguoChrono.INSTANCE}, + {ThaiBuddhistChrono.INSTANCE}}; + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badWithAdjusterChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDateTime cdt = chrono.date(refDate).atTime(LocalTime.NOON); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDateTime cdt2 = chrono2.date(refDate).atTime(LocalTime.NOON); + TemporalAdjuster adjuster = new FixedAdjuster(cdt2); + if (chrono != chrono2) { + try { + ChronoLocalDateTime notreached = cdt.with(adjuster); + Assert.fail("WithAdjuster should have thrown a ClassCastException, " + + "required: " + cdt + ", supplied: " + cdt2); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDateTime result = cdt.with(adjuster); + assertEquals(result, cdt2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badPlusAdjusterChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDateTime cdt = chrono.date(refDate).atTime(LocalTime.NOON); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDateTime cdt2 = chrono2.date(refDate).atTime(LocalTime.NOON); + TemporalAdder adjuster = new FixedAdjuster(cdt2); + if (chrono != chrono2) { + try { + ChronoLocalDateTime notreached = cdt.plus(adjuster); + Assert.fail("WithAdjuster should have thrown a ClassCastException, " + + "required: " + cdt + ", supplied: " + cdt2); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDateTime result = cdt.plus(adjuster); + assertEquals(result, cdt2, "WithAdjuster failed to replace date time"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badMinusAdjusterChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDateTime cdt = chrono.date(refDate).atTime(LocalTime.NOON); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDateTime cdt2 = chrono2.date(refDate).atTime(LocalTime.NOON); + TemporalSubtractor adjuster = new FixedAdjuster(cdt2); + if (chrono != chrono2) { + try { + ChronoLocalDateTime notreached = cdt.minus(adjuster); + Assert.fail("WithAdjuster should have thrown a ClassCastException, " + + "required: " + cdt + ", supplied: " + cdt2); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDateTime result = cdt.minus(adjuster); + assertEquals(result, cdt2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badPlusTemporalUnitChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDateTime cdt = chrono.date(refDate).atTime(LocalTime.NOON); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDateTime cdt2 = chrono2.date(refDate).atTime(LocalTime.NOON); + TemporalUnit adjuster = new FixedTemporalUnit(cdt2); + if (chrono != chrono2) { + try { + ChronoLocalDateTime notreached = cdt.plus(1, adjuster); + Assert.fail("TemporalUnit.doAdd plus should have thrown a ClassCastException" + cdt + + ", can not be cast to " + cdt2); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDateTime result = cdt.plus(1, adjuster); + assertEquals(result, cdt2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badMinusTemporalUnitChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDateTime cdt = chrono.date(refDate).atTime(LocalTime.NOON); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDateTime cdt2 = chrono2.date(refDate).atTime(LocalTime.NOON); + TemporalUnit adjuster = new FixedTemporalUnit(cdt2); + if (chrono != chrono2) { + try { + ChronoLocalDateTime notreached = cdt.minus(1, adjuster); + Assert.fail("TemporalUnit.doAdd minus should have thrown a ClassCastException" + cdt.getClass() + + ", can not be cast to " + cdt2.getClass()); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDateTime result = cdt.minus(1, adjuster); + assertEquals(result, cdt2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badTemporalFieldChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDateTime cdt = chrono.date(refDate).atTime(LocalTime.NOON); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDateTime cdt2 = chrono2.date(refDate).atTime(LocalTime.NOON); + TemporalField adjuster = new FixedTemporalField(cdt2); + if (chrono != chrono2) { + try { + ChronoLocalDateTime notreached = cdt.with(adjuster, 1); + Assert.fail("TemporalField doSet should have thrown a ClassCastException" + cdt.getClass() + + ", can not be cast to " + cdt2.getClass()); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDateTime result = cdt.with(adjuster, 1); + assertEquals(result, cdt2, "TemporalField doSet failed to replace date"); + } + } + } + + //----------------------------------------------------------------------- + // isBefore, isAfter, isEqual + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="calendars") + public void test_datetime_comparisons(Chrono chrono) { + List> dates = new ArrayList<>(); + + ChronoLocalDateTime date = chrono.date(LocalDate.of(1900, 1, 1)).atTime(LocalTime.MIN); + + // Insert dates in order, no duplicates + dates.add(date.minus(100, ChronoUnit.YEARS)); + dates.add(date.minus(1, ChronoUnit.YEARS)); + dates.add(date.minus(1, ChronoUnit.MONTHS)); + dates.add(date.minus(1, ChronoUnit.WEEKS)); + dates.add(date.minus(1, ChronoUnit.DAYS)); + dates.add(date.minus(1, ChronoUnit.HOURS)); + dates.add(date.minus(1, ChronoUnit.MINUTES)); + dates.add(date.minus(1, ChronoUnit.SECONDS)); + dates.add(date.minus(1, ChronoUnit.NANOS)); + dates.add(date); + dates.add(date.plus(1, ChronoUnit.NANOS)); + dates.add(date.plus(1, ChronoUnit.SECONDS)); + dates.add(date.plus(1, ChronoUnit.MINUTES)); + dates.add(date.plus(1, ChronoUnit.HOURS)); + dates.add(date.plus(1, ChronoUnit.DAYS)); + dates.add(date.plus(1, ChronoUnit.WEEKS)); + dates.add(date.plus(1, ChronoUnit.MONTHS)); + dates.add(date.plus(1, ChronoUnit.YEARS)); + dates.add(date.plus(100, ChronoUnit.YEARS)); + + // Check these dates against the corresponding dates for every calendar + for (Chrono[] clist : data_of_calendars()) { + List> otherDates = new ArrayList<>(); + Chrono chrono2 = clist[0]; + for (ChronoLocalDateTime d : dates) { + otherDates.add(chrono2.date(d).atTime(d.getTime())); + } + + // Now compare the sequence of original dates with the sequence of converted dates + for (int i = 0; i < dates.size(); i++) { + ChronoLocalDateTime a = dates.get(i); + for (int j = 0; j < otherDates.size(); j++) { + ChronoLocalDateTime b = otherDates.get(j); + int cmp = ChronoLocalDateTime.DATE_TIME_COMPARATOR.compare(a, b); + if (i < j) { + assertTrue(cmp < 0, a + " compare " + b); + assertEquals(a.isBefore(b), true, a + " isBefore " + b); + assertEquals(a.isAfter(b), false, a + " isAfter " + b); + assertEquals(a.isEqual(b), false, a + " isEqual " + b); + } else if (i > j) { + assertTrue(cmp > 0, a + " compare " + b); + assertEquals(a.isBefore(b), false, a + " isBefore " + b); + assertEquals(a.isAfter(b), true, a + " isAfter " + b); + assertEquals(a.isEqual(b), false, a + " isEqual " + b); + } else { + assertTrue(cmp == 0, a + " compare " + b); + assertEquals(a.isBefore(b), false, a + " isBefore " + b); + assertEquals(a.isAfter(b), false, a + " isAfter " + b); + assertEquals(a.isEqual(b), true, a + " isEqual " + b); + } + } + } + } + } + + //----------------------------------------------------------------------- + // Test Serialization of ISO via chrono API + //----------------------------------------------------------------------- + @Test( groups={"tck"}, dataProvider="calendars") + public > void test_ChronoLocalDateTimeSerialization(C chrono) throws Exception { + LocalDateTime ref = LocalDate.of(2000, 1, 5).atTime(12, 1, 2, 3); + ChronoLocalDateTime orginal = chrono.date(ref).atTime(ref.getTime()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(orginal); + out.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + ChronoLocalDateTime ser = (ChronoLocalDateTime) in.readObject(); + assertEquals(ser, orginal, "deserialized date is wrong"); + } + + + /** + * FixedAdjusted returns a fixed Temporal in all adjustments. + * Construct an adjuster with the Temporal that should be returned from adjust. + */ + static class FixedAdjuster implements TemporalAdjuster, TemporalAdder, TemporalSubtractor { + private Temporal datetime; + + FixedAdjuster(Temporal datetime) { + this.datetime = datetime; + } + + @Override + public Temporal adjustInto(Temporal ignore) { + return datetime; + } + + @Override + public Temporal addTo(Temporal ignore) { + return datetime; + } + + @Override + public Temporal subtractFrom(Temporal ignore) { + return datetime; + } + + } + + /** + * FixedTemporalUnit returns a fixed Temporal in all adjustments. + * Construct an FixedTemporalUnit with the Temporal that should be returned from doAdd. + */ + static class FixedTemporalUnit implements TemporalUnit { + private Temporal temporal; + + FixedTemporalUnit(Temporal temporal) { + this.temporal = temporal; + } + + @Override + public String getName() { + return "FixedTemporalUnit"; + } + + @Override + public Duration getDuration() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isDurationEstimated() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isSupported(Temporal temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @SuppressWarnings("unchecked") + @Override + public R doPlus(R dateTime, long periodToAdd) { + return (R) this.temporal; + } + + @Override + public SimplePeriod between(R dateTime1, R dateTime2) { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + /** + * FixedTemporalField returns a fixed Temporal in all adjustments. + * Construct an FixedTemporalField with the Temporal that should be returned from doSet. + */ + static class FixedTemporalField implements TemporalField { + private Temporal temporal; + FixedTemporalField(Temporal temporal) { + this.temporal = temporal; + } + + @Override + public String getName() { + return "FixedTemporalField"; + } + + @Override + public TemporalUnit getBaseUnit() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public TemporalUnit getRangeUnit() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ValueRange range() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean doIsSupported(TemporalAccessor temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ValueRange doRange(TemporalAccessor temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public long doGet(TemporalAccessor temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @SuppressWarnings("unchecked") + @Override + public R doWith(R temporal, long newValue) { + return (R) this.temporal; + } + + @Override + public boolean resolve(DateTimeBuilder builder, long value) { + throw new UnsupportedOperationException("Not supported yet."); + } + + } +} diff --git a/test/java/time/tck/java/time/calendar/TestHijrahChrono.java b/test/java/time/tck/java/time/calendar/TestHijrahChrono.java new file mode 100644 index 0000000000000000000000000000000000000000..3157e822f094a09cbed8bfe9e2e286392af03b42 --- /dev/null +++ b/test/java/time/tck/java/time/calendar/TestHijrahChrono.java @@ -0,0 +1,236 @@ +/* + * 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) 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 tck.java.time.calendar; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Month; +import java.time.calendar.HijrahChrono; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.Adjusters; +import java.time.temporal.Chrono; +import java.time.temporal.ISOChrono; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test. + */ +@Test +public class TestHijrahChrono { + + //----------------------------------------------------------------------- + // Chrono.ofName("Hijrah") Lookup by name + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_chrono_byName() { + Chrono c = HijrahChrono.INSTANCE; + Chrono test = Chrono.of("Hijrah"); + Assert.assertNotNull(test, "The Hijrah calendar could not be found byName"); + Assert.assertEquals(test.getId(), "Hijrah", "ID mismatch"); + Assert.assertEquals(test.getCalendarType(), "islamicc", "Type mismatch"); + Assert.assertEquals(test, c); + } + + //----------------------------------------------------------------------- + // creation, toLocalDate() + //----------------------------------------------------------------------- + @DataProvider(name="samples") + Object[][] data_samples() { + return new Object[][] { + {HijrahChrono.INSTANCE.date(1, 1, 1), LocalDate.of(622, 7, 19)}, + {HijrahChrono.INSTANCE.date(1, 1, 2), LocalDate.of(622, 7, 20)}, + {HijrahChrono.INSTANCE.date(1, 1, 3), LocalDate.of(622, 7, 21)}, + + {HijrahChrono.INSTANCE.date(2, 1, 1), LocalDate.of(623, 7, 8)}, + {HijrahChrono.INSTANCE.date(3, 1, 1), LocalDate.of(624, 6, 27)}, + {HijrahChrono.INSTANCE.date(3, 12, 6), LocalDate.of(625, 5, 23)}, + {HijrahChrono.INSTANCE.date(4, 1, 1), LocalDate.of(625, 6, 16)}, + {HijrahChrono.INSTANCE.date(4, 7, 3), LocalDate.of(625, 12, 12)}, + {HijrahChrono.INSTANCE.date(4, 7, 4), LocalDate.of(625, 12, 13)}, + {HijrahChrono.INSTANCE.date(5, 1, 1), LocalDate.of(626, 6, 5)}, + {HijrahChrono.INSTANCE.date(1662, 3, 3), LocalDate.of(2234, 4, 3)}, + {HijrahChrono.INSTANCE.date(1728, 10, 28), LocalDate.of(2298, 12, 03)}, + {HijrahChrono.INSTANCE.date(1728, 10, 29), LocalDate.of(2298, 12, 04)}, + }; + } + + @Test(dataProvider="samples", groups={"tck"}) + public void test_toLocalDate(ChronoLocalDate hijrahDate, LocalDate iso) { + assertEquals(LocalDate.from(hijrahDate), iso); + } + + @Test(dataProvider="samples", groups={"tck"}) + public void test_fromCalendrical(ChronoLocalDate hijrahDate, LocalDate iso) { + assertEquals(HijrahChrono.INSTANCE.date(iso), hijrahDate); + } + + @DataProvider(name="badDates") + Object[][] data_badDates() { + return new Object[][] { + {1728, 0, 0}, + + {1728, -1, 1}, + {1728, 0, 1}, + {1728, 14, 1}, + {1728, 15, 1}, + + {1728, 1, -1}, + {1728, 1, 0}, + {1728, 1, 32}, + + {1728, 12, -1}, + {1728, 12, 0}, + {1728, 12, 32}, + }; + } + + @Test(dataProvider="badDates", groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_badDates(int year, int month, int dom) { + HijrahChrono.INSTANCE.date(year, month, dom); + } + + //----------------------------------------------------------------------- + // with(WithAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjust1() { + ChronoLocalDate base = HijrahChrono.INSTANCE.date(1728, 10, 28); + ChronoLocalDate test = base.with(Adjusters.lastDayOfMonth()); + assertEquals(test, HijrahChrono.INSTANCE.date(1728, 10, 29)); + } + + @Test(groups={"tck"}) + public void test_adjust2() { + ChronoLocalDate base = HijrahChrono.INSTANCE.date(1728, 12, 2); + ChronoLocalDate test = base.with(Adjusters.lastDayOfMonth()); + assertEquals(test, HijrahChrono.INSTANCE.date(1728, 12, 30)); + } + + //----------------------------------------------------------------------- + // HijrahDate.with(Local*) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjust_toLocalDate() { + ChronoLocalDate hijrahDate = HijrahChrono.INSTANCE.date(1726, 1, 4); + ChronoLocalDate test = hijrahDate.with(LocalDate.of(2012, 7, 6)); + assertEquals(test, HijrahChrono.INSTANCE.date(1433, 8, 16)); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_adjust_toMonth() { + ChronoLocalDate hijrahDate = HijrahChrono.INSTANCE.date(1726, 1, 4); + hijrahDate.with(Month.APRIL); + } + + //----------------------------------------------------------------------- + // LocalDate.with(HijrahDate) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_LocalDate_adjustToHijrahDate() { + ChronoLocalDate hijrahDate = HijrahChrono.INSTANCE.date(1728, 10, 29); + LocalDate test = LocalDate.MIN.with(hijrahDate); + assertEquals(test, LocalDate.of(2298, 12, 4)); + } + + @Test(groups={"tck"}) + public void test_LocalDateTime_adjustToHijrahDate() { + ChronoLocalDate hijrahDate = HijrahChrono.INSTANCE.date(1728, 10, 29); + LocalDateTime test = LocalDateTime.MIN.with(hijrahDate); + assertEquals(test, LocalDateTime.of(2298, 12, 4, 0, 0)); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="toString") + Object[][] data_toString() { + return new Object[][] { + {HijrahChrono.INSTANCE.date(1, 1, 1), "Hijrah AH 1-01-01"}, + {HijrahChrono.INSTANCE.date(1728, 10, 28), "Hijrah AH 1728-10-28"}, + {HijrahChrono.INSTANCE.date(1728, 10, 29), "Hijrah AH 1728-10-29"}, + {HijrahChrono.INSTANCE.date(1727, 12, 5), "Hijrah AH 1727-12-05"}, + {HijrahChrono.INSTANCE.date(1727, 12, 6), "Hijrah AH 1727-12-06"}, + }; + } + + @Test(dataProvider="toString", groups={"tck"}) + public void test_toString(ChronoLocalDate hijrahDate, String expected) { + assertEquals(hijrahDate.toString(), expected); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @Test(groups="tck") + public void test_equals_true() { + assertTrue(HijrahChrono.INSTANCE.equals(HijrahChrono.INSTANCE)); + } + + @Test(groups="tck") + public void test_equals_false() { + assertFalse(HijrahChrono.INSTANCE.equals(ISOChrono.INSTANCE)); + } + +} diff --git a/test/java/time/tck/java/time/calendar/TestJapaneseChrono.java b/test/java/time/tck/java/time/calendar/TestJapaneseChrono.java new file mode 100644 index 0000000000000000000000000000000000000000..614a3ff0b0a8b0efd339a9fe553e14deedb2a275 --- /dev/null +++ b/test/java/time/tck/java/time/calendar/TestJapaneseChrono.java @@ -0,0 +1,284 @@ +/* + * 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) 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 tck.java.time.calendar; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.util.List; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Month; +import java.time.calendar.JapaneseChrono; +import java.time.temporal.Adjusters; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.Era; +import java.time.temporal.ISOChrono; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test. + */ +@Test +public class TestJapaneseChrono { + + //----------------------------------------------------------------------- + // Chrono.ofName("Japanese") Lookup by name + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_chrono_byName() { + Chrono c = JapaneseChrono.INSTANCE; + Chrono test = Chrono.of("Japanese"); + Assert.assertNotNull(test, "The Japanese calendar could not be found byName"); + Assert.assertEquals(test.getId(), "Japanese", "ID mismatch"); + Assert.assertEquals(test.getCalendarType(), "japanese", "Type mismatch"); + Assert.assertEquals(test, c); + } + + //----------------------------------------------------------------------- + // creation, toLocalDate() + //----------------------------------------------------------------------- + @DataProvider(name="samples") + Object[][] data_samples() { + return new Object[][] { + {JapaneseChrono.INSTANCE.date(1, 1, 1), LocalDate.of(1, 1, 1)}, + {JapaneseChrono.INSTANCE.date(1, 1, 2), LocalDate.of(1, 1, 2)}, + {JapaneseChrono.INSTANCE.date(1, 1, 3), LocalDate.of(1, 1, 3)}, + + {JapaneseChrono.INSTANCE.date(2, 1, 1), LocalDate.of(2, 1, 1)}, + {JapaneseChrono.INSTANCE.date(3, 1, 1), LocalDate.of(3, 1, 1)}, + {JapaneseChrono.INSTANCE.date(3, 12, 6), LocalDate.of(3, 12, 6)}, + {JapaneseChrono.INSTANCE.date(4, 1, 1), LocalDate.of(4, 1, 1)}, + {JapaneseChrono.INSTANCE.date(4, 7, 3), LocalDate.of(4, 7, 3)}, + {JapaneseChrono.INSTANCE.date(4, 7, 4), LocalDate.of(4, 7, 4)}, + {JapaneseChrono.INSTANCE.date(5, 1, 1), LocalDate.of(5, 1, 1)}, + {JapaneseChrono.INSTANCE.date(1662, 3, 3), LocalDate.of(1662, 3, 3)}, + {JapaneseChrono.INSTANCE.date(1728, 10, 28), LocalDate.of(1728, 10, 28)}, + {JapaneseChrono.INSTANCE.date(1728, 10, 29), LocalDate.of(1728, 10, 29)}, + }; + } + + @Test(dataProvider="samples", groups={"tck"}) + public void test_toLocalDate(ChronoLocalDate jdate, LocalDate iso) { + assertEquals(LocalDate.from(jdate), iso); + } + + @Test(dataProvider="samples", groups={"tck"}) + public void test_fromCalendrical(ChronoLocalDate jdate, LocalDate iso) { + assertEquals(JapaneseChrono.INSTANCE.date(iso), jdate); + } + + @DataProvider(name="badDates") + Object[][] data_badDates() { + return new Object[][] { + {1728, 0, 0}, + + {1728, -1, 1}, + {1728, 0, 1}, + {1728, 14, 1}, + {1728, 15, 1}, + + {1728, 1, -1}, + {1728, 1, 0}, + {1728, 1, 32}, + + {1728, 12, -1}, + {1728, 12, 0}, + {1728, 12, 32}, + }; + } + + @Test(dataProvider="badDates", groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_badDates(int year, int month, int dom) { + JapaneseChrono.INSTANCE.date(year, month, dom); + } + + //----------------------------------------------------------------------- + // with(WithAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjust1() { + ChronoLocalDate base = JapaneseChrono.INSTANCE.date(1728, 10, 29); + ChronoLocalDate test = base.with(Adjusters.lastDayOfMonth()); + assertEquals(test, JapaneseChrono.INSTANCE.date(1728, 10, 31)); + } + + @Test(groups={"tck"}) + public void test_adjust2() { + ChronoLocalDate base = JapaneseChrono.INSTANCE.date(1728, 12, 2); + ChronoLocalDate test = base.with(Adjusters.lastDayOfMonth()); + assertEquals(test, JapaneseChrono.INSTANCE.date(1728, 12, 31)); + } + + //----------------------------------------------------------------------- + // JapaneseDate.with(Local*) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjust_toLocalDate() { + ChronoLocalDate jdate = JapaneseChrono.INSTANCE.date(1726, 1, 4); + ChronoLocalDate test = jdate.with(LocalDate.of(2012, 7, 6)); + assertEquals(test, JapaneseChrono.INSTANCE.date(2012, 7, 6)); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_adjust_toMonth() { + ChronoLocalDate jdate = JapaneseChrono.INSTANCE.date(1726, 1, 4); + jdate.with(Month.APRIL); + } + + //----------------------------------------------------------------------- + // LocalDate.with(JapaneseDate) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_LocalDate_adjustToJapaneseDate() { + ChronoLocalDate jdate = JapaneseChrono.INSTANCE.date(1728, 10, 29); + LocalDate test = LocalDate.MIN.with(jdate); + assertEquals(test, LocalDate.of(1728, 10, 29)); + } + + @Test(groups={"tck"}) + public void test_LocalDateTime_adjustToJapaneseDate() { + ChronoLocalDate jdate = JapaneseChrono.INSTANCE.date(1728, 10, 29); + LocalDateTime test = LocalDateTime.MIN.with(jdate); + assertEquals(test, LocalDateTime.of(1728, 10, 29, 0, 0)); + } + + //----------------------------------------------------------------------- + // Check Japanese Eras + //----------------------------------------------------------------------- + @DataProvider(name="japaneseEras") + Object[][] data_japanseseEras() { + return new Object[][] { + { JapaneseChrono.ERA_SEIREKI, -999, "Seireki"}, + { JapaneseChrono.ERA_MEIJI, -1, "Meiji"}, + { JapaneseChrono.ERA_TAISHO, 0, "Taisho"}, + { JapaneseChrono.ERA_SHOWA, 1, "Showa"}, + { JapaneseChrono.ERA_HEISEI, 2, "Heisei"}, + }; + } + + @Test(groups={"tck"}, dataProvider="japaneseEras") + public void test_Japanese_Eras(Era era, int eraValue, String name) { + assertEquals(era.getValue(), eraValue, "EraValue"); + assertEquals(era.toString(), name, "Era Name"); + assertEquals(era, JapaneseChrono.INSTANCE.eraOf(eraValue), "JapaneseChrono.eraOf()"); + List> eras = JapaneseChrono.INSTANCE.eras(); + assertTrue(eras.contains(era), "Era is not present in JapaneseChrono.INSTANCE.eras()"); + } + + @Test(groups="tck") + public void test_Japanese_badEras() { + int badEras[] = {-1000, -998, -997, -2, 3, 4, 1000}; + for (int badEra : badEras) { + try { + Era era = JapaneseChrono.INSTANCE.eraOf(badEra); + fail("JapaneseChrono.eraOf returned " + era + " + for invalid eraValue " + badEra); + } catch (DateTimeException ex) { + // ignore expected exception + } + } + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="toString") + Object[][] data_toString() { + return new Object[][] { + {JapaneseChrono.INSTANCE.date(0001, 1, 1), "Japanese 0001-01-01"}, + {JapaneseChrono.INSTANCE.date(1728, 10, 28), "Japanese 1728-10-28"}, + {JapaneseChrono.INSTANCE.date(1728, 10, 29), "Japanese 1728-10-29"}, + {JapaneseChrono.INSTANCE.date(1727, 12, 5), "Japanese 1727-12-05"}, + {JapaneseChrono.INSTANCE.date(1727, 12, 6), "Japanese 1727-12-06"}, + {JapaneseChrono.INSTANCE.date(1868, 9, 8), "Japanese Meiji 1-09-08"}, + {JapaneseChrono.INSTANCE.date(1912, 7, 29), "Japanese Meiji 45-07-29"}, + {JapaneseChrono.INSTANCE.date(1912, 7, 30), "Japanese Taisho 1-07-30"}, + {JapaneseChrono.INSTANCE.date(1926, 12, 24), "Japanese Taisho 15-12-24"}, + {JapaneseChrono.INSTANCE.date(1926, 12, 25), "Japanese Showa 1-12-25"}, + {JapaneseChrono.INSTANCE.date(1989, 1, 7), "Japanese Showa 64-01-07"}, + {JapaneseChrono.INSTANCE.date(1989, 1, 8), "Japanese Heisei 1-01-08"}, + {JapaneseChrono.INSTANCE.date(2012, 12, 6), "Japanese Heisei 24-12-06"}, + }; + } + + @Test(dataProvider="toString", groups={"tck"}) + public void test_toString(ChronoLocalDate jdate, String expected) { + assertEquals(jdate.toString(), expected); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @Test(groups="tck") + public void test_equals_true() { + assertTrue(JapaneseChrono.INSTANCE.equals(JapaneseChrono.INSTANCE)); + } + + @Test(groups="tck") + public void test_equals_false() { + assertFalse(JapaneseChrono.INSTANCE.equals(ISOChrono.INSTANCE)); + } + +} diff --git a/test/java/time/tck/java/time/calendar/TestMinguoChrono.java b/test/java/time/tck/java/time/calendar/TestMinguoChrono.java new file mode 100644 index 0000000000000000000000000000000000000000..118f7d89b706b061dc89d79d559eeba5abcd9570 --- /dev/null +++ b/test/java/time/tck/java/time/calendar/TestMinguoChrono.java @@ -0,0 +1,272 @@ +/* + * 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) 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 tck.java.time.calendar; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneOffset; +import java.time.calendar.MinguoChrono; +import java.time.temporal.Adjusters; +import java.time.temporal.ChronoUnit; +import java.time.temporal.ChronoZonedDateTime; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.ChronoLocalDateTime; +import java.time.temporal.ISOChrono; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test. + */ +@Test +public class TestMinguoChrono { + + //----------------------------------------------------------------------- + // Chrono.ofName("Minguo") Lookup by name + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_chrono_byName() { + Chrono c = MinguoChrono.INSTANCE; + Chrono test = Chrono.of("Minguo"); + Assert.assertNotNull(test, "The Minguo calendar could not be found byName"); + Assert.assertEquals(test.getId(), "Minguo", "ID mismatch"); + Assert.assertEquals(test.getCalendarType(), "roc", "Type mismatch"); + Assert.assertEquals(test, c); + } + + //----------------------------------------------------------------------- + // creation, toLocalDate() + //----------------------------------------------------------------------- + @DataProvider(name="samples") + Object[][] data_samples() { + return new Object[][] { + {MinguoChrono.INSTANCE.date(1, 1, 1), LocalDate.of(1912, 1, 1)}, + {MinguoChrono.INSTANCE.date(1, 1, 2), LocalDate.of(1912, 1, 2)}, + {MinguoChrono.INSTANCE.date(1, 1, 3), LocalDate.of(1912, 1, 3)}, + + {MinguoChrono.INSTANCE.date(2, 1, 1), LocalDate.of(1913, 1, 1)}, + {MinguoChrono.INSTANCE.date(3, 1, 1), LocalDate.of(1914, 1, 1)}, + {MinguoChrono.INSTANCE.date(3, 12, 6), LocalDate.of(1914, 12, 6)}, + {MinguoChrono.INSTANCE.date(4, 1, 1), LocalDate.of(1915, 1, 1)}, + {MinguoChrono.INSTANCE.date(4, 7, 3), LocalDate.of(1915, 7, 3)}, + {MinguoChrono.INSTANCE.date(4, 7, 4), LocalDate.of(1915, 7, 4)}, + {MinguoChrono.INSTANCE.date(5, 1, 1), LocalDate.of(1916, 1, 1)}, + {MinguoChrono.INSTANCE.date(100, 3, 3), LocalDate.of(2011, 3, 3)}, + {MinguoChrono.INSTANCE.date(101, 10, 28), LocalDate.of(2012, 10, 28)}, + {MinguoChrono.INSTANCE.date(101, 10, 29), LocalDate.of(2012, 10, 29)}, + }; + } + + @Test(dataProvider="samples", groups={"tck"}) + public void test_toLocalDate(ChronoLocalDate minguo, LocalDate iso) { + assertEquals(LocalDate.from(minguo), iso); + } + + @Test(dataProvider="samples", groups={"tck"}) + public void test_fromCalendrical(ChronoLocalDate minguo, LocalDate iso) { + assertEquals(MinguoChrono.INSTANCE.date(iso), minguo); + } + + @SuppressWarnings("unused") + @Test(dataProvider="samples", groups={"implementation"}) + public void test_MinguoDate(ChronoLocalDate minguoDate, LocalDate iso) { + ChronoLocalDate hd = minguoDate; + ChronoLocalDateTime hdt = hd.atTime(LocalTime.NOON); + ZoneOffset zo = ZoneOffset.ofHours(1); + ChronoZonedDateTime hzdt = hdt.atZone(zo); + hdt = hdt.plus(1, ChronoUnit.YEARS); + hdt = hdt.plus(1, ChronoUnit.MONTHS); + hdt = hdt.plus(1, ChronoUnit.DAYS); + hdt = hdt.plus(1, ChronoUnit.HOURS); + hdt = hdt.plus(1, ChronoUnit.MINUTES); + hdt = hdt.plus(1, ChronoUnit.SECONDS); + hdt = hdt.plus(1, ChronoUnit.NANOS); + ChronoLocalDateTime a2 = hzdt.getDateTime(); + ChronoLocalDate a3 = a2.getDate(); + ChronoLocalDate a5 = hzdt.getDate(); + //System.out.printf(" d: %s, dt: %s; odt: %s; zodt: %s; a4: %s%n", date, hdt, hodt, hzdt, a5); + } + + @Test() + public void test_MinguoChrono() { + ChronoLocalDate h1 = MinguoChrono.ERA_ROC.date(1, 2, 3); + ChronoLocalDate h2 = h1; + ChronoLocalDateTime h3 = h2.atTime(LocalTime.NOON); + @SuppressWarnings("unused") + ChronoZonedDateTime h4 = h3.atZone(ZoneOffset.UTC); + } + + @DataProvider(name="badDates") + Object[][] data_badDates() { + return new Object[][] { + {1912, 0, 0}, + + {1912, -1, 1}, + {1912, 0, 1}, + {1912, 14, 1}, + {1912, 15, 1}, + + {1912, 1, -1}, + {1912, 1, 0}, + {1912, 1, 32}, + {1912, 2, 29}, + {1912, 2, 30}, + + {1912, 12, -1}, + {1912, 12, 0}, + {1912, 12, 32}, + }; + } + + @Test(dataProvider="badDates", groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_badDates(int year, int month, int dom) { + MinguoChrono.INSTANCE.date(year, month, dom); + } + + //----------------------------------------------------------------------- + // with(DateTimeAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjust1() { + ChronoLocalDate base = MinguoChrono.INSTANCE.date(2012, 10, 29); + ChronoLocalDate test = base.with(Adjusters.lastDayOfMonth()); + assertEquals(test, MinguoChrono.INSTANCE.date(2012, 10, 31)); + } + + @Test(groups={"tck"}) + public void test_adjust2() { + ChronoLocalDate base = MinguoChrono.INSTANCE.date(1728, 12, 2); + ChronoLocalDate test = base.with(Adjusters.lastDayOfMonth()); + assertEquals(test, MinguoChrono.INSTANCE.date(1728, 12, 31)); + } + + //----------------------------------------------------------------------- + // MinguoDate.with(Local*) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjust_toLocalDate() { + ChronoLocalDate minguo = MinguoChrono.INSTANCE.date(99, 1, 4); + ChronoLocalDate test = minguo.with(LocalDate.of(2012, 7, 6)); + assertEquals(test, MinguoChrono.INSTANCE.date(101, 7, 6)); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_adjust_toMonth() { + ChronoLocalDate minguo = MinguoChrono.INSTANCE.date(1726, 1, 4); + minguo.with(Month.APRIL); + } + + //----------------------------------------------------------------------- + // LocalDate.with(MinguoDate) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_LocalDate_adjustToMinguoDate() { + ChronoLocalDate minguo = MinguoChrono.INSTANCE.date(101, 10, 29); + LocalDate test = LocalDate.MIN.with(minguo); + assertEquals(test, LocalDate.of(2012, 10, 29)); + } + + @Test(groups={"tck"}) + public void test_LocalDateTime_adjustToMinguoDate() { + ChronoLocalDate minguo = MinguoChrono.INSTANCE.date(101, 10, 29); + LocalDateTime test = LocalDateTime.MIN.with(minguo); + assertEquals(test, LocalDateTime.of(2012, 10, 29, 0, 0)); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="toString") + Object[][] data_toString() { + return new Object[][] { + {MinguoChrono.INSTANCE.date(1, 1, 1), "Minguo ROC 1-01-01"}, + {MinguoChrono.INSTANCE.date(1728, 10, 28), "Minguo ROC 1728-10-28"}, + {MinguoChrono.INSTANCE.date(1728, 10, 29), "Minguo ROC 1728-10-29"}, + {MinguoChrono.INSTANCE.date(1727, 12, 5), "Minguo ROC 1727-12-05"}, + {MinguoChrono.INSTANCE.date(1727, 12, 6), "Minguo ROC 1727-12-06"}, + }; + } + + @Test(dataProvider="toString", groups={"tck"}) + public void test_toString(ChronoLocalDate minguo, String expected) { + assertEquals(minguo.toString(), expected); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @Test(groups="tck") + public void test_equals_true() { + assertTrue(MinguoChrono.INSTANCE.equals(MinguoChrono.INSTANCE)); + } + + @Test(groups="tck") + public void test_equals_false() { + assertFalse(MinguoChrono.INSTANCE.equals(ISOChrono.INSTANCE)); + } + +} diff --git a/test/java/time/tck/java/time/calendar/TestServiceLoader.java b/test/java/time/tck/java/time/calendar/TestServiceLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..b7381b77c84bcce2b73e88e8a74881d18de92460 --- /dev/null +++ b/test/java/time/tck/java/time/calendar/TestServiceLoader.java @@ -0,0 +1,106 @@ +/* + * 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. + * + * 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) 2011-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 tck.java.time.calendar; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.fail; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.ServiceLoader; +import java.time.LocalDate; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.ISOChrono; +import java.time.DateTimeException; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * Tests that a custom Chronology is available via the ServiceLoader. + * The CopticChrono is configured via META-INF/services/java.time.temporal.Chrono. + */ +@Test +public class TestServiceLoader { + + @Test(groups={"tck"}) + public void test_CopticServiceLoader() { + Chrono chrono = Chrono.of("Coptic"); + ChronoLocalDate copticDate = chrono.date(1729, 4, 27); + LocalDate ld = LocalDate.from(copticDate); + assertEquals(ld, LocalDate.of(2013, 1, 5), "CopticDate does not match LocalDate"); + } + + @Test(groups="implementation") + public void test_copticServiceLoader() { + Map chronos = new HashMap<>(); + ServiceLoader loader = ServiceLoader.load(Chrono.class, null); + for (Chrono chrono : loader) { + chronos.put(chrono.getId(), chrono); + } + assertNotNull(chronos.get("Coptic"), "CopticChrono not found"); + } + +} diff --git a/test/java/time/tck/java/time/calendar/TestThaiBuddhistChrono.java b/test/java/time/tck/java/time/calendar/TestThaiBuddhistChrono.java new file mode 100644 index 0000000000000000000000000000000000000000..4d20f570be85207ce0fd1fe536216e6c960b717b --- /dev/null +++ b/test/java/time/tck/java/time/calendar/TestThaiBuddhistChrono.java @@ -0,0 +1,302 @@ +/* + * 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) 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 tck.java.time.calendar; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Month; +import java.time.calendar.ThaiBuddhistChrono; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.Adjusters; +import java.time.temporal.ValueRange; +import java.time.temporal.ISOChrono; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test. + */ +@Test +public class TestThaiBuddhistChrono { + + private static final int YDIFF = 543; + + //----------------------------------------------------------------------- + // Chrono.ofName("ThaiBuddhist") Lookup by name + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_chrono_byName() { + Chrono c = ThaiBuddhistChrono.INSTANCE; + Chrono test = Chrono.of("ThaiBuddhist"); + Assert.assertNotNull(test, "The ThaiBuddhist calendar could not be found byName"); + Assert.assertEquals(test.getId(), "ThaiBuddhist", "ID mismatch"); + Assert.assertEquals(test.getCalendarType(), "buddhist", "Type mismatch"); + Assert.assertEquals(test, c); + } + + //----------------------------------------------------------------------- + // creation, toLocalDate() + //----------------------------------------------------------------------- + @DataProvider(name="samples") + Object[][] data_samples() { + return new Object[][] { + {ThaiBuddhistChrono.INSTANCE.date(1 + YDIFF, 1, 1), LocalDate.of(1, 1, 1)}, + {ThaiBuddhistChrono.INSTANCE.date(1 + YDIFF, 1, 2), LocalDate.of(1, 1, 2)}, + {ThaiBuddhistChrono.INSTANCE.date(1 + YDIFF, 1, 3), LocalDate.of(1, 1, 3)}, + + {ThaiBuddhistChrono.INSTANCE.date(2 + YDIFF, 1, 1), LocalDate.of(2, 1, 1)}, + {ThaiBuddhistChrono.INSTANCE.date(3 + YDIFF, 1, 1), LocalDate.of(3, 1, 1)}, + {ThaiBuddhistChrono.INSTANCE.date(3 + YDIFF, 12, 6), LocalDate.of(3, 12, 6)}, + {ThaiBuddhistChrono.INSTANCE.date(4 + YDIFF, 1, 1), LocalDate.of(4, 1, 1)}, + {ThaiBuddhistChrono.INSTANCE.date(4 + YDIFF, 7, 3), LocalDate.of(4, 7, 3)}, + {ThaiBuddhistChrono.INSTANCE.date(4 + YDIFF, 7, 4), LocalDate.of(4, 7, 4)}, + {ThaiBuddhistChrono.INSTANCE.date(5 + YDIFF, 1, 1), LocalDate.of(5, 1, 1)}, + {ThaiBuddhistChrono.INSTANCE.date(1662 + YDIFF, 3, 3), LocalDate.of(1662, 3, 3)}, + {ThaiBuddhistChrono.INSTANCE.date(1728 + YDIFF, 10, 28), LocalDate.of(1728, 10, 28)}, + {ThaiBuddhistChrono.INSTANCE.date(1728 + YDIFF, 10, 29), LocalDate.of(1728, 10, 29)}, + {ThaiBuddhistChrono.INSTANCE.date(2555, 8, 29), LocalDate.of(2012, 8, 29)}, + }; + } + + @Test(dataProvider="samples", groups={"tck"}) + public void test_toLocalDate(ChronoLocalDate jdate, LocalDate iso) { + assertEquals(LocalDate.from(jdate), iso); + } + + @Test(dataProvider="samples", groups={"tck"}) + public void test_fromCalendrical(ChronoLocalDate jdate, LocalDate iso) { + assertEquals(ThaiBuddhistChrono.INSTANCE.date(iso), jdate); + } + + @DataProvider(name="badDates") + Object[][] data_badDates() { + return new Object[][] { + {1728, 0, 0}, + + {1728, -1, 1}, + {1728, 0, 1}, + {1728, 14, 1}, + {1728, 15, 1}, + + {1728, 1, -1}, + {1728, 1, 0}, + {1728, 1, 32}, + + {1728, 12, -1}, + {1728, 12, 0}, + {1728, 12, 32}, + }; + } + + @Test(dataProvider="badDates", groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_badDates(int year, int month, int dom) { + ThaiBuddhistChrono.INSTANCE.date(year, month, dom); + } + + //----------------------------------------------------------------------- + // with(WithAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjust1() { + ChronoLocalDate base = ThaiBuddhistChrono.INSTANCE.date(1728, 10, 29); + ChronoLocalDate test = base.with(Adjusters.lastDayOfMonth()); + assertEquals(test, ThaiBuddhistChrono.INSTANCE.date(1728, 10, 31)); + } + + @Test(groups={"tck"}) + public void test_adjust2() { + ChronoLocalDate base = ThaiBuddhistChrono.INSTANCE.date(1728, 12, 2); + ChronoLocalDate test = base.with(Adjusters.lastDayOfMonth()); + assertEquals(test, ThaiBuddhistChrono.INSTANCE.date(1728, 12, 31)); + } + + //----------------------------------------------------------------------- + // withYear() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withYear_BE() { + ChronoLocalDate base = ThaiBuddhistChrono.INSTANCE.date(2555, 8, 29); + ChronoLocalDate test = base.with(YEAR, 2554); + assertEquals(test, ThaiBuddhistChrono.INSTANCE.date(2554, 8, 29)); + } + + @Test(groups={"tck"}) + public void test_withYear_BBE() { + ChronoLocalDate base = ThaiBuddhistChrono.INSTANCE.date(-2554, 8, 29); + ChronoLocalDate test = base.with(YEAR_OF_ERA, 2554); + assertEquals(test, ThaiBuddhistChrono.INSTANCE.date(-2553, 8, 29)); + } + + //----------------------------------------------------------------------- + // withEra() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withEra_BE() { + ChronoLocalDate base = ThaiBuddhistChrono.INSTANCE.date(2555, 8, 29); + ChronoLocalDate test = base.with(ChronoField.ERA, ThaiBuddhistChrono.ERA_BE.getValue()); + assertEquals(test, ThaiBuddhistChrono.INSTANCE.date(2555, 8, 29)); + } + + @Test(groups={"tck"}) + public void test_withEra_BBE() { + ChronoLocalDate base = ThaiBuddhistChrono.INSTANCE.date(-2554, 8, 29); + ChronoLocalDate test = base.with(ChronoField.ERA, ThaiBuddhistChrono.ERA_BEFORE_BE.getValue()); + assertEquals(test, ThaiBuddhistChrono.INSTANCE.date(-2554, 8, 29)); + } + + @Test(groups={"tck"}) + public void test_withEra_swap() { + ChronoLocalDate base = ThaiBuddhistChrono.INSTANCE.date(-2554, 8, 29); + ChronoLocalDate test = base.with(ChronoField.ERA, ThaiBuddhistChrono.ERA_BE.getValue()); + assertEquals(test, ThaiBuddhistChrono.INSTANCE.date(2555, 8, 29)); + } + + //----------------------------------------------------------------------- + // BuddhistDate.with(Local*) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjust_toLocalDate() { + ChronoLocalDate jdate = ThaiBuddhistChrono.INSTANCE.date(1726, 1, 4); + ChronoLocalDate test = jdate.with(LocalDate.of(2012, 7, 6)); + assertEquals(test, ThaiBuddhistChrono.INSTANCE.date(2555, 7, 6)); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_adjust_toMonth() { + ChronoLocalDate jdate = ThaiBuddhistChrono.INSTANCE.date(1726, 1, 4); + jdate.with(Month.APRIL); + } + + //----------------------------------------------------------------------- + // LocalDate.with(BuddhistDate) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_LocalDate_adjustToBuddhistDate() { + ChronoLocalDate jdate = ThaiBuddhistChrono.INSTANCE.date(2555, 10, 29); + LocalDate test = LocalDate.MIN.with(jdate); + assertEquals(test, LocalDate.of(2012, 10, 29)); + } + + @Test(groups={"tck"}) + public void test_LocalDateTime_adjustToBuddhistDate() { + ChronoLocalDate jdate = ThaiBuddhistChrono.INSTANCE.date(2555, 10, 29); + LocalDateTime test = LocalDateTime.MIN.with(jdate); + assertEquals(test, LocalDateTime.of(2012, 10, 29, 0, 0)); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="toString") + Object[][] data_toString() { + return new Object[][] { + {ThaiBuddhistChrono.INSTANCE.date(544, 1, 1), "ThaiBuddhist BE 544-01-01"}, + {ThaiBuddhistChrono.INSTANCE.date(2271, 10, 28), "ThaiBuddhist BE 2271-10-28"}, + {ThaiBuddhistChrono.INSTANCE.date(2271, 10, 29), "ThaiBuddhist BE 2271-10-29"}, + {ThaiBuddhistChrono.INSTANCE.date(2270, 12, 5), "ThaiBuddhist BE 2270-12-05"}, + {ThaiBuddhistChrono.INSTANCE.date(2270, 12, 6), "ThaiBuddhist BE 2270-12-06"}, + }; + } + + @Test(dataProvider="toString", groups={"tck"}) + public void test_toString(ChronoLocalDate jdate, String expected) { + assertEquals(jdate.toString(), expected); + } + + //----------------------------------------------------------------------- + // chronology range(ChronoField) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_Chrono_range() { + long minYear = LocalDate.MIN.getYear() + YDIFF; + long maxYear = LocalDate.MAX.getYear() + YDIFF; + assertEquals(ThaiBuddhistChrono.INSTANCE.range(YEAR), ValueRange.of(minYear, maxYear)); + assertEquals(ThaiBuddhistChrono.INSTANCE.range(YEAR_OF_ERA), ValueRange.of(1, -minYear + 1, maxYear)); + + assertEquals(ThaiBuddhistChrono.INSTANCE.range(DAY_OF_MONTH), DAY_OF_MONTH.range()); + assertEquals(ThaiBuddhistChrono.INSTANCE.range(DAY_OF_YEAR), DAY_OF_YEAR.range()); + assertEquals(ThaiBuddhistChrono.INSTANCE.range(MONTH_OF_YEAR), MONTH_OF_YEAR.range()); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @Test(groups="tck") + public void test_equals_true() { + assertTrue(ThaiBuddhistChrono.INSTANCE.equals(ThaiBuddhistChrono.INSTANCE)); + } + + @Test(groups="tck") + public void test_equals_false() { + assertFalse(ThaiBuddhistChrono.INSTANCE.equals(ISOChrono.INSTANCE)); + } + +} diff --git a/test/java/time/tck/java/time/format/TCKDateTimeFormatSymbols.java b/test/java/time/tck/java/time/format/TCKDateTimeFormatSymbols.java new file mode 100644 index 0000000000000000000000000000000000000000..f2be900c9085482adcb969fa86d4a0f8c670feb7 --- /dev/null +++ b/test/java/time/tck/java/time/format/TCKDateTimeFormatSymbols.java @@ -0,0 +1,212 @@ +/* + * 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. + * + * 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) 2011-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 tck.java.time.format; + +import java.time.format.*; +import test.java.time.format.*; + +import static org.testng.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Locale; + +import org.testng.annotations.Test; + +/** + * Test DateTimeFormatSymbols. + */ +@Test +public class TCKDateTimeFormatSymbols { + + @Test(groups={"tck"}) + public void test_getAvailableLocales() { + Locale[] locales = DateTimeFormatSymbols.getAvailableLocales(); + assertEquals(locales.length > 0, true); + assertEquals(Arrays.asList(locales).contains(Locale.US), true); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_of_Locale() { + DateTimeFormatSymbols loc1 = DateTimeFormatSymbols.of(Locale.CANADA); + assertEquals(loc1.getZeroDigit(), '0'); + assertEquals(loc1.getPositiveSign(), '+'); + assertEquals(loc1.getNegativeSign(), '-'); + assertEquals(loc1.getDecimalSeparator(), '.'); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_STANDARD() { + DateTimeFormatSymbols loc1 = DateTimeFormatSymbols.STANDARD; + assertEquals(loc1.getZeroDigit(), '0'); + assertEquals(loc1.getPositiveSign(), '+'); + assertEquals(loc1.getNegativeSign(), '-'); + assertEquals(loc1.getDecimalSeparator(), '.'); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_zeroDigit() { + DateTimeFormatSymbols base = DateTimeFormatSymbols.STANDARD; + assertEquals(base.withZeroDigit('A').getZeroDigit(), 'A'); + } + + @Test(groups={"tck"}) + public void test_positiveSign() { + DateTimeFormatSymbols base = DateTimeFormatSymbols.STANDARD; + assertEquals(base.withPositiveSign('A').getPositiveSign(), 'A'); + } + + @Test(groups={"tck"}) + public void test_negativeSign() { + DateTimeFormatSymbols base = DateTimeFormatSymbols.STANDARD; + assertEquals(base.withNegativeSign('A').getNegativeSign(), 'A'); + } + + @Test(groups={"tck"}) + public void test_decimalSeparator() { + DateTimeFormatSymbols base = DateTimeFormatSymbols.STANDARD; + assertEquals(base.withDecimalSeparator('A').getDecimalSeparator(), 'A'); + } + + //----------------------------------------------------------------------- + /* TBD: convertToDigit and convertNumberToI18N are package-private methods + @Test(groups={"tck"}) + public void test_convertToDigit_base() { + DateTimeFormatSymbols base = DateTimeFormatSymbols.STANDARD; + assertEquals(base.convertToDigit('0'), 0); + assertEquals(base.convertToDigit('1'), 1); + assertEquals(base.convertToDigit('9'), 9); + assertEquals(base.convertToDigit(' '), -1); + assertEquals(base.convertToDigit('A'), -1); + } + + @Test(groups={"tck"}) + public void test_convertToDigit_altered() { + DateTimeFormatSymbols base = DateTimeFormatSymbols.STANDARD.withZeroDigit('A'); + assertEquals(base.convertToDigit('A'), 0); + assertEquals(base.convertToDigit('B'), 1); + assertEquals(base.convertToDigit('J'), 9); + assertEquals(base.convertToDigit(' '), -1); + assertEquals(base.convertToDigit('0'), -1); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_convertNumberToI18N_base() { + DateTimeFormatSymbols base = DateTimeFormatSymbols.STANDARD; + assertEquals(base.convertNumberToI18N("134"), "134"); + } + + @Test(groups={"tck"}) + public void test_convertNumberToI18N_altered() { + DateTimeFormatSymbols base = DateTimeFormatSymbols.STANDARD.withZeroDigit('A'); + assertEquals(base.convertNumberToI18N("134"), "BDE"); + } + */ + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_equalsHashCode1() { + DateTimeFormatSymbols a = DateTimeFormatSymbols.STANDARD; + DateTimeFormatSymbols b = DateTimeFormatSymbols.STANDARD; + assertEquals(a.equals(b), true); + assertEquals(b.equals(a), true); + assertEquals(a.hashCode(), b.hashCode()); + } + + @Test(groups={"tck"}) + public void test_equalsHashCode2() { + DateTimeFormatSymbols a = DateTimeFormatSymbols.STANDARD.withZeroDigit('A'); + DateTimeFormatSymbols b = DateTimeFormatSymbols.STANDARD.withZeroDigit('A'); + assertEquals(a.equals(b), true); + assertEquals(b.equals(a), true); + assertEquals(a.hashCode(), b.hashCode()); + } + + @Test(groups={"tck"}) + public void test_equalsHashCode3() { + DateTimeFormatSymbols a = DateTimeFormatSymbols.STANDARD.withZeroDigit('A'); + DateTimeFormatSymbols b = DateTimeFormatSymbols.STANDARD.withDecimalSeparator('A'); + assertEquals(a.equals(b), false); + assertEquals(b.equals(a), false); + } + + @Test(groups={"tck"}) + public void test_equalsHashCode_bad() { + DateTimeFormatSymbols a = DateTimeFormatSymbols.STANDARD; + assertEquals(a.equals(""), false); + assertEquals(a.equals(null), false); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString_base() { + DateTimeFormatSymbols base = DateTimeFormatSymbols.STANDARD; + assertEquals(base.toString(), "Symbols[0+-.]"); + } + + @Test(groups={"tck"}) + public void test_toString_altered() { + DateTimeFormatSymbols base = DateTimeFormatSymbols.of(Locale.US).withZeroDigit('A').withDecimalSeparator('@'); + assertEquals(base.toString(), "Symbols[A+-@]"); + } + +} diff --git a/test/java/time/tck/java/time/format/TCKDateTimeFormatter.java b/test/java/time/tck/java/time/format/TCKDateTimeFormatter.java new file mode 100644 index 0000000000000000000000000000000000000000..e206a1f28a7d721ae14e37e82f903b5cb429a9c9 --- /dev/null +++ b/test/java/time/tck/java/time/format/TCKDateTimeFormatter.java @@ -0,0 +1,705 @@ +/* + * 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. + * + * 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 tck.java.time.format; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.YEAR; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +import java.io.IOException; +import java.text.Format; +import java.text.ParseException; +import java.text.ParsePosition; +import java.util.Locale; + +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.calendar.ThaiBuddhistChrono; +import java.time.format.DateTimeFormatSymbols; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.format.DateTimePrintException; +import java.time.format.SignStyle; +import java.time.format.DateTimeBuilder; +import java.time.temporal.Chrono; +import java.time.temporal.ISOChrono; +import java.time.temporal.OffsetDate; +import java.time.temporal.OffsetDateTime; +import java.time.temporal.OffsetTime; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQuery; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import test.java.time.format.MockIOExceptionAppendable; + +/** + * Test DateTimeFormatter. + */ +@Test(groups={"tck"}) +public class TCKDateTimeFormatter { + + private static final ZoneOffset OFFSET_PONE = ZoneOffset.ofHours(1); + private static final ZoneOffset OFFSET_PTHREE = ZoneOffset.ofHours(3); + private static final ZoneId ZONE_PARIS = ZoneId.of("Europe/Paris"); + + private static final DateTimeFormatter BASIC_FORMATTER = DateTimeFormatters.pattern("'ONE'd"); + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatters.pattern("'ONE'yyyy MM dd"); + + private DateTimeFormatter fmt; + + @BeforeMethod + public void setUp() { + fmt = new DateTimeFormatterBuilder().appendLiteral("ONE") + .appendValue(DAY_OF_MONTH, 1, 2, SignStyle.NOT_NEGATIVE) + .toFormatter(); + } + + //----------------------------------------------------------------------- + @Test + public void test_withLocale() { + DateTimeFormatter base = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + DateTimeFormatter test = base.withLocale(Locale.GERMAN); + assertEquals(test.getLocale(), Locale.GERMAN); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_withLocale_null() { + DateTimeFormatter base = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + base.withLocale((Locale) null); + } + + //----------------------------------------------------------------------- + @Test + public void test_withChrono() { + DateTimeFormatter test = fmt; + assertEquals(test.getChrono(), null); + test = test.withChrono(ISOChrono.INSTANCE); + assertEquals(test.getChrono(), ISOChrono.INSTANCE); + test = test.withChrono(null); + assertEquals(test.getChrono(), null); + } + + //----------------------------------------------------------------------- + @Test + public void test_withZone() { + DateTimeFormatter test = fmt; + assertEquals(test.getZone(), null); + test = test.withZone(ZoneId.of("Europe/Paris")); + assertEquals(test.getZone(), ZoneId.of("Europe/Paris")); + test = test.withZone(ZoneOffset.UTC); + assertEquals(test.getZone(), ZoneOffset.UTC); + test = test.withZone(null); + assertEquals(test.getZone(), null); + } + + //----------------------------------------------------------------------- + // print + //----------------------------------------------------------------------- + @DataProvider(name="print") + Object[][] data_print() { + LocalDate ld = LocalDate.of(2008, 6, 30); + LocalTime lt = LocalTime.of(11, 30); + LocalDateTime ldt = LocalDateTime.of(2008, 6, 30, 11, 30); + OffsetDate od = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); + OffsetTime ot = OffsetTime.of(LocalTime.of(11, 30), OFFSET_PONE); + OffsetDateTime odt = OffsetDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 30), OFFSET_PONE); + ZonedDateTime zdt = ZonedDateTime.of(LocalDateTime.of(2008, 6, 30, 11, 30), ZONE_PARIS); + Instant instant = Instant.ofEpochSecond(3600); + return new Object[][] { + {null, null, ld, "2008::"}, + {null, null, lt, ":11:"}, + {null, null, ldt, "2008:11:"}, + {null, null, od, "2008::+01:00"}, + {null, null, ot, ":11:+01:00"}, + {null, null, odt, "2008:11:+01:00"}, + {null, null, zdt, "2008:11:+02:00Europe/Paris"}, + {null, null, instant, "::"}, + + {null, ZONE_PARIS, ld, "2008::"}, + {null, ZONE_PARIS, lt, ":11:"}, + {null, ZONE_PARIS, ldt, "2008:11:"}, + {null, ZONE_PARIS, od, "2008::+01:00"}, + {null, ZONE_PARIS, ot, ":11:+01:00"}, + {null, ZONE_PARIS, odt, "2008:12:+02:00Europe/Paris"}, + {null, ZONE_PARIS, zdt, "2008:11:+02:00Europe/Paris"}, + {null, ZONE_PARIS, instant, "1970:02:+01:00Europe/Paris"}, + + {null, OFFSET_PTHREE, ld, "2008::"}, + {null, OFFSET_PTHREE, lt, ":11:"}, + {null, OFFSET_PTHREE, ldt, "2008:11:"}, + {null, OFFSET_PTHREE, od, "2008::+01:00"}, + {null, OFFSET_PTHREE, ot, ":11:+01:00"}, + {null, OFFSET_PTHREE, odt, "2008:13:+03:00"}, + {null, OFFSET_PTHREE, zdt, "2008:12:+03:00"}, + {null, OFFSET_PTHREE, instant, "1970:04:+03:00"}, + + {ThaiBuddhistChrono.INSTANCE, null, ld, "2551::"}, + {ThaiBuddhistChrono.INSTANCE, null, lt, ":11:"}, + {ThaiBuddhistChrono.INSTANCE, null, ldt, "2551:11:"}, + {ThaiBuddhistChrono.INSTANCE, null, od, "2551::+01:00"}, + {ThaiBuddhistChrono.INSTANCE, null, ot, ":11:+01:00"}, + {ThaiBuddhistChrono.INSTANCE, null, odt, "2551:11:+01:00"}, + {ThaiBuddhistChrono.INSTANCE, null, zdt, "2551:11:+02:00Europe/Paris"}, + {ThaiBuddhistChrono.INSTANCE, null, instant, "::"}, + + {ThaiBuddhistChrono.INSTANCE, ZONE_PARIS, ld, "2551::"}, + {ThaiBuddhistChrono.INSTANCE, ZONE_PARIS, lt, ":11:"}, + {ThaiBuddhistChrono.INSTANCE, ZONE_PARIS, ldt, "2551:11:"}, + {ThaiBuddhistChrono.INSTANCE, ZONE_PARIS, od, "2551::+01:00"}, + {ThaiBuddhistChrono.INSTANCE, ZONE_PARIS, ot, ":11:+01:00"}, + {ThaiBuddhistChrono.INSTANCE, ZONE_PARIS, odt, "2551:12:+02:00Europe/Paris"}, + {ThaiBuddhistChrono.INSTANCE, ZONE_PARIS, zdt, "2551:11:+02:00Europe/Paris"}, + {ThaiBuddhistChrono.INSTANCE, ZONE_PARIS, instant, "1970:02:+01:00Europe/Paris"}, + }; + } + + @Test(dataProvider="print") + public void test_print_Temporal(Chrono overrideChrono, ZoneId overrideZone, Temporal temporal, String expected) { + DateTimeFormatter test = new DateTimeFormatterBuilder() + .optionalStart().appendValue(YEAR, 4).optionalEnd() + .appendLiteral(':').optionalStart().appendValue(HOUR_OF_DAY, 2).optionalEnd() + .appendLiteral(':').optionalStart().appendOffsetId().optionalStart().appendZoneRegionId().optionalEnd().optionalEnd() + .toFormatter(Locale.ENGLISH) + .withChrono(overrideChrono).withZone(overrideZone); + String result = test.print(temporal); + assertEquals(result, expected); + } + + @Test + public void test_print_Temporal_simple() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + String result = test.print(LocalDate.of(2008, 6, 30)); + assertEquals(result, "ONE30"); + } + + @Test(expectedExceptions=DateTimeException.class) + public void test_print_Temporal_noSuchField() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + test.print(LocalTime.of(11, 30)); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_print_Temporal_null() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + test.print((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + @Test + public void test_print_TemporalAppendable() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + StringBuilder buf = new StringBuilder(); + test.printTo(LocalDate.of(2008, 6, 30), buf); + assertEquals(buf.toString(), "ONE30"); + } + + @Test(expectedExceptions=DateTimeException.class) + public void test_print_TemporalAppendable_noSuchField() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + StringBuilder buf = new StringBuilder(); + test.printTo(LocalTime.of(11, 30), buf); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_print_TemporalAppendable_nullTemporal() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + StringBuilder buf = new StringBuilder(); + test.printTo((TemporalAccessor) null, buf); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_print_TemporalAppendable_nullAppendable() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + test.printTo(LocalDate.of(2008, 6, 30), (Appendable) null); + } + + @Test(expectedExceptions=IOException.class) // IOException + public void test_print_TemporalAppendable_ioError() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + try { + test.printTo(LocalDate.of(2008, 6, 30), new MockIOExceptionAppendable()); + } catch (DateTimePrintException ex) { + assertEquals(ex.getCause() instanceof IOException, true); + ex.rethrowIOException(); + } + } + + //----------------------------------------------------------------------- + // parse(Query) + //----------------------------------------------------------------------- + @Test + public void test_parse_Query_String() throws Exception { + LocalDate result = DATE_FORMATTER.parse("ONE2012 07 27", LocalDate::from); + assertEquals(result, LocalDate.of(2012, 7, 27)); + } + + @Test + public void test_parse_Query_CharSequence() throws Exception { + LocalDate result = DATE_FORMATTER.parse(new StringBuilder("ONE2012 07 27"), LocalDate::from); + assertEquals(result, LocalDate.of(2012, 7, 27)); + } + + @Test(expectedExceptions=DateTimeParseException.class) + public void test_parse_Query_String_parseError() throws Exception { + try { + DATE_FORMATTER.parse("ONE2012 07 XX", LocalDate::from); + } catch (DateTimeParseException ex) { + assertEquals(ex.getMessage().contains("could not be parsed"), true); + assertEquals(ex.getMessage().contains("ONE2012 07 XX"), true); + assertEquals(ex.getParsedString(), "ONE2012 07 XX"); + assertEquals(ex.getErrorIndex(), 11); + throw ex; + } + } + + @Test(expectedExceptions=DateTimeParseException.class) + public void test_parse_Query_String_parseErrorLongText() throws Exception { + try { + DATE_FORMATTER.parse("ONEXXX67890123456789012345678901234567890123456789012345678901234567890123456789", LocalDate::from); + } catch (DateTimeParseException ex) { + assertEquals(ex.getMessage().contains("could not be parsed"), true); + assertEquals(ex.getMessage().contains("ONEXXX6789012345678901234567890123456789012345678901234567890123..."), true); + assertEquals(ex.getParsedString(), "ONEXXX67890123456789012345678901234567890123456789012345678901234567890123456789"); + assertEquals(ex.getErrorIndex(), 3); + throw ex; + } + } + + @Test(expectedExceptions=DateTimeParseException.class) + public void test_parse_Query_String_parseIncomplete() throws Exception { + try { + DATE_FORMATTER.parse("ONE2012 07 27SomethingElse", LocalDate::from); + } catch (DateTimeParseException ex) { + assertEquals(ex.getMessage().contains("could not be parsed"), true); + assertEquals(ex.getMessage().contains("ONE2012 07 27SomethingElse"), true); + assertEquals(ex.getParsedString(), "ONE2012 07 27SomethingElse"); + assertEquals(ex.getErrorIndex(), 13); + throw ex; + } + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_parse_Query_String_nullText() throws Exception { + DATE_FORMATTER.parse((String) null, LocalDate::from); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_parse_Query_String_nullRule() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + test.parse("30", (TemporalQuery) null); + } + + //----------------------------------------------------------------------- + @Test + public void test_parseBest_firstOption() throws Exception { + DateTimeFormatter test = DateTimeFormatters.pattern("yyyy-MM-dd[ZZZ]"); + TemporalAccessor result = test.parseBest("2011-06-30+03:00", OffsetDate::from, LocalDate::from); + assertEquals(result, OffsetDate.of(LocalDate.of(2011, 6, 30), ZoneOffset.ofHours(3))); + } + + @Test + public void test_parseBest_secondOption() throws Exception { + DateTimeFormatter test = DateTimeFormatters.pattern("yyyy-MM-dd[ZZZ]"); + TemporalAccessor result = test.parseBest("2011-06-30", OffsetDate::from, LocalDate::from); + assertEquals(result, LocalDate.of(2011, 6, 30)); + } + + @Test(expectedExceptions=DateTimeParseException.class) + public void test_parseBest_String_parseError() throws Exception { + DateTimeFormatter test = DateTimeFormatters.pattern("yyyy-MM-dd[ZZZ]"); + try { + test.parseBest("2011-06-XX", OffsetDate::from, LocalDate::from); + } catch (DateTimeParseException ex) { + assertEquals(ex.getMessage().contains("could not be parsed"), true); + assertEquals(ex.getMessage().contains("XX"), true); + assertEquals(ex.getParsedString(), "2011-06-XX"); + assertEquals(ex.getErrorIndex(), 8); + throw ex; + } + } + + @Test(expectedExceptions=DateTimeParseException.class) + public void test_parseBest_String_parseErrorLongText() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + try { + test.parseBest("ONEXXX67890123456789012345678901234567890123456789012345678901234567890123456789", LocalDate::from, OffsetDate::from); + } catch (DateTimeParseException ex) { + assertEquals(ex.getMessage().contains("could not be parsed"), true); + assertEquals(ex.getMessage().contains("ONEXXX6789012345678901234567890123456789012345678901234567890123..."), true); + assertEquals(ex.getParsedString(), "ONEXXX67890123456789012345678901234567890123456789012345678901234567890123456789"); + assertEquals(ex.getErrorIndex(), 3); + throw ex; + } + } + + @Test(expectedExceptions=DateTimeParseException.class) + public void test_parseBest_String_parseIncomplete() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + try { + test.parseBest("ONE30SomethingElse", LocalDate::from, OffsetDate::from); + } catch (DateTimeParseException ex) { + assertEquals(ex.getMessage().contains("could not be parsed"), true); + assertEquals(ex.getMessage().contains("ONE30SomethingElse"), true); + assertEquals(ex.getParsedString(), "ONE30SomethingElse"); + assertEquals(ex.getErrorIndex(), 5); + throw ex; + } + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_parseBest_String_nullText() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + test.parseBest((String) null, LocalDate::from, OffsetDate::from); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_parseBest_String_nullRules() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + test.parseBest("30", (TemporalQuery[]) null); + } + + @Test(expectedExceptions=IllegalArgumentException.class) + public void test_parseBest_String_zeroRules() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + test.parseBest("30", new TemporalQuery[0]); + } + + @Test(expectedExceptions=IllegalArgumentException.class) + public void test_parseBest_String_oneRule() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + test.parseBest("30", LocalDate::from); + } + + //----------------------------------------------------------------------- + @Test + public void test_parseToBuilder_String() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + DateTimeBuilder result = test.parseToBuilder("ONE30"); + assertEquals(result.getFieldValueMap().size(), 1); + assertEquals(result.getFieldValue(DAY_OF_MONTH), 30L); + assertEquals(result.getCalendricalList().size(), 0); + } + + @Test + public void test_parseToBuilder_CharSequence() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + DateTimeBuilder result = test.parseToBuilder(new StringBuilder("ONE30")); + assertEquals(result.getFieldValueMap().size(), 1); + assertEquals(result.getFieldValue(DAY_OF_MONTH), 30L); + assertEquals(result.getCalendricalList().size(), 0); + } + + @Test(expectedExceptions=DateTimeParseException.class) + public void test_parseToBuilder_String_parseError() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + try { + test.parseToBuilder("ONEXXX"); + } catch (DateTimeParseException ex) { + assertEquals(ex.getMessage().contains("ONEXXX"), true); + assertEquals(ex.getParsedString(), "ONEXXX"); + assertEquals(ex.getErrorIndex(), 3); + throw ex; + } + } + + @Test(expectedExceptions=DateTimeParseException.class) + public void test_parseToBuilder_String_parseErrorLongText() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + try { + test.parseToBuilder("ONEXXX67890123456789012345678901234567890123456789012345678901234567890123456789"); + } catch (DateTimeParseException ex) { + assertEquals(ex.getMessage().contains("ONEXXX6789012345678901234567890123456789012345678901234567890123..."), true); + assertEquals(ex.getParsedString(), "ONEXXX67890123456789012345678901234567890123456789012345678901234567890123456789"); + assertEquals(ex.getErrorIndex(), 3); + throw ex; + } + } + + @Test(expectedExceptions=DateTimeParseException.class) + public void test_parseToBuilder_String_parseIncomplete() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + try { + test.parseToBuilder("ONE30SomethingElse"); + } catch (DateTimeParseException ex) { + assertEquals(ex.getMessage().contains("ONE30SomethingElse"), true); + assertEquals(ex.getParsedString(), "ONE30SomethingElse"); + assertEquals(ex.getErrorIndex(), 5); + throw ex; + } + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_parseToBuilder_String_null() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + test.parseToBuilder((String) null); + } + + //----------------------------------------------------------------------- + @Test + public void test_parseToBuilder_StringParsePosition() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder result = test.parseToBuilder("ONE30XXX", pos); + assertEquals(pos.getIndex(), 5); + assertEquals(pos.getErrorIndex(), -1); + assertEquals(result.getFieldValueMap().size(), 1); + assertEquals(result.getFieldValueMap().get(DAY_OF_MONTH), Long.valueOf(30)); + } + + @Test + public void test_parseToBuilder_StringParsePosition_parseError() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder result = test.parseToBuilder("ONEXXX", pos); + assertEquals(pos.getIndex(), 0); // TODO: is this right? + assertEquals(pos.getErrorIndex(), 3); + assertEquals(result, null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_parseToBuilder_StringParsePosition_nullString() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + ParsePosition pos = new ParsePosition(0); + test.parseToBuilder((String) null, pos); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_parseToBuilder_StringParsePosition_nullParsePosition() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + test.parseToBuilder("ONE30", (ParsePosition) null); + } + + @Test(expectedExceptions=IndexOutOfBoundsException.class) + public void test_parseToBuilder_StringParsePosition_invalidPosition() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + ParsePosition pos = new ParsePosition(6); + test.parseToBuilder("ONE30", pos); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @Test + public void test_toFormat_format() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + Format format = test.toFormat(); + String result = format.format(LocalDate.of(2008, 6, 30)); + assertEquals(result, "ONE30"); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_toFormat_format_null() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + Format format = test.toFormat(); + format.format(null); + } + + @Test(expectedExceptions=IllegalArgumentException.class) + public void test_toFormat_format_notTemporal() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + Format format = test.toFormat(); + format.format("Not a Temporal"); + } + + //----------------------------------------------------------------------- + @Test + public void test_toFormat_parseObject_String() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + Format format = test.toFormat(); + DateTimeBuilder result = (DateTimeBuilder) format.parseObject("ONE30"); + assertEquals(result.getFieldValueMap().size(), 1); + assertEquals(result.getFieldValue(DAY_OF_MONTH), 30L); + } + + @Test(expectedExceptions=ParseException.class) + public void test_toFormat_parseObject_String_parseError() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + Format format = test.toFormat(); + try { + format.parseObject("ONEXXX"); + } catch (ParseException ex) { + assertEquals(ex.getMessage().contains("ONEXXX"), true); + assertEquals(ex.getErrorOffset(), 3); + throw ex; + } + } + + @Test(expectedExceptions=ParseException.class) + public void test_toFormat_parseObject_String_parseErrorLongText() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + Format format = test.toFormat(); + try { + format.parseObject("ONEXXX67890123456789012345678901234567890123456789012345678901234567890123456789"); + } catch (DateTimeParseException ex) { + assertEquals(ex.getMessage().contains("ONEXXX6789012345678901234567890123456789012345678901234567890123..."), true); + assertEquals(ex.getParsedString(), "ONEXXX67890123456789012345678901234567890123456789012345678901234567890123456789"); + assertEquals(ex.getErrorIndex(), 3); + throw ex; + } + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_toFormat_parseObject_String_null() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + Format format = test.toFormat(); + format.parseObject((String) null); + } + + //----------------------------------------------------------------------- + @Test + public void test_toFormat_parseObject_StringParsePosition() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + Format format = test.toFormat(); + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder result = (DateTimeBuilder) format.parseObject("ONE30XXX", pos); + assertEquals(pos.getIndex(), 5); + assertEquals(pos.getErrorIndex(), -1); + assertEquals(result.getFieldValueMap().size(), 1); + assertEquals(result.getFieldValue(DAY_OF_MONTH), 30L); + } + + @Test + public void test_toFormat_parseObject_StringParsePosition_parseError() throws Exception { + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + Format format = test.toFormat(); + ParsePosition pos = new ParsePosition(0); + TemporalAccessor result = (TemporalAccessor) format.parseObject("ONEXXX", pos); + assertEquals(pos.getIndex(), 0); // TODO: is this right? + assertEquals(pos.getErrorIndex(), 3); + assertEquals(result, null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_toFormat_parseObject_StringParsePosition_nullString() throws Exception { + // SimpleDateFormat has this behavior + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + Format format = test.toFormat(); + ParsePosition pos = new ParsePosition(0); + format.parseObject((String) null, pos); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_toFormat_parseObject_StringParsePosition_nullParsePosition() throws Exception { + // SimpleDateFormat has this behavior + DateTimeFormatter test = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + Format format = test.toFormat(); + format.parseObject("ONE30", (ParsePosition) null); + } + + @Test + public void test_toFormat_parseObject_StringParsePosition_invalidPosition_tooBig() throws Exception { + // SimpleDateFormat has this behavior + DateTimeFormatter dtf = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + ParsePosition pos = new ParsePosition(6); + Format test = dtf.toFormat(); + assertNull(test.parseObject("ONE30", pos)); + assertTrue(pos.getErrorIndex() >= 0); + } + + @Test + public void test_toFormat_parseObject_StringParsePosition_invalidPosition_tooSmall() throws Exception { + // SimpleDateFormat throws StringIndexOutOfBoundException + DateTimeFormatter dtf = fmt.withLocale(Locale.ENGLISH).withSymbols(DateTimeFormatSymbols.STANDARD); + ParsePosition pos = new ParsePosition(-1); + Format test = dtf.toFormat(); + assertNull(test.parseObject("ONE30", pos)); + assertTrue(pos.getErrorIndex() >= 0); + } + + //----------------------------------------------------------------------- + @Test + public void test_toFormat_Query_format() throws Exception { + Format format = BASIC_FORMATTER.toFormat(); + String result = format.format(LocalDate.of(2008, 6, 30)); + assertEquals(result, "ONE30"); + } + + @Test + public void test_toFormat_Query_parseObject_String() throws Exception { + Format format = DATE_FORMATTER.toFormat(LocalDate::from); + LocalDate result = (LocalDate) format.parseObject("ONE2012 07 27"); + assertEquals(result, LocalDate.of(2012, 7, 27)); + } + + @Test(expectedExceptions=ParseException.class) + public void test_toFormat_parseObject_StringParsePosition_dateTimeError() throws Exception { + Format format = DATE_FORMATTER.toFormat(LocalDate::from); + format.parseObject("ONE2012 07 32"); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_toFormat_Query() throws Exception { + BASIC_FORMATTER.toFormat(null); + } + +} diff --git a/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java b/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..1fe79b165eb89ba5c84d192c0f22422a48995c3a --- /dev/null +++ b/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java @@ -0,0 +1,856 @@ +/* + * 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. + * + * 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 tck.java.time.format; + +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.format.*; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR; +import static org.testng.Assert.assertEquals; + +import java.text.ParsePosition; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import java.time.format.DateTimeBuilder; +import java.time.temporal.Temporal; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test DateTimeFormatterBuilder. + */ +@Test +public class TCKDateTimeFormatterBuilder { + + private DateTimeFormatterBuilder builder; + + @BeforeMethod(groups={"tck"}) + public void setUp() { + builder = new DateTimeFormatterBuilder(); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toFormatter_empty() throws Exception { + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), ""); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_parseCaseSensitive() throws Exception { + builder.parseCaseSensitive(); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "ParseCaseSensitive(true)"); + } + + @Test(groups={"tck"}) + public void test_parseCaseInsensitive() throws Exception { + builder.parseCaseInsensitive(); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "ParseCaseSensitive(false)"); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_parseStrict() throws Exception { + builder.parseStrict(); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "ParseStrict(true)"); + } + + @Test(groups={"tck"}) + public void test_parseLenient() throws Exception { + builder.parseLenient(); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "ParseStrict(false)"); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_appendValue_1arg() throws Exception { + builder.appendValue(DAY_OF_MONTH); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(DayOfMonth)"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_appendValue_1arg_null() throws Exception { + builder.appendValue(null); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_appendValue_2arg() throws Exception { + builder.appendValue(DAY_OF_MONTH, 3); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(DayOfMonth,3)"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_appendValue_2arg_null() throws Exception { + builder.appendValue(null, 3); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_appendValue_2arg_widthTooSmall() throws Exception { + builder.appendValue(DAY_OF_MONTH, 0); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_appendValue_2arg_widthTooBig() throws Exception { + builder.appendValue(DAY_OF_MONTH, 20); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_appendValue_3arg() throws Exception { + builder.appendValue(DAY_OF_MONTH, 2, 3, SignStyle.NORMAL); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(DayOfMonth,2,3,NORMAL)"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_appendValue_3arg_nullField() throws Exception { + builder.appendValue(null, 2, 3, SignStyle.NORMAL); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_appendValue_3arg_minWidthTooSmall() throws Exception { + builder.appendValue(DAY_OF_MONTH, 0, 2, SignStyle.NORMAL); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_appendValue_3arg_minWidthTooBig() throws Exception { + builder.appendValue(DAY_OF_MONTH, 20, 2, SignStyle.NORMAL); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_appendValue_3arg_maxWidthTooSmall() throws Exception { + builder.appendValue(DAY_OF_MONTH, 2, 0, SignStyle.NORMAL); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_appendValue_3arg_maxWidthTooBig() throws Exception { + builder.appendValue(DAY_OF_MONTH, 2, 20, SignStyle.NORMAL); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_appendValue_3arg_maxWidthMinWidth() throws Exception { + builder.appendValue(DAY_OF_MONTH, 4, 2, SignStyle.NORMAL); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_appendValue_3arg_nullSignStyle() throws Exception { + builder.appendValue(DAY_OF_MONTH, 2, 3, null); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_appendValue_subsequent2_parse3() throws Exception { + builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValue(DAY_OF_MONTH, 2); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)Value(DayOfMonth,2)"); + DateTimeBuilder cal = f.parseToBuilder("123", new ParsePosition(0)); + assertEquals(cal.getFieldValueMap().get(MONTH_OF_YEAR), Long.valueOf(1)); + assertEquals(cal.getFieldValueMap().get(DAY_OF_MONTH), Long.valueOf(23)); + } + + @Test(groups={"tck"}) + public void test_appendValue_subsequent2_parse4() throws Exception { + builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValue(DAY_OF_MONTH, 2); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)Value(DayOfMonth,2)"); + DateTimeBuilder cal = f.parseToBuilder("0123", new ParsePosition(0)); + assertEquals(cal.getFieldValueMap().get(MONTH_OF_YEAR), Long.valueOf(1)); + assertEquals(cal.getFieldValueMap().get(DAY_OF_MONTH), Long.valueOf(23)); + } + + @Test(groups={"tck"}) + public void test_appendValue_subsequent2_parse5() throws Exception { + builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValue(DAY_OF_MONTH, 2).appendLiteral('4'); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)Value(DayOfMonth,2)'4'"); + DateTimeBuilder cal = f.parseToBuilder("01234", new ParsePosition(0)); + assertEquals(cal.getFieldValueMap().get(MONTH_OF_YEAR), Long.valueOf(1)); + assertEquals(cal.getFieldValueMap().get(DAY_OF_MONTH), Long.valueOf(23)); + } + + @Test(groups={"tck"}) + public void test_appendValue_subsequent3_parse6() throws Exception { + builder + .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) + .appendValue(MONTH_OF_YEAR, 2) + .appendValue(DAY_OF_MONTH, 2); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(Year,4,10,EXCEEDS_PAD)Value(MonthOfYear,2)Value(DayOfMonth,2)"); + DateTimeBuilder cal = f.parseToBuilder("20090630", new ParsePosition(0)); + assertEquals(cal.getFieldValueMap().get(YEAR), Long.valueOf(2009)); + assertEquals(cal.getFieldValueMap().get(MONTH_OF_YEAR), Long.valueOf(6)); + assertEquals(cal.getFieldValueMap().get(DAY_OF_MONTH), Long.valueOf(30)); + } + + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_appendValueReduced_null() throws Exception { + builder.appendValueReduced(null, 2, 2000); + } + + @Test(groups={"tck"}) + public void test_appendValueReduced() throws Exception { + builder.appendValueReduced(YEAR, 2, 2000); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "ReducedValue(Year,2,2000)"); + DateTimeBuilder cal = f.parseToBuilder("12", new ParsePosition(0)); + assertEquals(cal.getFieldValueMap().get(YEAR), Long.valueOf(2012)); + } + + @Test(groups={"tck"}) + public void test_appendValueReduced_subsequent_parse() throws Exception { + builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValueReduced(YEAR, 2, 2000); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)ReducedValue(Year,2,2000)"); + DateTimeBuilder cal = f.parseToBuilder("123", new ParsePosition(0)); + assertEquals(cal.getFieldValueMap().get(MONTH_OF_YEAR), Long.valueOf(1)); + assertEquals(cal.getFieldValueMap().get(YEAR), Long.valueOf(2023)); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_appendFraction_4arg() throws Exception { + builder.appendFraction(MINUTE_OF_HOUR, 1, 9, false); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Fraction(MinuteOfHour,1,9)"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_appendFraction_4arg_nullRule() throws Exception { + builder.appendFraction(null, 1, 9, false); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_appendFraction_4arg_invalidRuleNotFixedSet() throws Exception { + builder.appendFraction(DAY_OF_MONTH, 1, 9, false); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_appendFraction_4arg_minTooSmall() throws Exception { + builder.appendFraction(MINUTE_OF_HOUR, -1, 9, false); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_appendFraction_4arg_minTooBig() throws Exception { + builder.appendFraction(MINUTE_OF_HOUR, 10, 9, false); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_appendFraction_4arg_maxTooSmall() throws Exception { + builder.appendFraction(MINUTE_OF_HOUR, 0, -1, false); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_appendFraction_4arg_maxTooBig() throws Exception { + builder.appendFraction(MINUTE_OF_HOUR, 1, 10, false); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_appendFraction_4arg_maxWidthMinWidth() throws Exception { + builder.appendFraction(MINUTE_OF_HOUR, 9, 3, false); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_appendText_1arg() throws Exception { + builder.appendText(MONTH_OF_YEAR); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Text(MonthOfYear)"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_appendText_1arg_null() throws Exception { + builder.appendText(null); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_appendText_2arg() throws Exception { + builder.appendText(MONTH_OF_YEAR, TextStyle.SHORT); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Text(MonthOfYear,SHORT)"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_appendText_2arg_nullRule() throws Exception { + builder.appendText(null, TextStyle.SHORT); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_appendText_2arg_nullStyle() throws Exception { + builder.appendText(MONTH_OF_YEAR, (TextStyle) null); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_appendTextMap() throws Exception { + Map map = new HashMap(); + map.put(1L, "JNY"); + map.put(2L, "FBY"); + map.put(3L, "MCH"); + map.put(4L, "APL"); + map.put(5L, "MAY"); + map.put(6L, "JUN"); + map.put(7L, "JLY"); + map.put(8L, "AGT"); + map.put(9L, "SPT"); + map.put(10L, "OBR"); + map.put(11L, "NVR"); + map.put(12L, "DBR"); + builder.appendText(MONTH_OF_YEAR, map); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Text(MonthOfYear)"); // TODO: toString should be different? + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_appendTextMap_nullRule() throws Exception { + builder.appendText(null, new HashMap()); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_appendTextMap_nullStyle() throws Exception { + builder.appendText(MONTH_OF_YEAR, (Map) null); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_appendOffsetId() throws Exception { + builder.appendOffsetId(); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Offset('Z',+HH:MM:ss)"); + } + + @DataProvider(name="offsetPatterns") + Object[][] data_offsetPatterns() { + return new Object[][] { + {"+HH", 2, 0, 0, "+02"}, + {"+HH", -2, 0, 0, "-02"}, + {"+HH", 2, 30, 0, "+02"}, + {"+HH", 2, 0, 45, "+02"}, + {"+HH", 2, 30, 45, "+02"}, + + {"+HHMM", 2, 0, 0, "+0200"}, + {"+HHMM", -2, 0, 0, "-0200"}, + {"+HHMM", 2, 30, 0, "+0230"}, + {"+HHMM", 2, 0, 45, "+0200"}, + {"+HHMM", 2, 30, 45, "+0230"}, + + {"+HH:MM", 2, 0, 0, "+02:00"}, + {"+HH:MM", -2, 0, 0, "-02:00"}, + {"+HH:MM", 2, 30, 0, "+02:30"}, + {"+HH:MM", 2, 0, 45, "+02:00"}, + {"+HH:MM", 2, 30, 45, "+02:30"}, + + {"+HHMMss", 2, 0, 0, "+0200"}, + {"+HHMMss", -2, 0, 0, "-0200"}, + {"+HHMMss", 2, 30, 0, "+0230"}, + {"+HHMMss", 2, 0, 45, "+020045"}, + {"+HHMMss", 2, 30, 45, "+023045"}, + + {"+HH:MM:ss", 2, 0, 0, "+02:00"}, + {"+HH:MM:ss", -2, 0, 0, "-02:00"}, + {"+HH:MM:ss", 2, 30, 0, "+02:30"}, + {"+HH:MM:ss", 2, 0, 45, "+02:00:45"}, + {"+HH:MM:ss", 2, 30, 45, "+02:30:45"}, + + {"+HHMMSS", 2, 0, 0, "+020000"}, + {"+HHMMSS", -2, 0, 0, "-020000"}, + {"+HHMMSS", 2, 30, 0, "+023000"}, + {"+HHMMSS", 2, 0, 45, "+020045"}, + {"+HHMMSS", 2, 30, 45, "+023045"}, + + {"+HH:MM:SS", 2, 0, 0, "+02:00:00"}, + {"+HH:MM:SS", -2, 0, 0, "-02:00:00"}, + {"+HH:MM:SS", 2, 30, 0, "+02:30:00"}, + {"+HH:MM:SS", 2, 0, 45, "+02:00:45"}, + {"+HH:MM:SS", 2, 30, 45, "+02:30:45"}, + }; + } + + @Test(dataProvider="offsetPatterns", groups={"tck"}) + public void test_appendOffset_print(String pattern, int h, int m, int s, String expected) throws Exception { + builder.appendOffset(pattern, "Z"); + DateTimeFormatter f = builder.toFormatter(); + ZoneOffset offset = ZoneOffset.ofHoursMinutesSeconds(h, m, s); + assertEquals(f.print(offset), expected); + } + + @Test(dataProvider="offsetPatterns", groups={"tck"}) + public void test_appendOffset_parse(String pattern, int h, int m, int s, String expected) throws Exception { + builder.appendOffset(pattern, "Z"); + DateTimeFormatter f = builder.toFormatter(); + ZoneOffset offset = ZoneOffset.ofHoursMinutesSeconds(h, m, s); + ZoneOffset parsed = f.parse(expected, ZoneOffset::from); + assertEquals(f.print(parsed), expected); + } + + @DataProvider(name="badOffsetPatterns") + Object[][] data_badOffsetPatterns() { + return new Object[][] { + {"HH"}, + {"HHMM"}, + {"HH:MM"}, + {"HHMMss"}, + {"HH:MM:ss"}, + {"HHMMSS"}, + {"HH:MM:SS"}, + {"+H"}, + {"+HMM"}, + {"+HHM"}, + {"+A"}, + }; + } + + @Test(dataProvider="badOffsetPatterns", expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_appendOffset_badPattern(String pattern) throws Exception { + builder.appendOffset(pattern, "Z"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_appendOffset_3arg_nullText() throws Exception { + builder.appendOffset("+HH:MM", null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_appendOffset_3arg_nullPattern() throws Exception { + builder.appendOffset(null, "Z"); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_appendZoneId() throws Exception { + builder.appendZoneId(); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "ZoneId()"); + } + + @Test(groups={"tck"}) + public void test_appendZoneText_1arg() throws Exception { + builder.appendZoneText(TextStyle.FULL); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "ZoneText(FULL)"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_appendZoneText_1arg_nullText() throws Exception { + builder.appendZoneText(null); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_padNext_1arg() throws Exception { + builder.appendValue(MONTH_OF_YEAR).padNext(2).appendValue(DAY_OF_MONTH).appendValue(DAY_OF_WEEK); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(MonthOfYear)Pad(Value(DayOfMonth),2)Value(DayOfWeek)"); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_padNext_1arg_invalidWidth() throws Exception { + builder.padNext(0); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_padNext_2arg_dash() throws Exception { + builder.appendValue(MONTH_OF_YEAR).padNext(2, '-').appendValue(DAY_OF_MONTH).appendValue(DAY_OF_WEEK); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(MonthOfYear)Pad(Value(DayOfMonth),2,'-')Value(DayOfWeek)"); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_padNext_2arg_invalidWidth() throws Exception { + builder.padNext(0, '-'); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_padOptional() throws Exception { + builder.appendValue(MONTH_OF_YEAR).padNext(5).optionalStart().appendValue(DAY_OF_MONTH).optionalEnd().appendValue(DAY_OF_WEEK); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(MonthOfYear)Pad([Value(DayOfMonth)],5)Value(DayOfWeek)"); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_optionalStart_noEnd() throws Exception { + builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH).appendValue(DAY_OF_WEEK); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)Value(DayOfWeek)]"); + } + + @Test(groups={"tck"}) + public void test_optionalStart2_noEnd() throws Exception { + builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH).optionalStart().appendValue(DAY_OF_WEEK); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)[Value(DayOfWeek)]]"); + } + + @Test(groups={"tck"}) + public void test_optionalStart_doubleStart() throws Exception { + builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalStart().appendValue(DAY_OF_MONTH); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(MonthOfYear)[[Value(DayOfMonth)]]"); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_optionalEnd() throws Exception { + builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH).optionalEnd().appendValue(DAY_OF_WEEK); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)]Value(DayOfWeek)"); + } + + @Test(groups={"tck"}) + public void test_optionalEnd2() throws Exception { + builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH) + .optionalStart().appendValue(DAY_OF_WEEK).optionalEnd().appendValue(DAY_OF_MONTH).optionalEnd(); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)[Value(DayOfWeek)]Value(DayOfMonth)]"); + } + + @Test(groups={"tck"}) + public void test_optionalEnd_doubleStartSingleEnd() throws Exception { + builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalStart().appendValue(DAY_OF_MONTH).optionalEnd(); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(MonthOfYear)[[Value(DayOfMonth)]]"); + } + + @Test(groups={"tck"}) + public void test_optionalEnd_doubleStartDoubleEnd() throws Exception { + builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalStart().appendValue(DAY_OF_MONTH).optionalEnd().optionalEnd(); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(MonthOfYear)[[Value(DayOfMonth)]]"); + } + + @Test(groups={"tck"}) + public void test_optionalStartEnd_immediateStartEnd() throws Exception { + builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalEnd().appendValue(DAY_OF_MONTH); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), "Value(MonthOfYear)Value(DayOfMonth)"); + } + + @Test(expectedExceptions=IllegalStateException.class, groups={"tck"}) + public void test_optionalEnd_noStart() throws Exception { + builder.optionalEnd(); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @DataProvider(name="validPatterns") + Object[][] dataValid() { + return new Object[][] { + {"'a'", "'a'"}, + {"''", "''"}, + {"'!'", "'!'"}, + {"!", "'!'"}, + + {"'hello_people,][)('", "'hello_people,][)('"}, + {"'hi'", "'hi'"}, + {"'yyyy'", "'yyyy'"}, + {"''''", "''"}, + {"'o''clock'", "'o''clock'"}, + + {"G", "Value(Era)"}, + {"GG", "Value(Era,2)"}, + {"GGG", "Text(Era,SHORT)"}, + {"GGGG", "Text(Era)"}, + {"GGGGG", "Text(Era,NARROW)"}, + + {"y", "Value(Year)"}, + {"yy", "ReducedValue(Year,2,2000)"}, + {"yyy", "Value(Year,3,19,NORMAL)"}, + {"yyyy", "Value(Year,4,19,EXCEEDS_PAD)"}, + {"yyyyy", "Value(Year,5,19,EXCEEDS_PAD)"}, + +// {"Y", "Value(WeekBasedYear)"}, +// {"YY", "ReducedValue(WeekBasedYear,2,2000)"}, +// {"YYY", "Value(WeekBasedYear,3,19,NORMAL)"}, +// {"YYYY", "Value(WeekBasedYear,4,19,EXCEEDS_PAD)"}, +// {"YYYYY", "Value(WeekBasedYear,5,19,EXCEEDS_PAD)"}, + + {"M", "Value(MonthOfYear)"}, + {"MM", "Value(MonthOfYear,2)"}, + {"MMM", "Text(MonthOfYear,SHORT)"}, + {"MMMM", "Text(MonthOfYear)"}, + {"MMMMM", "Text(MonthOfYear,NARROW)"}, + + {"D", "Value(DayOfYear)"}, + {"DD", "Value(DayOfYear,2)"}, + {"DDD", "Value(DayOfYear,3)"}, + + {"d", "Value(DayOfMonth)"}, + {"dd", "Value(DayOfMonth,2)"}, + {"ddd", "Value(DayOfMonth,3)"}, + + {"F", "Value(AlignedWeekOfMonth)"}, + {"FF", "Value(AlignedWeekOfMonth,2)"}, + {"FFF", "Value(AlignedWeekOfMonth,3)"}, + + {"Q", "Value(QuarterOfYear)"}, + {"QQ", "Value(QuarterOfYear,2)"}, + {"QQQ", "Text(QuarterOfYear,SHORT)"}, + {"QQQQ", "Text(QuarterOfYear)"}, + {"QQQQQ", "Text(QuarterOfYear,NARROW)"}, + + {"E", "Value(DayOfWeek)"}, + {"EE", "Value(DayOfWeek,2)"}, + {"EEE", "Text(DayOfWeek,SHORT)"}, + {"EEEE", "Text(DayOfWeek)"}, + {"EEEEE", "Text(DayOfWeek,NARROW)"}, + + {"a", "Text(AmPmOfDay,SHORT)"}, + {"aa", "Text(AmPmOfDay,SHORT)"}, + {"aaa", "Text(AmPmOfDay,SHORT)"}, + {"aaaa", "Text(AmPmOfDay)"}, + {"aaaaa", "Text(AmPmOfDay,NARROW)"}, + + {"H", "Value(HourOfDay)"}, + {"HH", "Value(HourOfDay,2)"}, + {"HHH", "Value(HourOfDay,3)"}, + + {"K", "Value(HourOfAmPm)"}, + {"KK", "Value(HourOfAmPm,2)"}, + {"KKK", "Value(HourOfAmPm,3)"}, + + {"k", "Value(ClockHourOfDay)"}, + {"kk", "Value(ClockHourOfDay,2)"}, + {"kkk", "Value(ClockHourOfDay,3)"}, + + {"h", "Value(ClockHourOfAmPm)"}, + {"hh", "Value(ClockHourOfAmPm,2)"}, + {"hhh", "Value(ClockHourOfAmPm,3)"}, + + {"m", "Value(MinuteOfHour)"}, + {"mm", "Value(MinuteOfHour,2)"}, + {"mmm", "Value(MinuteOfHour,3)"}, + + {"s", "Value(SecondOfMinute)"}, + {"ss", "Value(SecondOfMinute,2)"}, + {"sss", "Value(SecondOfMinute,3)"}, + + {"S", "Fraction(NanoOfSecond,1,1)"}, + {"SS", "Fraction(NanoOfSecond,2,2)"}, + {"SSS", "Fraction(NanoOfSecond,3,3)"}, + {"SSSSSSSSS", "Fraction(NanoOfSecond,9,9)"}, + + {"A", "Value(MilliOfDay)"}, + {"AA", "Value(MilliOfDay,2)"}, + {"AAA", "Value(MilliOfDay,3)"}, + + {"n", "Value(NanoOfSecond)"}, + {"nn", "Value(NanoOfSecond,2)"}, + {"nnn", "Value(NanoOfSecond,3)"}, + + {"N", "Value(NanoOfDay)"}, + {"NN", "Value(NanoOfDay,2)"}, + {"NNN", "Value(NanoOfDay,3)"}, + + {"z", "ZoneText(SHORT)"}, + {"zz", "ZoneText(SHORT)"}, + {"zzz", "ZoneText(SHORT)"}, + {"zzzz", "ZoneText(FULL)"}, + {"zzzzz", "ZoneText(FULL)"}, + + {"I", "ZoneId()"}, + {"II", "ZoneId()"}, + {"III", "ZoneId()"}, + {"IIII", "ZoneId()"}, + {"IIIII", "ZoneId()"}, + + {"Z", "Offset('+0000',+HHMM)"}, // SimpleDateFormat compatible + {"ZZ", "Offset('+0000',+HHMM)"}, + {"ZZZ", "Offset('+00:00',+HH:MM)"}, + + {"X", "Offset('Z',+HH)"}, + {"XX", "Offset('Z',+HHMM)"}, + {"XXX", "Offset('Z',+HH:MM)"}, + {"XXXX", "Offset('Z',+HHMMss)"}, + {"XXXXX", "Offset('Z',+HH:MM:ss)"}, + + {"ppH", "Pad(Value(HourOfDay),2)"}, + {"pppDD", "Pad(Value(DayOfYear,2),3)"}, + + {"yyyy[-MM[-dd", "Value(Year,4,19,EXCEEDS_PAD)['-'Value(MonthOfYear,2)['-'Value(DayOfMonth,2)]]"}, + {"yyyy[-MM[-dd]]", "Value(Year,4,19,EXCEEDS_PAD)['-'Value(MonthOfYear,2)['-'Value(DayOfMonth,2)]]"}, + {"yyyy[-MM[]-dd]", "Value(Year,4,19,EXCEEDS_PAD)['-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2)]"}, + + {"yyyy-MM-dd'T'HH:mm:ss.SSS", "Value(Year,4,19,EXCEEDS_PAD)'-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2)" + + "'T'Value(HourOfDay,2)':'Value(MinuteOfHour,2)':'Value(SecondOfMinute,2)'.'Fraction(NanoOfSecond,3,3)"}, + + {"e", "WeekBased(e1)"}, + {"w", "WeekBased(w1)"}, + {"W", "WeekBased(W1)"}, + {"WW", "WeekBased(W2)"}, + + }; + } + + @Test(dataProvider="validPatterns", groups={"implementation"}) + public void test_appendPattern_valid(String input, String expected) throws Exception { + builder.appendPattern(input); + DateTimeFormatter f = builder.toFormatter(); + assertEquals(f.toString(), expected); + } + + //----------------------------------------------------------------------- + @DataProvider(name="invalidPatterns") + Object[][] dataInvalid() { + return new Object[][] { + {"'"}, + {"'hello"}, + {"'hel''lo"}, + {"'hello''"}, + {"{"}, + {"}"}, + {"{}"}, + {"]"}, + {"yyyy]"}, + {"yyyy]MM"}, + {"yyyy[MM]]"}, + + {"MMMMMM"}, + {"QQQQQQ"}, + {"EEEEEE"}, + {"aaaaaa"}, + {"ZZZZ"}, + {"XXXXXX"}, + + {"RO"}, + + {"p"}, + {"pp"}, + {"p:"}, + + {"f"}, + {"ff"}, + {"f:"}, + {"fy"}, + {"fa"}, + {"fM"}, + + {"ww"}, + {"ee"}, + {"WWW"}, + }; + } + + @Test(dataProvider="invalidPatterns", expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_appendPattern_invalid(String input) throws Exception { + try { + builder.appendPattern(input); + } catch (IllegalArgumentException ex) { + throw ex; + } + } + + //----------------------------------------------------------------------- + @DataProvider(name="patternPrint") + Object[][] data_patternPrint() { + return new Object[][] { + {"Q", date(2012, 2, 10), "1"}, + {"QQ", date(2012, 2, 10), "01"}, +// {"QQQ", date(2012, 2, 10), "Q1"}, // TODO: data for quarters? +// {"QQQQ", date(2012, 2, 10), "Q1"}, +// {"QQQQQ", date(2012, 2, 10), "Q1"}, + }; + } + + @Test(dataProvider="patternPrint", groups={"tck"}) + public void test_appendPattern_patternPrint(String input, Temporal temporal, String expected) throws Exception { + DateTimeFormatter f = builder.appendPattern(input).toFormatter(Locale.UK); + String test = f.print(temporal); + assertEquals(test, expected); + } + + private static Temporal date(int y, int m, int d) { + return LocalDate.of(y, m, d); + } + +} diff --git a/test/java/time/tck/java/time/format/TCKDateTimeFormatters.java b/test/java/time/tck/java/time/format/TCKDateTimeFormatters.java new file mode 100644 index 0000000000000000000000000000000000000000..cbb79b981b9b16c2b2860fd67ba93f3402010965 --- /dev/null +++ b/test/java/time/tck/java/time/format/TCKDateTimeFormatters.java @@ -0,0 +1,1276 @@ +/* + * 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. + * + * 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 tck.java.time.format; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.OFFSET_SECONDS; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoField.YEAR; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.text.ParsePosition; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeBuilder; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.format.DateTimePrintException; +import java.time.temporal.ISOFields; +import java.time.temporal.Queries; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.Year; +import java.time.temporal.YearMonth; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test DateTimeFormatters. + */ +@Test +public class TCKDateTimeFormatters { + + @BeforeMethod + public void setUp() { + } + + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_print_nullCalendrical() { + DateTimeFormatters.isoDate().print((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_pattern_String() { + DateTimeFormatter test = DateTimeFormatters.pattern("d MMM yyyy"); + assertEquals(test.toString(), "Value(DayOfMonth)' 'Text(MonthOfYear,SHORT)' 'Value(Year,4,19,EXCEEDS_PAD)"); + assertEquals(test.getLocale(), Locale.getDefault()); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_pattern_String_invalid() { + DateTimeFormatters.pattern("p"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_pattern_String_null() { + DateTimeFormatters.pattern(null); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_pattern_StringLocale() { + DateTimeFormatter test = DateTimeFormatters.pattern("d MMM yyyy", Locale.UK); + assertEquals(test.toString(), "Value(DayOfMonth)' 'Text(MonthOfYear,SHORT)' 'Value(Year,4,19,EXCEEDS_PAD)"); + assertEquals(test.getLocale(), Locale.UK); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_pattern_StringLocale_invalid() { + DateTimeFormatters.pattern("p", Locale.UK); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_pattern_StringLocale_nullPattern() { + DateTimeFormatters.pattern(null, Locale.UK); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_pattern_StringLocale_nullLocale() { + DateTimeFormatters.pattern("yyyy", null); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @DataProvider(name="sample_isoLocalDate") + Object[][] provider_sample_isoLocalDate() { + return new Object[][]{ + {2008, null, null, null, null, null, DateTimeException.class}, + {null, 6, null, null, null, null, DateTimeException.class}, + {null, null, 30, null, null, null, DateTimeException.class}, + {null, null, null, "+01:00", null, null, DateTimeException.class}, + {null, null, null, null, "Europe/Paris", null, DateTimeException.class}, + {2008, 6, null, null, null, null, DateTimeException.class}, + {null, 6, 30, null, null, null, DateTimeException.class}, + + {2008, 6, 30, null, null, "2008-06-30", null}, + {2008, 6, 30, "+01:00", null, "2008-06-30", null}, + {2008, 6, 30, "+01:00", "Europe/Paris", "2008-06-30", null}, + {2008, 6, 30, null, "Europe/Paris", "2008-06-30", null}, + + {123456, 6, 30, null, null, "+123456-06-30", null}, + }; + } + + @Test(dataProvider="sample_isoLocalDate", groups={"tck"}) + public void test_print_isoLocalDate( + Integer year, Integer month, Integer day, String offsetId, String zoneId, + String expected, Class expectedEx) { + TemporalAccessor test = buildAccessor(year, month, day, null, null, null, null, offsetId, zoneId); + if (expectedEx == null) { + assertEquals(DateTimeFormatters.isoLocalDate().print(test), expected); + } else { + try { + DateTimeFormatters.isoLocalDate().print(test); + fail(); + } catch (Exception ex) { + assertTrue(expectedEx.isInstance(ex)); + } + } + } + + @Test(dataProvider="sample_isoLocalDate", groups={"tck"}) + public void test_parse_isoLocalDate( + Integer year, Integer month, Integer day, String offsetId, String zoneId, + String input, Class invalid) { + if (input != null) { + DateTimeBuilder expected = createDate(year, month, day); + // offset/zone not expected to be parsed + assertParseMatch(DateTimeFormatters.isoLocalDate().parseToBuilder(input, new ParsePosition(0)), expected); + } + } + + @Test(groups={"tck"}) + public void test_parse_isoLocalDate_999999999() { + DateTimeBuilder expected = createDate(999999999, 8, 6); + assertParseMatch(DateTimeFormatters.isoLocalDate().parseToBuilder("+999999999-08-06", new ParsePosition(0)), expected); + assertEquals(LocalDate.parse("+999999999-08-06"), LocalDate.of(999999999, 8, 6)); + } + + @Test(groups={"tck"}) + public void test_parse_isoLocalDate_1000000000() { + DateTimeBuilder expected = createDate(1000000000, 8, 6); + assertParseMatch(DateTimeFormatters.isoLocalDate().parseToBuilder("+1000000000-08-06", new ParsePosition(0)), expected); + } + + @Test(expectedExceptions = DateTimeException.class, groups={"tck"}) + public void test_parse_isoLocalDate_1000000000_failedCreate() { + LocalDate.parse("+1000000000-08-06"); + } + + @Test(groups={"tck"}) + public void test_parse_isoLocalDate_M999999999() { + DateTimeBuilder expected = createDate(-999999999, 8, 6); + assertParseMatch(DateTimeFormatters.isoLocalDate().parseToBuilder("-999999999-08-06", new ParsePosition(0)), expected); + assertEquals(LocalDate.parse("-999999999-08-06"), LocalDate.of(-999999999, 8, 6)); + } + + @Test(groups={"tck"}) + public void test_parse_isoLocalDate_M1000000000() { + DateTimeBuilder expected = createDate(-1000000000, 8, 6); + assertParseMatch(DateTimeFormatters.isoLocalDate().parseToBuilder("-1000000000-08-06", new ParsePosition(0)), expected); + } + + @Test(expectedExceptions = DateTimeException.class, groups={"tck"}) + public void test_parse_isoLocalDate_M1000000000_failedCreate() { + LocalDate.parse("-1000000000-08-06"); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @DataProvider(name="sample_isoOffsetDate") + Object[][] provider_sample_isoOffsetDate() { + return new Object[][]{ + {2008, null, null, null, null, null, DateTimeException.class}, + {null, 6, null, null, null, null, DateTimeException.class}, + {null, null, 30, null, null, null, DateTimeException.class}, + {null, null, null, "+01:00", null, null, DateTimeException.class}, + {null, null, null, null, "Europe/Paris", null, DateTimeException.class}, + {2008, 6, null, null, null, null, DateTimeException.class}, + {null, 6, 30, null, null, null, DateTimeException.class}, + + {2008, 6, 30, null, null, null, DateTimeException.class}, + {2008, 6, 30, "+01:00", null, "2008-06-30+01:00", null}, + {2008, 6, 30, "+01:00", "Europe/Paris", "2008-06-30+01:00", null}, + {2008, 6, 30, null, "Europe/Paris", null, DateTimeException.class}, + + {123456, 6, 30, "+01:00", null, "+123456-06-30+01:00", null}, + }; + } + + @Test(dataProvider="sample_isoOffsetDate", groups={"tck"}) + public void test_print_isoOffsetDate( + Integer year, Integer month, Integer day, String offsetId, String zoneId, + String expected, Class expectedEx) { + TemporalAccessor test = buildAccessor(year, month, day, null, null, null, null, offsetId, zoneId); + if (expectedEx == null) { + assertEquals(DateTimeFormatters.isoOffsetDate().print(test), expected); + } else { + try { + DateTimeFormatters.isoOffsetDate().print(test); + fail(); + } catch (Exception ex) { + assertTrue(expectedEx.isInstance(ex)); + } + } + } + + @Test(dataProvider="sample_isoOffsetDate", groups={"tck"}) + public void test_parse_isoOffsetDate( + Integer year, Integer month, Integer day, String offsetId, String zoneId, + String input, Class invalid) { + if (input != null) { + DateTimeBuilder expected = createDate(year, month, day); + buildCalendrical(expected, offsetId, null); // zone not expected to be parsed + assertParseMatch(DateTimeFormatters.isoOffsetDate().parseToBuilder(input, new ParsePosition(0)), expected); + } + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @DataProvider(name="sample_isoDate") + Object[][] provider_sample_isoDate() { + return new Object[][]{ + {2008, null, null, null, null, null, DateTimeException.class}, + {null, 6, null, null, null, null, DateTimeException.class}, + {null, null, 30, null, null, null, DateTimeException.class}, + {null, null, null, "+01:00", null, null, DateTimeException.class}, + {null, null, null, null, "Europe/Paris", null, DateTimeException.class}, + {2008, 6, null, null, null, null, DateTimeException.class}, + {null, 6, 30, null, null, null, DateTimeException.class}, + + {2008, 6, 30, null, null, "2008-06-30", null}, + {2008, 6, 30, "+01:00", null, "2008-06-30+01:00", null}, + {2008, 6, 30, "+01:00", "Europe/Paris", "2008-06-30+01:00", null}, + {2008, 6, 30, null, "Europe/Paris", "2008-06-30", null}, + + {123456, 6, 30, "+01:00", "Europe/Paris", "+123456-06-30+01:00", null}, + }; + } + + @Test(dataProvider="sample_isoDate", groups={"tck"}) + public void test_print_isoDate( + Integer year, Integer month, Integer day, String offsetId, String zoneId, + String expected, Class expectedEx) { + TemporalAccessor test = buildAccessor(year, month, day, null, null, null, null, offsetId, zoneId); + if (expectedEx == null) { + assertEquals(DateTimeFormatters.isoDate().print(test), expected); + } else { + try { + DateTimeFormatters.isoDate().print(test); + fail(); + } catch (Exception ex) { + assertTrue(expectedEx.isInstance(ex)); + } + } + } + + @Test(dataProvider="sample_isoDate", groups={"tck"}) + public void test_parse_isoDate( + Integer year, Integer month, Integer day, String offsetId, String zoneId, + String input, Class invalid) { + if (input != null) { + DateTimeBuilder expected = createDate(year, month, day); + if (offsetId != null) { + expected.addFieldValue(OFFSET_SECONDS, ZoneOffset.of(offsetId).getTotalSeconds()); + } + assertParseMatch(DateTimeFormatters.isoDate().parseToBuilder(input, new ParsePosition(0)), expected); + } + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @DataProvider(name="sample_isoLocalTime") + Object[][] provider_sample_isoLocalTime() { + return new Object[][]{ + {11, null, null, null, null, null, null, DateTimeException.class}, + {null, 5, null, null, null, null, null, DateTimeException.class}, + {null, null, 30, null, null, null, null, DateTimeException.class}, + {null, null, null, 1, null, null, null, DateTimeException.class}, + {null, null, null, null, "+01:00", null, null, DateTimeException.class}, + {null, null, null, null, null, "Europe/Paris", null, DateTimeException.class}, + + {11, 5, null, null, null, null, "11:05", null}, + {11, 5, 30, null, null, null, "11:05:30", null}, + {11, 5, 30, 500000000, null, null, "11:05:30.5", null}, + {11, 5, 30, 1, null, null, "11:05:30.000000001", null}, + + {11, 5, null, null, "+01:00", null, "11:05", null}, + {11, 5, 30, null, "+01:00", null, "11:05:30", null}, + {11, 5, 30, 500000000, "+01:00", null, "11:05:30.5", null}, + {11, 5, 30, 1, "+01:00", null, "11:05:30.000000001", null}, + + {11, 5, null, null, "+01:00", "Europe/Paris", "11:05", null}, + {11, 5, 30, null, "+01:00", "Europe/Paris", "11:05:30", null}, + {11, 5, 30, 500000000, "+01:00", "Europe/Paris", "11:05:30.5", null}, + {11, 5, 30, 1, "+01:00", "Europe/Paris", "11:05:30.000000001", null}, + + {11, 5, null, null, null, "Europe/Paris", "11:05", null}, + {11, 5, 30, null, null, "Europe/Paris", "11:05:30", null}, + {11, 5, 30, 500000000, null, "Europe/Paris", "11:05:30.5", null}, + {11, 5, 30, 1, null, "Europe/Paris", "11:05:30.000000001", null}, + }; + } + + @Test(dataProvider="sample_isoLocalTime", groups={"tck"}) + public void test_print_isoLocalTime( + Integer hour, Integer min, Integer sec, Integer nano, String offsetId, String zoneId, + String expected, Class expectedEx) { + TemporalAccessor test = buildAccessor(null, null, null, hour, min, sec, nano, offsetId, zoneId); + if (expectedEx == null) { + assertEquals(DateTimeFormatters.isoLocalTime().print(test), expected); + } else { + try { + DateTimeFormatters.isoLocalTime().print(test); + fail(); + } catch (Exception ex) { + assertTrue(expectedEx.isInstance(ex)); + } + } + } + + @Test(dataProvider="sample_isoLocalTime", groups={"tck"}) + public void test_parse_isoLocalTime( + Integer hour, Integer min, Integer sec, Integer nano, String offsetId, String zoneId, + String input, Class invalid) { + if (input != null) { + DateTimeBuilder expected = createTime(hour, min, sec, nano); + // offset/zone not expected to be parsed + assertParseMatch(DateTimeFormatters.isoLocalTime().parseToBuilder(input, new ParsePosition(0)), expected); + } + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @DataProvider(name="sample_isoOffsetTime") + Object[][] provider_sample_isoOffsetTime() { + return new Object[][]{ + {11, null, null, null, null, null, null, DateTimeException.class}, + {null, 5, null, null, null, null, null, DateTimeException.class}, + {null, null, 30, null, null, null, null, DateTimeException.class}, + {null, null, null, 1, null, null, null, DateTimeException.class}, + {null, null, null, null, "+01:00", null, null, DateTimeException.class}, + {null, null, null, null, null, "Europe/Paris", null, DateTimeException.class}, + + {11, 5, null, null, null, null, null, DateTimeException.class}, + {11, 5, 30, null, null, null, null, DateTimeException.class}, + {11, 5, 30, 500000000, null, null, null, DateTimeException.class}, + {11, 5, 30, 1, null, null, null, DateTimeException.class}, + + {11, 5, null, null, "+01:00", null, "11:05+01:00", null}, + {11, 5, 30, null, "+01:00", null, "11:05:30+01:00", null}, + {11, 5, 30, 500000000, "+01:00", null, "11:05:30.5+01:00", null}, + {11, 5, 30, 1, "+01:00", null, "11:05:30.000000001+01:00", null}, + + {11, 5, null, null, "+01:00", "Europe/Paris", "11:05+01:00", null}, + {11, 5, 30, null, "+01:00", "Europe/Paris", "11:05:30+01:00", null}, + {11, 5, 30, 500000000, "+01:00", "Europe/Paris", "11:05:30.5+01:00", null}, + {11, 5, 30, 1, "+01:00", "Europe/Paris", "11:05:30.000000001+01:00", null}, + + {11, 5, null, null, null, "Europe/Paris", null, DateTimeException.class}, + {11, 5, 30, null, null, "Europe/Paris", null, DateTimeException.class}, + {11, 5, 30, 500000000, null, "Europe/Paris", null, DateTimeException.class}, + {11, 5, 30, 1, null, "Europe/Paris", null, DateTimeException.class}, + }; + } + + @Test(dataProvider="sample_isoOffsetTime", groups={"tck"}) + public void test_print_isoOffsetTime( + Integer hour, Integer min, Integer sec, Integer nano, String offsetId, String zoneId, + String expected, Class expectedEx) { + TemporalAccessor test = buildAccessor(null, null, null, hour, min, sec, nano, offsetId, zoneId); + if (expectedEx == null) { + assertEquals(DateTimeFormatters.isoOffsetTime().print(test), expected); + } else { + try { + DateTimeFormatters.isoOffsetTime().print(test); + fail(); + } catch (Exception ex) { + assertTrue(expectedEx.isInstance(ex)); + } + } + } + + @Test(dataProvider="sample_isoOffsetTime", groups={"tck"}) + public void test_parse_isoOffsetTime( + Integer hour, Integer min, Integer sec, Integer nano, String offsetId, String zoneId, + String input, Class invalid) { + if (input != null) { + DateTimeBuilder expected = createTime(hour, min, sec, nano); + buildCalendrical(expected, offsetId, null); // zoneId is not expected from parse + assertParseMatch(DateTimeFormatters.isoOffsetTime().parseToBuilder(input, new ParsePosition(0)), expected); + } + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @DataProvider(name="sample_isoTime") + Object[][] provider_sample_isoTime() { + return new Object[][]{ + {11, null, null, null, null, null, null, DateTimeException.class}, + {null, 5, null, null, null, null, null, DateTimeException.class}, + {null, null, 30, null, null, null, null, DateTimeException.class}, + {null, null, null, 1, null, null, null, DateTimeException.class}, + {null, null, null, null, "+01:00", null, null, DateTimeException.class}, + {null, null, null, null, null, "Europe/Paris", null, DateTimeException.class}, + + {11, 5, null, null, null, null, "11:05", null}, + {11, 5, 30, null, null, null, "11:05:30", null}, + {11, 5, 30, 500000000, null, null, "11:05:30.5", null}, + {11, 5, 30, 1, null, null, "11:05:30.000000001", null}, + + {11, 5, null, null, "+01:00", null, "11:05+01:00", null}, + {11, 5, 30, null, "+01:00", null, "11:05:30+01:00", null}, + {11, 5, 30, 500000000, "+01:00", null, "11:05:30.5+01:00", null}, + {11, 5, 30, 1, "+01:00", null, "11:05:30.000000001+01:00", null}, + + {11, 5, null, null, "+01:00", "Europe/Paris", "11:05+01:00", null}, + {11, 5, 30, null, "+01:00", "Europe/Paris", "11:05:30+01:00", null}, + {11, 5, 30, 500000000, "+01:00", "Europe/Paris", "11:05:30.5+01:00", null}, + {11, 5, 30, 1, "+01:00", "Europe/Paris", "11:05:30.000000001+01:00", null}, + + {11, 5, null, null, null, "Europe/Paris", "11:05", null}, + {11, 5, 30, null, null, "Europe/Paris", "11:05:30", null}, + {11, 5, 30, 500000000, null, "Europe/Paris", "11:05:30.5", null}, + {11, 5, 30, 1, null, "Europe/Paris", "11:05:30.000000001", null}, + }; + } + + @Test(dataProvider="sample_isoTime", groups={"tck"}) + public void test_print_isoTime( + Integer hour, Integer min, Integer sec, Integer nano, String offsetId, String zoneId, + String expected, Class expectedEx) { + TemporalAccessor test = buildAccessor(null, null, null, hour, min, sec, nano, offsetId, zoneId); + if (expectedEx == null) { + assertEquals(DateTimeFormatters.isoTime().print(test), expected); + } else { + try { + DateTimeFormatters.isoTime().print(test); + fail(); + } catch (Exception ex) { + assertTrue(expectedEx.isInstance(ex)); + } + } + } + + @Test(dataProvider="sample_isoTime", groups={"tck"}) + public void test_parse_isoTime( + Integer hour, Integer min, Integer sec, Integer nano, String offsetId, String zoneId, + String input, Class invalid) { + if (input != null) { + DateTimeBuilder expected = createTime(hour, min, sec, nano); + if (offsetId != null) { + expected.addFieldValue(OFFSET_SECONDS, ZoneOffset.of(offsetId).getTotalSeconds()); + } + assertParseMatch(DateTimeFormatters.isoTime().parseToBuilder(input, new ParsePosition(0)), expected); + } + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @DataProvider(name="sample_isoLocalDateTime") + Object[][] provider_sample_isoLocalDateTime() { + return new Object[][]{ + {2008, null, null, null, null, null, null, null, null, null, DateTimeException.class}, + {null, 6, null, null, null, null, null, null, null, null, DateTimeException.class}, + {null, null, 30, null, null, null, null, null, null, null, DateTimeException.class}, + {null, null, null, 11, null, null, null, null, null, null, DateTimeException.class}, + {null, null, null, null, 5, null, null, null, null, null, DateTimeException.class}, + {null, null, null, null, null, null, null, "+01:00", null, null, DateTimeException.class}, + {null, null, null, null, null, null, null, null, "Europe/Paris", null, DateTimeException.class}, + {2008, 6, 30, 11, null, null, null, null, null, null, DateTimeException.class}, + {2008, 6, 30, null, 5, null, null, null, null, null, DateTimeException.class}, + {2008, 6, null, 11, 5, null, null, null, null, null, DateTimeException.class}, + {2008, null, 30, 11, 5, null, null, null, null, null, DateTimeException.class}, + {null, 6, 30, 11, 5, null, null, null, null, null, DateTimeException.class}, + + {2008, 6, 30, 11, 5, null, null, null, null, "2008-06-30T11:05", null}, + {2008, 6, 30, 11, 5, 30, null, null, null, "2008-06-30T11:05:30", null}, + {2008, 6, 30, 11, 5, 30, 500000000, null, null, "2008-06-30T11:05:30.5", null}, + {2008, 6, 30, 11, 5, 30, 1, null, null, "2008-06-30T11:05:30.000000001", null}, + + {2008, 6, 30, 11, 5, null, null, "+01:00", null, "2008-06-30T11:05", null}, + {2008, 6, 30, 11, 5, 30, null, "+01:00", null, "2008-06-30T11:05:30", null}, + {2008, 6, 30, 11, 5, 30, 500000000, "+01:00", null, "2008-06-30T11:05:30.5", null}, + {2008, 6, 30, 11, 5, 30, 1, "+01:00", null, "2008-06-30T11:05:30.000000001", null}, + + {2008, 6, 30, 11, 5, null, null, "+01:00", "Europe/Paris", "2008-06-30T11:05", null}, + {2008, 6, 30, 11, 5, 30, null, "+01:00", "Europe/Paris", "2008-06-30T11:05:30", null}, + {2008, 6, 30, 11, 5, 30, 500000000, "+01:00", "Europe/Paris", "2008-06-30T11:05:30.5", null}, + {2008, 6, 30, 11, 5, 30, 1, "+01:00", "Europe/Paris", "2008-06-30T11:05:30.000000001", null}, + + {2008, 6, 30, 11, 5, null, null, null, "Europe/Paris", "2008-06-30T11:05", null}, + {2008, 6, 30, 11, 5, 30, null, null, "Europe/Paris", "2008-06-30T11:05:30", null}, + {2008, 6, 30, 11, 5, 30, 500000000, null, "Europe/Paris", "2008-06-30T11:05:30.5", null}, + {2008, 6, 30, 11, 5, 30, 1, null, "Europe/Paris", "2008-06-30T11:05:30.000000001", null}, + + {123456, 6, 30, 11, 5, null, null, null, null, "+123456-06-30T11:05", null}, + }; + } + + @Test(dataProvider="sample_isoLocalDateTime", groups={"tck"}) + public void test_print_isoLocalDateTime( + Integer year, Integer month, Integer day, + Integer hour, Integer min, Integer sec, Integer nano, String offsetId, String zoneId, + String expected, Class expectedEx) { + TemporalAccessor test = buildAccessor(year, month, day, hour, min, sec, nano, offsetId, zoneId); + if (expectedEx == null) { + assertEquals(DateTimeFormatters.isoLocalDateTime().print(test), expected); + } else { + try { + DateTimeFormatters.isoLocalDateTime().print(test); + fail(); + } catch (Exception ex) { + assertTrue(expectedEx.isInstance(ex)); + } + } + } + + @Test(dataProvider="sample_isoLocalDateTime", groups={"tck"}) + public void test_parse_isoLocalDateTime( + Integer year, Integer month, Integer day, + Integer hour, Integer min, Integer sec, Integer nano, String offsetId, String zoneId, + String input, Class invalid) { + if (input != null) { + DateTimeBuilder expected = createDateTime(year, month, day, hour, min, sec, nano); + assertParseMatch(DateTimeFormatters.isoLocalDateTime().parseToBuilder(input, new ParsePosition(0)), expected); + } + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @DataProvider(name="sample_isoOffsetDateTime") + Object[][] provider_sample_isoOffsetDateTime() { + return new Object[][]{ + {2008, null, null, null, null, null, null, null, null, null, DateTimeException.class}, + {null, 6, null, null, null, null, null, null, null, null, DateTimeException.class}, + {null, null, 30, null, null, null, null, null, null, null, DateTimeException.class}, + {null, null, null, 11, null, null, null, null, null, null, DateTimeException.class}, + {null, null, null, null, 5, null, null, null, null, null, DateTimeException.class}, + {null, null, null, null, null, null, null, "+01:00", null, null, DateTimeException.class}, + {null, null, null, null, null, null, null, null, "Europe/Paris", null, DateTimeException.class}, + {2008, 6, 30, 11, null, null, null, null, null, null, DateTimeException.class}, + {2008, 6, 30, null, 5, null, null, null, null, null, DateTimeException.class}, + {2008, 6, null, 11, 5, null, null, null, null, null, DateTimeException.class}, + {2008, null, 30, 11, 5, null, null, null, null, null, DateTimeException.class}, + {null, 6, 30, 11, 5, null, null, null, null, null, DateTimeException.class}, + + {2008, 6, 30, 11, 5, null, null, null, null, null, DateTimeException.class}, + {2008, 6, 30, 11, 5, 30, null, null, null, null, DateTimeException.class}, + {2008, 6, 30, 11, 5, 30, 500000000, null, null, null, DateTimeException.class}, + {2008, 6, 30, 11, 5, 30, 1, null, null, null, DateTimeException.class}, + + {2008, 6, 30, 11, 5, null, null, "+01:00", null, "2008-06-30T11:05+01:00", null}, + {2008, 6, 30, 11, 5, 30, null, "+01:00", null, "2008-06-30T11:05:30+01:00", null}, + {2008, 6, 30, 11, 5, 30, 500000000, "+01:00", null, "2008-06-30T11:05:30.5+01:00", null}, + {2008, 6, 30, 11, 5, 30, 1, "+01:00", null, "2008-06-30T11:05:30.000000001+01:00", null}, + + {2008, 6, 30, 11, 5, null, null, "+01:00", "Europe/Paris", "2008-06-30T11:05+01:00", null}, + {2008, 6, 30, 11, 5, 30, null, "+01:00", "Europe/Paris", "2008-06-30T11:05:30+01:00", null}, + {2008, 6, 30, 11, 5, 30, 500000000, "+01:00", "Europe/Paris", "2008-06-30T11:05:30.5+01:00", null}, + {2008, 6, 30, 11, 5, 30, 1, "+01:00", "Europe/Paris", "2008-06-30T11:05:30.000000001+01:00", null}, + + {2008, 6, 30, 11, 5, null, null, null, "Europe/Paris", null, DateTimeException.class}, + {2008, 6, 30, 11, 5, 30, null, null, "Europe/Paris", null, DateTimeException.class}, + {2008, 6, 30, 11, 5, 30, 500000000, null, "Europe/Paris", null, DateTimeException.class}, + {2008, 6, 30, 11, 5, 30, 1, null, "Europe/Paris", null, DateTimeException.class}, + + {123456, 6, 30, 11, 5, null, null, "+01:00", null, "+123456-06-30T11:05+01:00", null}, + }; + } + + @Test(dataProvider="sample_isoOffsetDateTime", groups={"tck"}) + public void test_print_isoOffsetDateTime( + Integer year, Integer month, Integer day, + Integer hour, Integer min, Integer sec, Integer nano, String offsetId, String zoneId, + String expected, Class expectedEx) { + TemporalAccessor test = buildAccessor(year, month, day, hour, min, sec, nano, offsetId, zoneId); + if (expectedEx == null) { + assertEquals(DateTimeFormatters.isoOffsetDateTime().print(test), expected); + } else { + try { + DateTimeFormatters.isoOffsetDateTime().print(test); + fail(); + } catch (Exception ex) { + assertTrue(expectedEx.isInstance(ex)); + } + } + } + + @Test(dataProvider="sample_isoOffsetDateTime", groups={"tck"}) + public void test_parse_isoOffsetDateTime( + Integer year, Integer month, Integer day, + Integer hour, Integer min, Integer sec, Integer nano, String offsetId, String zoneId, + String input, Class invalid) { + if (input != null) { + DateTimeBuilder expected = createDateTime(year, month, day, hour, min, sec, nano); + buildCalendrical(expected, offsetId, null); // zone not expected to be parsed + assertParseMatch(DateTimeFormatters.isoOffsetDateTime().parseToBuilder(input, new ParsePosition(0)), expected); + } + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @DataProvider(name="sample_isoZonedDateTime") + Object[][] provider_sample_isoZonedDateTime() { + return new Object[][]{ + {2008, null, null, null, null, null, null, null, null, null, DateTimeException.class}, + {null, 6, null, null, null, null, null, null, null, null, DateTimeException.class}, + {null, null, 30, null, null, null, null, null, null, null, DateTimeException.class}, + {null, null, null, 11, null, null, null, null, null, null, DateTimeException.class}, + {null, null, null, null, 5, null, null, null, null, null, DateTimeException.class}, + {null, null, null, null, null, null, null, "+01:00", null, null, DateTimeException.class}, + {null, null, null, null, null, null, null, null, "Europe/Paris", null, DateTimeException.class}, + {2008, 6, 30, 11, null, null, null, null, null, null, DateTimeException.class}, + {2008, 6, 30, null, 5, null, null, null, null, null, DateTimeException.class}, + {2008, 6, null, 11, 5, null, null, null, null, null, DateTimeException.class}, + {2008, null, 30, 11, 5, null, null, null, null, null, DateTimeException.class}, + {null, 6, 30, 11, 5, null, null, null, null, null, DateTimeException.class}, + + {2008, 6, 30, 11, 5, null, null, null, null, null, DateTimeException.class}, + {2008, 6, 30, 11, 5, 30, null, null, null, null, DateTimeException.class}, + {2008, 6, 30, 11, 5, 30, 500000000, null, null, null, DateTimeException.class}, + {2008, 6, 30, 11, 5, 30, 1, null, null, null, DateTimeException.class}, + + // allow OffsetDateTime (no harm comes of this AFAICT) + {2008, 6, 30, 11, 5, null, null, "+01:00", null, "2008-06-30T11:05+01:00", null}, + {2008, 6, 30, 11, 5, 30, null, "+01:00", null, "2008-06-30T11:05:30+01:00", null}, + {2008, 6, 30, 11, 5, 30, 500000000, "+01:00", null, "2008-06-30T11:05:30.5+01:00", null}, + {2008, 6, 30, 11, 5, 30, 1, "+01:00", null, "2008-06-30T11:05:30.000000001+01:00", null}, + + // ZonedDateTime with ZoneId of ZoneOffset + {2008, 6, 30, 11, 5, null, null, "+01:00", "+01:00", "2008-06-30T11:05+01:00", null}, + {2008, 6, 30, 11, 5, 30, null, "+01:00", "+01:00", "2008-06-30T11:05:30+01:00", null}, + {2008, 6, 30, 11, 5, 30, 500000000, "+01:00", "+01:00", "2008-06-30T11:05:30.5+01:00", null}, + {2008, 6, 30, 11, 5, 30, 1, "+01:00", "+01:00", "2008-06-30T11:05:30.000000001+01:00", null}, + + // ZonedDateTime with ZoneId of ZoneRegion + {2008, 6, 30, 11, 5, null, null, "+01:00", "Europe/Paris", "2008-06-30T11:05+01:00[Europe/Paris]", null}, + {2008, 6, 30, 11, 5, 30, null, "+01:00", "Europe/Paris", "2008-06-30T11:05:30+01:00[Europe/Paris]", null}, + {2008, 6, 30, 11, 5, 30, 500000000, "+01:00", "Europe/Paris", "2008-06-30T11:05:30.5+01:00[Europe/Paris]", null}, + {2008, 6, 30, 11, 5, 30, 1, "+01:00", "Europe/Paris", "2008-06-30T11:05:30.000000001+01:00[Europe/Paris]", null}, + + // offset required + {2008, 6, 30, 11, 5, null, null, null, "Europe/Paris", null, DateTimeException.class}, + {2008, 6, 30, 11, 5, 30, null, null, "Europe/Paris", null, DateTimeException.class}, + {2008, 6, 30, 11, 5, 30, 500000000, null, "Europe/Paris", null, DateTimeException.class}, + {2008, 6, 30, 11, 5, 30, 1, null, "Europe/Paris", null, DateTimeException.class}, + + {123456, 6, 30, 11, 5, null, null, "+01:00", "Europe/Paris", "+123456-06-30T11:05+01:00[Europe/Paris]", null}, + }; + } + + @Test(dataProvider="sample_isoZonedDateTime", groups={"tck"}) + public void test_print_isoZonedDateTime( + Integer year, Integer month, Integer day, + Integer hour, Integer min, Integer sec, Integer nano, String offsetId, String zoneId, + String expected, Class expectedEx) { + TemporalAccessor test = buildAccessor(year, month, day, hour, min, sec, nano, offsetId, zoneId); + if (expectedEx == null) { + assertEquals(DateTimeFormatters.isoZonedDateTime().print(test), expected); + } else { + try { + DateTimeFormatters.isoZonedDateTime().print(test); + fail(test.toString()); + } catch (Exception ex) { + assertTrue(expectedEx.isInstance(ex)); + } + } + } + + @Test(dataProvider="sample_isoZonedDateTime", groups={"tck"}) + public void test_parse_isoZonedDateTime( + Integer year, Integer month, Integer day, + Integer hour, Integer min, Integer sec, Integer nano, String offsetId, String zoneId, + String input, Class invalid) { + if (input != null) { + DateTimeBuilder expected = createDateTime(year, month, day, hour, min, sec, nano); + if (offsetId.equals(zoneId)) { + buildCalendrical(expected, offsetId, null); + } else { + buildCalendrical(expected, offsetId, zoneId); + } + assertParseMatch(DateTimeFormatters.isoZonedDateTime().parseToBuilder(input, new ParsePosition(0)), expected); + } + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @DataProvider(name="sample_isoDateTime") + Object[][] provider_sample_isoDateTime() { + return new Object[][]{ + {2008, null, null, null, null, null, null, null, null, null, DateTimeException.class}, + {null, 6, null, null, null, null, null, null, null, null, DateTimeException.class}, + {null, null, 30, null, null, null, null, null, null, null, DateTimeException.class}, + {null, null, null, 11, null, null, null, null, null, null, DateTimeException.class}, + {null, null, null, null, 5, null, null, null, null, null, DateTimeException.class}, + {null, null, null, null, null, null, null, "+01:00", null, null, DateTimeException.class}, + {null, null, null, null, null, null, null, null, "Europe/Paris", null, DateTimeException.class}, + {2008, 6, 30, 11, null, null, null, null, null, null, DateTimeException.class}, + {2008, 6, 30, null, 5, null, null, null, null, null, DateTimeException.class}, + {2008, 6, null, 11, 5, null, null, null, null, null, DateTimeException.class}, + {2008, null, 30, 11, 5, null, null, null, null, null, DateTimeException.class}, + {null, 6, 30, 11, 5, null, null, null, null, null, DateTimeException.class}, + + {2008, 6, 30, 11, 5, null, null, null, null, "2008-06-30T11:05", null}, + {2008, 6, 30, 11, 5, 30, null, null, null, "2008-06-30T11:05:30", null}, + {2008, 6, 30, 11, 5, 30, 500000000, null, null, "2008-06-30T11:05:30.5", null}, + {2008, 6, 30, 11, 5, 30, 1, null, null, "2008-06-30T11:05:30.000000001", null}, + + {2008, 6, 30, 11, 5, null, null, "+01:00", null, "2008-06-30T11:05+01:00", null}, + {2008, 6, 30, 11, 5, 30, null, "+01:00", null, "2008-06-30T11:05:30+01:00", null}, + {2008, 6, 30, 11, 5, 30, 500000000, "+01:00", null, "2008-06-30T11:05:30.5+01:00", null}, + {2008, 6, 30, 11, 5, 30, 1, "+01:00", null, "2008-06-30T11:05:30.000000001+01:00", null}, + + {2008, 6, 30, 11, 5, null, null, "+01:00", "Europe/Paris", "2008-06-30T11:05+01:00[Europe/Paris]", null}, + {2008, 6, 30, 11, 5, 30, null, "+01:00", "Europe/Paris", "2008-06-30T11:05:30+01:00[Europe/Paris]", null}, + {2008, 6, 30, 11, 5, 30, 500000000, "+01:00", "Europe/Paris", "2008-06-30T11:05:30.5+01:00[Europe/Paris]", null}, + {2008, 6, 30, 11, 5, 30, 1, "+01:00", "Europe/Paris", "2008-06-30T11:05:30.000000001+01:00[Europe/Paris]", null}, + + {2008, 6, 30, 11, 5, null, null, null, "Europe/Paris", "2008-06-30T11:05", null}, + {2008, 6, 30, 11, 5, 30, null, null, "Europe/Paris", "2008-06-30T11:05:30", null}, + {2008, 6, 30, 11, 5, 30, 500000000, null, "Europe/Paris", "2008-06-30T11:05:30.5", null}, + {2008, 6, 30, 11, 5, 30, 1, null, "Europe/Paris", "2008-06-30T11:05:30.000000001", null}, + + {123456, 6, 30, 11, 5, null, null, null, null, "+123456-06-30T11:05", null}, + }; + } + + @Test(dataProvider="sample_isoDateTime", groups={"tck"}) + public void test_print_isoDateTime( + Integer year, Integer month, Integer day, + Integer hour, Integer min, Integer sec, Integer nano, String offsetId, String zoneId, + String expected, Class expectedEx) { + TemporalAccessor test = buildAccessor(year, month, day, hour, min, sec, nano, offsetId, zoneId); + if (expectedEx == null) { + assertEquals(DateTimeFormatters.isoDateTime().print(test), expected); + } else { + try { + DateTimeFormatters.isoDateTime().print(test); + fail(); + } catch (Exception ex) { + assertTrue(expectedEx.isInstance(ex)); + } + } + } + + @Test(dataProvider="sample_isoDateTime", groups={"tck"}) + public void test_parse_isoDateTime( + Integer year, Integer month, Integer day, + Integer hour, Integer min, Integer sec, Integer nano, String offsetId, String zoneId, + String input, Class invalid) { + if (input != null) { + DateTimeBuilder expected = createDateTime(year, month, day, hour, min, sec, nano); + if (offsetId != null) { + expected.addFieldValue(OFFSET_SECONDS, ZoneOffset.of(offsetId).getTotalSeconds()); + if (zoneId != null) { + expected.addCalendrical(ZoneId.of(zoneId)); + } + } + assertParseMatch(DateTimeFormatters.isoDateTime().parseToBuilder(input, new ParsePosition(0)), expected); + } + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_print_isoOrdinalDate() { + TemporalAccessor test = buildAccessor(LocalDateTime.of(2008, 6, 3, 11, 5, 30), null, null); + assertEquals(DateTimeFormatters.isoOrdinalDate().print(test), "2008-155"); + } + + @Test(groups={"tck"}) + public void test_print_isoOrdinalDate_offset() { + TemporalAccessor test = buildAccessor(LocalDateTime.of(2008, 6, 3, 11, 5, 30), "Z", null); + assertEquals(DateTimeFormatters.isoOrdinalDate().print(test), "2008-155Z"); + } + + @Test(groups={"tck"}) + public void test_print_isoOrdinalDate_zoned() { + TemporalAccessor test = buildAccessor(LocalDateTime.of(2008, 6, 3, 11, 5, 30), "+02:00", "Europe/Paris"); + assertEquals(DateTimeFormatters.isoOrdinalDate().print(test), "2008-155+02:00"); + } + + @Test(groups={"tck"}) + public void test_print_isoOrdinalDate_zoned_largeYear() { + TemporalAccessor test = buildAccessor(LocalDateTime.of(123456, 6, 3, 11, 5, 30), "Z", null); + assertEquals(DateTimeFormatters.isoOrdinalDate().print(test), "+123456-155Z"); + } + + @Test(groups={"tck"}) + public void test_print_isoOrdinalDate_fields() { + TemporalAccessor test = new DateTimeBuilder(YEAR, 2008).addFieldValue(DAY_OF_YEAR, 231); + assertEquals(DateTimeFormatters.isoOrdinalDate().print(test), "2008-231"); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_print_isoOrdinalDate_missingField() { + TemporalAccessor test = Year.of(2008); + DateTimeFormatters.isoOrdinalDate().print(test); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_parse_isoOrdinalDate() { + DateTimeBuilder expected = new DateTimeBuilder(YEAR, 2008).addFieldValue(DAY_OF_YEAR, 123); + assertParseMatch(DateTimeFormatters.isoOrdinalDate().parseToBuilder("2008-123", new ParsePosition(0)), expected); + } + + @Test(groups={"tck"}) + public void test_parse_isoOrdinalDate_largeYear() { + DateTimeBuilder expected = new DateTimeBuilder(YEAR, 123456).addFieldValue(DAY_OF_YEAR, 123); + assertParseMatch(DateTimeFormatters.isoOrdinalDate().parseToBuilder("+123456-123", new ParsePosition(0)), expected); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_print_basicIsoDate() { + TemporalAccessor test = buildAccessor(LocalDateTime.of(2008, 6, 3, 11, 5, 30), null, null); + assertEquals(DateTimeFormatters.basicIsoDate().print(test), "20080603"); + } + + @Test(groups={"tck"}) + public void test_print_basicIsoDate_offset() { + TemporalAccessor test = buildAccessor(LocalDateTime.of(2008, 6, 3, 11, 5, 30), "Z", null); + assertEquals(DateTimeFormatters.basicIsoDate().print(test), "20080603Z"); + } + + @Test(groups={"tck"}) + public void test_print_basicIsoDate_zoned() { + TemporalAccessor test = buildAccessor(LocalDateTime.of(2008, 6, 3, 11, 5, 30), "+02:00", "Europe/Paris"); + assertEquals(DateTimeFormatters.basicIsoDate().print(test), "20080603+0200"); + } + + @Test(expectedExceptions=DateTimePrintException.class, groups={"tck"}) + public void test_print_basicIsoDate_largeYear() { + TemporalAccessor test = buildAccessor(LocalDateTime.of(123456, 6, 3, 11, 5, 30), "Z", null); + DateTimeFormatters.basicIsoDate().print(test); + } + + @Test(groups={"tck"}) + public void test_print_basicIsoDate_fields() { + TemporalAccessor test = buildAccessor(LocalDate.of(2008, 6, 3), null, null); + assertEquals(DateTimeFormatters.basicIsoDate().print(test), "20080603"); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_print_basicIsoDate_missingField() { + TemporalAccessor test = YearMonth.of(2008, 6); + DateTimeFormatters.basicIsoDate().print(test); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_parse_basicIsoDate() { + LocalDate expected = LocalDate.of(2008, 6, 3); + assertEquals(DateTimeFormatters.basicIsoDate().parse("20080603", LocalDate::from), expected); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void test_parse_basicIsoDate_largeYear() { + try { + LocalDate expected = LocalDate.of(123456, 6, 3); + assertEquals(DateTimeFormatters.basicIsoDate().parse("+1234560603", LocalDate::from), expected); + } catch (DateTimeParseException ex) { + assertEquals(ex.getErrorIndex(), 0); + assertEquals(ex.getParsedString(), "+1234560603"); + throw ex; + } + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @DataProvider(name="weekDate") + Iterator weekDate() { + return new Iterator() { + private ZonedDateTime date = ZonedDateTime.of(LocalDateTime.of(2003, 12, 29, 11, 5, 30), ZoneId.of("Europe/Paris")); + private ZonedDateTime endDate = date.withYear(2005).withMonth(1).withDayOfMonth(2); + private int week = 1; + private int day = 1; + + public boolean hasNext() { + return !date.isAfter(endDate); + } + public Object[] next() { + StringBuilder sb = new StringBuilder("2004-W"); + if (week < 10) { + sb.append('0'); + } + sb.append(week).append('-').append(day).append(date.getOffset()); + Object[] ret = new Object[] {date, sb.toString()}; + date = date.plusDays(1); + day += 1; + if (day == 8) { + day = 1; + week++; + } + return ret; + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Test(dataProvider="weekDate", groups={"tck"}) + public void test_print_isoWeekDate(TemporalAccessor test, String expected) { + assertEquals(DateTimeFormatters.isoWeekDate().print(test), expected); + } + + @Test(groups={"tck"}) + public void test_print_isoWeekDate_zoned_largeYear() { + TemporalAccessor test = buildAccessor(LocalDateTime.of(123456, 6, 3, 11, 5, 30), "Z", null); + assertEquals(DateTimeFormatters.isoWeekDate().print(test), "+123456-W23-2Z"); + } + + @Test(groups={"tck"}) + public void test_print_isoWeekDate_fields() { + TemporalAccessor test = buildAccessor(LocalDate.of(2004, 1, 27), null, null); + assertEquals(DateTimeFormatters.isoWeekDate().print(test), "2004-W05-2"); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_print_isoWeekDate_missingField() { + TemporalAccessor test = YearMonth.of(2008, 6); + DateTimeFormatters.isoWeekDate().print(test); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_parse_weekDate() { + LocalDate expected = LocalDate.of(2004, 1, 28); + assertEquals(DateTimeFormatters.isoWeekDate().parse("2004-W05-3", LocalDate::from), expected); + } + + @Test(groups={"tck"}) + public void test_parse_weekDate_largeYear() { + DateTimeBuilder builder = DateTimeFormatters.isoWeekDate().parseToBuilder("+123456-W04-5", new ParsePosition(0)); + assertEquals(builder.getFieldValue(ISOFields.WEEK_BASED_YEAR), 123456); + assertEquals(builder.getFieldValue(ISOFields.WEEK_OF_WEEK_BASED_YEAR), 4); + assertEquals(builder.getFieldValue(DAY_OF_WEEK), 5); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @DataProvider(name="rfc") + Object[][] data_rfc() { + return new Object[][] { + {LocalDateTime.of(2008, 6, 3, 11, 5, 30), "Z", "Tue, 3 Jun 2008 11:05:30 GMT"}, + {LocalDateTime.of(2008, 6, 30, 11, 5, 30), "Z", "Mon, 30 Jun 2008 11:05:30 GMT"}, + {LocalDateTime.of(2008, 6, 3, 11, 5, 30), "+02:00", "Tue, 3 Jun 2008 11:05:30 +0200"}, + {LocalDateTime.of(2008, 6, 30, 11, 5, 30), "-03:00", "Mon, 30 Jun 2008 11:05:30 -0300"}, + }; + } + + @Test(groups={"tck"}, dataProvider="rfc") + public void test_print_rfc1123(LocalDateTime base, String offsetId, String expected) { + TemporalAccessor test = buildAccessor(base, offsetId, null); + assertEquals(DateTimeFormatters.rfc1123().print(test), expected); + } + + @Test(groups={"tck"}, dataProvider="rfc") + public void test_print_rfc1123_french(LocalDateTime base, String offsetId, String expected) { + TemporalAccessor test = buildAccessor(base, offsetId, null); + assertEquals(DateTimeFormatters.rfc1123().withLocale(Locale.FRENCH).print(test), expected); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_print_rfc1123_missingField() { + TemporalAccessor test = YearMonth.of(2008, 6); + DateTimeFormatters.rfc1123().print(test); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + private DateTimeBuilder createDate(Integer year, Integer month, Integer day) { + DateTimeBuilder test = new DateTimeBuilder(); + if (year != null) { + test.addFieldValue(YEAR, year); + } + if (month != null) { + test.addFieldValue(MONTH_OF_YEAR, month); + } + if (day != null) { + test.addFieldValue(DAY_OF_MONTH, day); + } + return test; + } + + private DateTimeBuilder createTime(Integer hour, Integer min, Integer sec, Integer nano) { + DateTimeBuilder test = new DateTimeBuilder(); + if (hour != null) { + test.addFieldValue(HOUR_OF_DAY, hour); + } + if (min != null) { + test.addFieldValue(MINUTE_OF_HOUR, min); + } + if (sec != null) { + test.addFieldValue(SECOND_OF_MINUTE, sec); + } + if (nano != null) { + test.addFieldValue(NANO_OF_SECOND, nano); + } + return test; + } + + private DateTimeBuilder createDateTime( + Integer year, Integer month, Integer day, + Integer hour, Integer min, Integer sec, Integer nano) { + DateTimeBuilder test = new DateTimeBuilder(); + if (year != null) { + test.addFieldValue(YEAR, year); + } + if (month != null) { + test.addFieldValue(MONTH_OF_YEAR, month); + } + if (day != null) { + test.addFieldValue(DAY_OF_MONTH, day); + } + if (hour != null) { + test.addFieldValue(HOUR_OF_DAY, hour); + } + if (min != null) { + test.addFieldValue(MINUTE_OF_HOUR, min); + } + if (sec != null) { + test.addFieldValue(SECOND_OF_MINUTE, sec); + } + if (nano != null) { + test.addFieldValue(NANO_OF_SECOND, nano); + } + return test; + } + + private TemporalAccessor buildAccessor( + Integer year, Integer month, Integer day, + Integer hour, Integer min, Integer sec, Integer nano, + String offsetId, String zoneId) { + MockAccessor mock = new MockAccessor(); + if (year != null) { + mock.fields.put(YEAR, (long) year); + } + if (month != null) { + mock.fields.put(MONTH_OF_YEAR, (long) month); + } + if (day != null) { + mock.fields.put(DAY_OF_MONTH, (long) day); + } + if (hour != null) { + mock.fields.put(HOUR_OF_DAY, (long) hour); + } + if (min != null) { + mock.fields.put(MINUTE_OF_HOUR, (long) min); + } + if (sec != null) { + mock.fields.put(SECOND_OF_MINUTE, (long) sec); + } + if (nano != null) { + mock.fields.put(NANO_OF_SECOND, (long) nano); + } + mock.setOffset(offsetId); + mock.setZone(zoneId); + return mock; + } + + private TemporalAccessor buildAccessor(LocalDateTime base, String offsetId, String zoneId) { + MockAccessor mock = new MockAccessor(); + mock.setFields(base); + mock.setOffset(offsetId); + mock.setZone(zoneId); + return mock; + } + + private TemporalAccessor buildAccessor(LocalDate base, String offsetId, String zoneId) { + MockAccessor mock = new MockAccessor(); + mock.setFields(base); + mock.setOffset(offsetId); + mock.setZone(zoneId); + return mock; + } + + private void buildCalendrical(DateTimeBuilder cal, String offsetId, String zoneId) { + if (offsetId != null) { + cal.addFieldValue(OFFSET_SECONDS, ZoneOffset.of(offsetId).getTotalSeconds()); + } + if (zoneId != null) { + cal.addCalendrical(ZoneId.of(zoneId)); + } + } + + private void assertParseMatch(DateTimeBuilder parsed, DateTimeBuilder expected) { + Map parsedFVMap = parsed.getFieldValueMap(); + Map expectedFVMap = expected.getFieldValueMap(); + assertEquals(parsedFVMap, expectedFVMap); + + List parsedCMap = parsed.getCalendricalList(); + List expectedCMap = expected.getCalendricalList(); + assertEquals(parsedCMap, expectedCMap); + } + + //------------------------------------------------------------------------- + Map fields = new HashMap<>(); + ZoneId zoneId; + static class MockAccessor implements TemporalAccessor { + Map fields = new HashMap<>(); + ZoneId zoneId; + + void setFields(LocalDate dt) { + if (dt != null) { + fields.put(YEAR, (long) dt.getYear()); + fields.put(MONTH_OF_YEAR, (long) dt.getMonthValue()); + fields.put(DAY_OF_MONTH, (long) dt.getDayOfMonth()); + fields.put(DAY_OF_YEAR, (long) dt.getDayOfYear()); + fields.put(DAY_OF_WEEK, (long) dt.getDayOfWeek().getValue()); + fields.put(ISOFields.WEEK_BASED_YEAR, dt.getLong(ISOFields.WEEK_BASED_YEAR)); + fields.put(ISOFields.WEEK_OF_WEEK_BASED_YEAR, dt.getLong(ISOFields.WEEK_OF_WEEK_BASED_YEAR)); + } + } + + void setFields(LocalDateTime dt) { + if (dt != null) { + fields.put(YEAR, (long) dt.getYear()); + fields.put(MONTH_OF_YEAR, (long) dt.getMonthValue()); + fields.put(DAY_OF_MONTH, (long) dt.getDayOfMonth()); + fields.put(DAY_OF_YEAR, (long) dt.getDayOfYear()); + fields.put(DAY_OF_WEEK, (long) dt.getDayOfWeek().getValue()); + fields.put(ISOFields.WEEK_BASED_YEAR, dt.getLong(ISOFields.WEEK_BASED_YEAR)); + fields.put(ISOFields.WEEK_OF_WEEK_BASED_YEAR, dt.getLong(ISOFields.WEEK_OF_WEEK_BASED_YEAR)); + fields.put(HOUR_OF_DAY, (long) dt.getHour()); + fields.put(MINUTE_OF_HOUR, (long) dt.getMinute()); + fields.put(SECOND_OF_MINUTE, (long) dt.getSecond()); + fields.put(NANO_OF_SECOND, (long) dt.getNano()); + } + } + + void setOffset(String offsetId) { + if (offsetId != null) { + this.fields.put(OFFSET_SECONDS, (long) ZoneOffset.of(offsetId).getTotalSeconds()); + } + } + + void setZone(String zoneId) { + if (zoneId != null) { + this.zoneId = ZoneId.of(zoneId); + } + } + + @Override + public boolean isSupported(TemporalField field) { + return fields.containsKey(field); + } + + @Override + public long getLong(TemporalField field) { + try { + return fields.get(field); + } catch (NullPointerException ex) { + throw new DateTimeException("Field missing: " + field); + } + } + + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == Queries.zoneId()) { + return (R) zoneId; + } + return TemporalAccessor.super.query(query); + } + + @Override + public String toString() { + return fields + (zoneId != null ? " " + zoneId : ""); + } + } + +} diff --git a/test/java/time/tck/java/time/format/TCKDateTimePrintException.java b/test/java/time/tck/java/time/format/TCKDateTimePrintException.java new file mode 100644 index 0000000000000000000000000000000000000000..0117629f8cb4f17c8c1c39993dca9275aa753722 --- /dev/null +++ b/test/java/time/tck/java/time/format/TCKDateTimePrintException.java @@ -0,0 +1,101 @@ +/* + * 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. + * + * 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 tck.java.time.format; + +import java.time.format.*; +import test.java.time.format.*; + +import static org.testng.Assert.assertEquals; + +import java.io.IOException; + +import org.testng.annotations.Test; + +/** + * Test DateTimePrintException. + */ +@Test +public class TCKDateTimePrintException { + + @Test(groups={"tck"}) + public void test_constructor_String() throws Exception { + DateTimePrintException ex = new DateTimePrintException("TEST"); + assertEquals(ex.getMessage(), "TEST"); + } + + @Test(groups={"tck"}) + public void test_constructor_StringThrowable_notIOException_equal() throws Exception { + IllegalArgumentException iaex = new IllegalArgumentException("INNER"); + DateTimePrintException ex = new DateTimePrintException("TEST", iaex); + assertEquals(ex.getMessage(), "TEST"); + assertEquals(ex.getCause(), iaex); + ex.rethrowIOException(); // no effect + } + + @Test(expectedExceptions=IOException.class, groups={"tck"}) + public void test_constructor_StringThrowable_IOException() throws Exception { + IOException ioex = new IOException("INNER"); + DateTimePrintException ex = new DateTimePrintException("TEST", ioex); + assertEquals(ex.getMessage(), "TEST"); + assertEquals(ex.getCause(), ioex); + ex.rethrowIOException(); // rethrows + } + +} diff --git a/test/java/time/tck/java/time/format/TCKDateTimeTextPrinting.java b/test/java/time/tck/java/time/format/TCKDateTimeTextPrinting.java new file mode 100644 index 0000000000000000000000000000000000000000..5af0ebf41a92b35413f5f663eb5df34a3df79b12 --- /dev/null +++ b/test/java/time/tck/java/time/format/TCKDateTimeTextPrinting.java @@ -0,0 +1,223 @@ +/* + * 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. + * + * 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 tck.java.time.format; + +import java.time.format.*; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static org.testng.Assert.assertEquals; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import java.time.LocalDateTime; +import java.time.Month; +import java.time.temporal.TemporalField; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test text printing. + */ +@Test +public class TCKDateTimeTextPrinting { + + private DateTimeFormatterBuilder builder; + + @BeforeMethod(groups={"tck"}) + public void setUp() { + builder = new DateTimeFormatterBuilder(); + } + + //----------------------------------------------------------------------- + @DataProvider(name="printText") + Object[][] data_text() { + return new Object[][] { + {DAY_OF_WEEK, TextStyle.FULL, 1, "Monday"}, + {DAY_OF_WEEK, TextStyle.FULL, 2, "Tuesday"}, + {DAY_OF_WEEK, TextStyle.FULL, 3, "Wednesday"}, + {DAY_OF_WEEK, TextStyle.FULL, 4, "Thursday"}, + {DAY_OF_WEEK, TextStyle.FULL, 5, "Friday"}, + {DAY_OF_WEEK, TextStyle.FULL, 6, "Saturday"}, + {DAY_OF_WEEK, TextStyle.FULL, 7, "Sunday"}, + + {DAY_OF_WEEK, TextStyle.SHORT, 1, "Mon"}, + {DAY_OF_WEEK, TextStyle.SHORT, 2, "Tue"}, + {DAY_OF_WEEK, TextStyle.SHORT, 3, "Wed"}, + {DAY_OF_WEEK, TextStyle.SHORT, 4, "Thu"}, + {DAY_OF_WEEK, TextStyle.SHORT, 5, "Fri"}, + {DAY_OF_WEEK, TextStyle.SHORT, 6, "Sat"}, + {DAY_OF_WEEK, TextStyle.SHORT, 7, "Sun"}, + + {DAY_OF_MONTH, TextStyle.FULL, 1, "1"}, + {DAY_OF_MONTH, TextStyle.FULL, 2, "2"}, + {DAY_OF_MONTH, TextStyle.FULL, 3, "3"}, + {DAY_OF_MONTH, TextStyle.FULL, 28, "28"}, + {DAY_OF_MONTH, TextStyle.FULL, 29, "29"}, + {DAY_OF_MONTH, TextStyle.FULL, 30, "30"}, + {DAY_OF_MONTH, TextStyle.FULL, 31, "31"}, + + {DAY_OF_MONTH, TextStyle.SHORT, 1, "1"}, + {DAY_OF_MONTH, TextStyle.SHORT, 2, "2"}, + {DAY_OF_MONTH, TextStyle.SHORT, 3, "3"}, + {DAY_OF_MONTH, TextStyle.SHORT, 28, "28"}, + {DAY_OF_MONTH, TextStyle.SHORT, 29, "29"}, + {DAY_OF_MONTH, TextStyle.SHORT, 30, "30"}, + {DAY_OF_MONTH, TextStyle.SHORT, 31, "31"}, + + {MONTH_OF_YEAR, TextStyle.FULL, 1, "January"}, + {MONTH_OF_YEAR, TextStyle.FULL, 12, "December"}, + + {MONTH_OF_YEAR, TextStyle.SHORT, 1, "Jan"}, + {MONTH_OF_YEAR, TextStyle.SHORT, 12, "Dec"}, + }; + } + + @Test(dataProvider="printText", groups={"tck"}) + public void test_appendText2arg_print(TemporalField field, TextStyle style, int value, String expected) throws Exception { + DateTimeFormatter f = builder.appendText(field, style).toFormatter(Locale.ENGLISH); + LocalDateTime dt = LocalDateTime.of(2010, 1, 1, 0, 0); + dt = dt.with(field, value); + String text = f.print(dt); + assertEquals(text, expected); + } + + @Test(dataProvider="printText", groups={"tck"}) + public void test_appendText1arg_print(TemporalField field, TextStyle style, int value, String expected) throws Exception { + if (style == TextStyle.FULL) { + DateTimeFormatter f = builder.appendText(field).toFormatter(Locale.ENGLISH); + LocalDateTime dt = LocalDateTime.of(2010, 1, 1, 0, 0); + dt = dt.with(field, value); + String text = f.print(dt); + assertEquals(text, expected); + } + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_print_appendText2arg_french_long() throws Exception { + DateTimeFormatter f = builder.appendText(MONTH_OF_YEAR, TextStyle.FULL).toFormatter(Locale.FRENCH); + LocalDateTime dt = LocalDateTime.of(2010, 1, 1, 0, 0); + String text = f.print(dt); + assertEquals(text, "janvier"); + } + + @Test(groups={"tck"}) + public void test_print_appendText2arg_french_short() throws Exception { + DateTimeFormatter f = builder.appendText(MONTH_OF_YEAR, TextStyle.SHORT).toFormatter(Locale.FRENCH); + LocalDateTime dt = LocalDateTime.of(2010, 1, 1, 0, 0); + String text = f.print(dt); + assertEquals(text, "janv."); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_appendTextMap() throws Exception { + Map map = new HashMap(); + map.put(1L, "JNY"); + map.put(2L, "FBY"); + map.put(3L, "MCH"); + map.put(4L, "APL"); + map.put(5L, "MAY"); + map.put(6L, "JUN"); + map.put(7L, "JLY"); + map.put(8L, "AGT"); + map.put(9L, "SPT"); + map.put(10L, "OBR"); + map.put(11L, "NVR"); + map.put(12L, "DBR"); + builder.appendText(MONTH_OF_YEAR, map); + DateTimeFormatter f = builder.toFormatter(); + LocalDateTime dt = LocalDateTime.of(2010, 1, 1, 0, 0); + for (Month month : Month.values()) { + assertEquals(f.print(dt.with(month)), map.get((long) month.getValue())); + } + } + + @Test(groups={"tck"}) + public void test_appendTextMap_DOM() throws Exception { + Map map = new HashMap(); + map.put(1L, "1st"); + map.put(2L, "2nd"); + map.put(3L, "3rd"); + builder.appendText(DAY_OF_MONTH, map); + DateTimeFormatter f = builder.toFormatter(); + LocalDateTime dt = LocalDateTime.of(2010, 1, 1, 0, 0); + assertEquals(f.print(dt.withDayOfMonth(1)), "1st"); + assertEquals(f.print(dt.withDayOfMonth(2)), "2nd"); + assertEquals(f.print(dt.withDayOfMonth(3)), "3rd"); + } + + @Test(groups={"tck"}) + public void test_appendTextMapIncomplete() throws Exception { + Map map = new HashMap(); + map.put(1L, "JNY"); + builder.appendText(MONTH_OF_YEAR, map); + DateTimeFormatter f = builder.toFormatter(); + LocalDateTime dt = LocalDateTime.of(2010, 2, 1, 0, 0); + assertEquals(f.print(dt), "2"); + } + +} diff --git a/test/java/time/tck/java/time/format/TCKLocalizedFieldParser.java b/test/java/time/tck/java/time/format/TCKLocalizedFieldParser.java new file mode 100644 index 0000000000000000000000000000000000000000..d6f5f1619bb783d1a42a75d0abb2a010dc813ce6 --- /dev/null +++ b/test/java/time/tck/java/time/format/TCKLocalizedFieldParser.java @@ -0,0 +1,164 @@ +/* + * 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. + * + * 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) 2010-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 tck.java.time.format; + +import java.time.format.*; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +import java.text.ParsePosition; +import java.time.format.DateTimeBuilder; + +import java.time.LocalDate; +import java.time.temporal.TemporalField; +import java.time.temporal.WeekFields; + +import test.java.time.format.AbstractTestPrinterParser; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test TCKLocalizedFieldParser. + */ +@Test(groups={"tck"}) +public class TCKLocalizedFieldParser extends AbstractTestPrinterParser { + + //----------------------------------------------------------------------- + @DataProvider(name="FieldPatterns") + Object[][] provider_fieldPatterns() { + return new Object[][] { + {"e", "6", 0, 1, 6}, + {"w", "3", 0, 1, 3}, + {"W", "29", 0, 2, 29}, + {"WW", "29", 0, 2, 29}, + }; + } + + @Test(dataProvider="FieldPatterns",groups={"tck"}) + public void test_parse_textField(String pattern, String text, int pos, int expectedPos, long expectedValue) { + WeekFields weekDef = WeekFields.of(locale); + TemporalField field = null; + switch(pattern.charAt(0)) { + case 'e' : + field = weekDef.dayOfWeek(); + break; + case 'w': + field = weekDef.weekOfMonth(); + break; + case 'W': + field = weekDef.weekOfYear(); + break; + default: + throw new IllegalStateException("bad format letter from pattern"); + } + ParsePosition ppos = new ParsePosition(pos); + DateTimeFormatterBuilder b + = new DateTimeFormatterBuilder().appendPattern(pattern); + DateTimeFormatter dtf = b.toFormatter(locale); + DateTimeBuilder dtb = dtf.parseToBuilder(text, ppos); + if (ppos.getErrorIndex() != -1) { + assertEquals(ppos.getErrorIndex(), expectedPos); + } else { + assertEquals(ppos.getIndex(), expectedPos, "Incorrect ending parse position"); + long value = dtb.getLong(field); + assertEquals(value, expectedValue, "Value incorrect for " + field); + } + } + + //----------------------------------------------------------------------- + @DataProvider(name="LocalDatePatterns") + Object[][] provider_patternLocalDate() { + return new Object[][] { + {"e w M y", "1 1 1 2012", 0, 10, LocalDate.of(2012, 1, 1)}, + {"e w M y", "1 2 1 2012", 0, 10, LocalDate.of(2012, 1, 8)}, + {"e w M y", "2 2 1 2012", 0, 10, LocalDate.of(2012, 1, 9)}, + {"e w M y", "3 2 1 2012", 0, 10, LocalDate.of(2012, 1, 10)}, + {"e w M y", "1 3 1 2012", 0, 10, LocalDate.of(2012, 1, 15)}, + {"e w M y", "2 3 1 2012", 0, 10, LocalDate.of(2012, 1, 16)}, + {"e w M y", "6 2 1 2012", 0, 10, LocalDate.of(2012, 1, 13)}, + {"e w M y", "6 2 7 2012", 0, 10, LocalDate.of(2012, 7, 13)}, + {"e W y", "6 29 2012", 0, 9, LocalDate.of(2012, 7, 20)}, + {"'Date: 'y-MM', day-of-week: 'e', week-of-month: 'w", + "Date: 2012-07, day-of-week: 6, week-of-month: 3", 0, 47, LocalDate.of(2012, 7, 20)}, + {"'Date: 'y', day-of-week: 'e', week-of-year: 'W", + "Date: 2012, day-of-week: 6, week-of-year: 29", 0, 44, LocalDate.of(2012, 7, 20)}, + }; + } + @Test(dataProvider="LocalDatePatterns",groups={"tck"}) + public void test_parse_textLocalDate(String pattern, String text, int pos, int expectedPos, LocalDate expectedValue) { + WeekFields weekDef = WeekFields.of(locale); + ParsePosition ppos = new ParsePosition(pos); + DateTimeFormatterBuilder b + = new DateTimeFormatterBuilder().appendPattern(pattern); + DateTimeFormatter dtf = b.toFormatter(locale); + DateTimeBuilder dtb = dtf.parseToBuilder(text, ppos); + if (ppos.getErrorIndex() != -1) { + assertEquals(ppos.getErrorIndex(), expectedPos); + } else { + assertEquals(ppos.getIndex(), expectedPos, "Incorrect ending parse position"); + dtb.resolve(); + LocalDate result = dtb.query(LocalDate::from); + assertEquals(result, expectedValue, "LocalDate incorrect for " + pattern); + } + } + +} diff --git a/test/java/time/tck/java/time/format/TCKLocalizedFieldPrinter.java b/test/java/time/tck/java/time/format/TCKLocalizedFieldPrinter.java new file mode 100644 index 0000000000000000000000000000000000000000..dac65b6b11012f213ecbe98373064d926de3bdc2 --- /dev/null +++ b/test/java/time/tck/java/time/format/TCKLocalizedFieldPrinter.java @@ -0,0 +1,105 @@ +/* + * 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. + * + * 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) 2010-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 tck.java.time.format; + +import java.time.format.*; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +import java.time.LocalDate; + +import test.java.time.format.AbstractTestPrinterParser; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test LocalizedFieldPrinterParser. + */ +@Test(groups={"tck"}) +public class TCKLocalizedFieldPrinter extends AbstractTestPrinterParser { + + //----------------------------------------------------------------------- + @DataProvider(name="Patterns") + Object[][] provider_pad() { + return new Object[][] { + {"e", "6"}, + {"w", "3"}, + {"W", "29"}, + {"WW", "29"}, + {"'Date: 'y-MM-d', week-of-month: 'w', week-of-year: 'W", + "Date: 2012-07-20, week-of-month: 3, week-of-year: 29"}, + + }; + } + + //----------------------------------------------------------------------- + @Test(dataProvider="Patterns",groups={"tck"}) + public void test_localizedDayOfWeek(String pattern, String expected) { + DateTimeFormatterBuilder b + = new DateTimeFormatterBuilder().appendPattern(pattern); + LocalDate date = LocalDate.of(2012, 7, 20); + + String result = b.toFormatter(locale).print(date); + assertEquals(result, expected, "Wrong output for pattern '" + pattern + "'."); + } + +} diff --git a/test/java/time/tck/java/time/temporal/TCKDateTimeAdjusters.java b/test/java/time/tck/java/time/temporal/TCKDateTimeAdjusters.java new file mode 100644 index 0000000000000000000000000000000000000000..231a16b567489ae48ff26b14259efad804473de2 --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TCKDateTimeAdjusters.java @@ -0,0 +1,601 @@ +/* + * 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. + * + * 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 tck.java.time.temporal; + +import java.time.temporal.*; + +import static java.time.DayOfWeek.MONDAY; +import static java.time.DayOfWeek.TUESDAY; +import static java.time.Month.DECEMBER; +import static java.time.Month.JANUARY; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.Month; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test Adjusters. + */ +@Test +public class TCKDateTimeAdjusters { + + //----------------------------------------------------------------------- + // firstDayOfMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_firstDayOfMonth() { + assertNotNull(Adjusters.firstDayOfMonth()); + } + + @Test(groups={"tck"}) + public void test_firstDayOfMonth_nonLeap() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(false); i++) { + LocalDate date = date(2007, month, i); + LocalDate test = (LocalDate) Adjusters.firstDayOfMonth().adjustInto(date); + assertEquals(test.getYear(), 2007); + assertEquals(test.getMonth(), month); + assertEquals(test.getDayOfMonth(), 1); + } + } + } + + @Test(groups={"tck"}) + public void test_firstDayOfMonth_leap() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(true); i++) { + LocalDate date = date(2008, month, i); + LocalDate test = (LocalDate) Adjusters.firstDayOfMonth().adjustInto(date); + assertEquals(test.getYear(), 2008); + assertEquals(test.getMonth(), month); + assertEquals(test.getDayOfMonth(), 1); + } + } + } + + //----------------------------------------------------------------------- + // lastDayOfMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_lastDayOfMonth() { + assertNotNull(Adjusters.lastDayOfMonth()); + } + + @Test(groups={"tck"}) + public void test_lastDayOfMonth_nonLeap() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(false); i++) { + LocalDate date = date(2007, month, i); + LocalDate test = (LocalDate) Adjusters.lastDayOfMonth().adjustInto(date); + assertEquals(test.getYear(), 2007); + assertEquals(test.getMonth(), month); + assertEquals(test.getDayOfMonth(), month.length(false)); + } + } + } + + @Test(groups={"tck"}) + public void test_lastDayOfMonth_leap() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(true); i++) { + LocalDate date = date(2008, month, i); + LocalDate test = (LocalDate) Adjusters.lastDayOfMonth().adjustInto(date); + assertEquals(test.getYear(), 2008); + assertEquals(test.getMonth(), month); + assertEquals(test.getDayOfMonth(), month.length(true)); + } + } + } + + //----------------------------------------------------------------------- + // firstDayOfNextMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_firstDayOfNextMonth() { + assertNotNull(Adjusters.firstDayOfNextMonth()); + } + + @Test(groups={"tck"}) + public void test_firstDayOfNextMonth_nonLeap() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(false); i++) { + LocalDate date = date(2007, month, i); + LocalDate test = (LocalDate) Adjusters.firstDayOfNextMonth().adjustInto(date); + assertEquals(test.getYear(), month == DECEMBER ? 2008 : 2007); + assertEquals(test.getMonth(), month.plus(1)); + assertEquals(test.getDayOfMonth(), 1); + } + } + } + + @Test(groups={"tck"}) + public void test_firstDayOfNextMonth_leap() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(true); i++) { + LocalDate date = date(2008, month, i); + LocalDate test = (LocalDate) Adjusters.firstDayOfNextMonth().adjustInto(date); + assertEquals(test.getYear(), month == DECEMBER ? 2009 : 2008); + assertEquals(test.getMonth(), month.plus(1)); + assertEquals(test.getDayOfMonth(), 1); + } + } + } + + //----------------------------------------------------------------------- + // firstDayOfYear() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_firstDayOfYear() { + assertNotNull(Adjusters.firstDayOfYear()); + } + + @Test(groups={"tck"}) + public void test_firstDayOfYear_nonLeap() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(false); i++) { + LocalDate date = date(2007, month, i); + LocalDate test = (LocalDate) Adjusters.firstDayOfYear().adjustInto(date); + assertEquals(test.getYear(), 2007); + assertEquals(test.getMonth(), Month.JANUARY); + assertEquals(test.getDayOfMonth(), 1); + } + } + } + + @Test(groups={"tck"}) + public void test_firstDayOfYear_leap() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(true); i++) { + LocalDate date = date(2008, month, i); + LocalDate test = (LocalDate) Adjusters.firstDayOfYear().adjustInto(date); + assertEquals(test.getYear(), 2008); + assertEquals(test.getMonth(), Month.JANUARY); + assertEquals(test.getDayOfMonth(), 1); + } + } + } + + //----------------------------------------------------------------------- + // lastDayOfYear() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_lastDayOfYear() { + assertNotNull(Adjusters.lastDayOfYear()); + } + + @Test(groups={"tck"}) + public void test_lastDayOfYear_nonLeap() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(false); i++) { + LocalDate date = date(2007, month, i); + LocalDate test = (LocalDate) Adjusters.lastDayOfYear().adjustInto(date); + assertEquals(test.getYear(), 2007); + assertEquals(test.getMonth(), Month.DECEMBER); + assertEquals(test.getDayOfMonth(), 31); + } + } + } + + @Test(groups={"tck"}) + public void test_lastDayOfYear_leap() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(true); i++) { + LocalDate date = date(2008, month, i); + LocalDate test = (LocalDate) Adjusters.lastDayOfYear().adjustInto(date); + assertEquals(test.getYear(), 2008); + assertEquals(test.getMonth(), Month.DECEMBER); + assertEquals(test.getDayOfMonth(), 31); + } + } + } + + //----------------------------------------------------------------------- + // firstDayOfNextYear() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_firstDayOfNextYear() { + assertNotNull(Adjusters.firstDayOfNextYear()); + } + + @Test(groups={"tck"}) + public void test_firstDayOfNextYear_nonLeap() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(false); i++) { + LocalDate date = date(2007, month, i); + LocalDate test = (LocalDate) Adjusters.firstDayOfNextYear().adjustInto(date); + assertEquals(test.getYear(), 2008); + assertEquals(test.getMonth(), JANUARY); + assertEquals(test.getDayOfMonth(), 1); + } + } + } + + @Test(groups={"tck"}) + public void test_firstDayOfNextYear_leap() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(true); i++) { + LocalDate date = date(2008, month, i); + LocalDate test = (LocalDate) Adjusters.firstDayOfNextYear().adjustInto(date); + assertEquals(test.getYear(), 2009); + assertEquals(test.getMonth(), JANUARY); + assertEquals(test.getDayOfMonth(), 1); + } + } + } + + //----------------------------------------------------------------------- + // dayOfWeekInMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_dayOfWeekInMonth() { + assertNotNull(Adjusters.dayOfWeekInMonth(1, MONDAY)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_dayOfWeekInMonth_nullDayOfWeek() { + Adjusters.dayOfWeekInMonth(1, null); + } + + @DataProvider(name = "dayOfWeekInMonth_positive") + Object[][] data_dayOfWeekInMonth_positive() { + return new Object[][] { + {2011, 1, TUESDAY, date(2011, 1, 4)}, + {2011, 2, TUESDAY, date(2011, 2, 1)}, + {2011, 3, TUESDAY, date(2011, 3, 1)}, + {2011, 4, TUESDAY, date(2011, 4, 5)}, + {2011, 5, TUESDAY, date(2011, 5, 3)}, + {2011, 6, TUESDAY, date(2011, 6, 7)}, + {2011, 7, TUESDAY, date(2011, 7, 5)}, + {2011, 8, TUESDAY, date(2011, 8, 2)}, + {2011, 9, TUESDAY, date(2011, 9, 6)}, + {2011, 10, TUESDAY, date(2011, 10, 4)}, + {2011, 11, TUESDAY, date(2011, 11, 1)}, + {2011, 12, TUESDAY, date(2011, 12, 6)}, + }; + } + + @Test(groups={"tck"}, dataProvider = "dayOfWeekInMonth_positive") + public void test_dayOfWeekInMonth_positive(int year, int month, DayOfWeek dow, LocalDate expected) { + for (int ordinal = 1; ordinal <= 5; ordinal++) { + for (int day = 1; day <= Month.of(month).length(false); day++) { + LocalDate date = date(year, month, day); + LocalDate test = (LocalDate) Adjusters.dayOfWeekInMonth(ordinal, dow).adjustInto(date); + assertEquals(test, expected.plusWeeks(ordinal - 1)); + } + } + } + + @DataProvider(name = "dayOfWeekInMonth_zero") + Object[][] data_dayOfWeekInMonth_zero() { + return new Object[][] { + {2011, 1, TUESDAY, date(2010, 12, 28)}, + {2011, 2, TUESDAY, date(2011, 1, 25)}, + {2011, 3, TUESDAY, date(2011, 2, 22)}, + {2011, 4, TUESDAY, date(2011, 3, 29)}, + {2011, 5, TUESDAY, date(2011, 4, 26)}, + {2011, 6, TUESDAY, date(2011, 5, 31)}, + {2011, 7, TUESDAY, date(2011, 6, 28)}, + {2011, 8, TUESDAY, date(2011, 7, 26)}, + {2011, 9, TUESDAY, date(2011, 8, 30)}, + {2011, 10, TUESDAY, date(2011, 9, 27)}, + {2011, 11, TUESDAY, date(2011, 10, 25)}, + {2011, 12, TUESDAY, date(2011, 11, 29)}, + }; + } + + @Test(groups={"tck"}, dataProvider = "dayOfWeekInMonth_zero") + public void test_dayOfWeekInMonth_zero(int year, int month, DayOfWeek dow, LocalDate expected) { + for (int day = 1; day <= Month.of(month).length(false); day++) { + LocalDate date = date(year, month, day); + LocalDate test = (LocalDate) Adjusters.dayOfWeekInMonth(0, dow).adjustInto(date); + assertEquals(test, expected); + } + } + + @DataProvider(name = "dayOfWeekInMonth_negative") + Object[][] data_dayOfWeekInMonth_negative() { + return new Object[][] { + {2011, 1, TUESDAY, date(2011, 1, 25)}, + {2011, 2, TUESDAY, date(2011, 2, 22)}, + {2011, 3, TUESDAY, date(2011, 3, 29)}, + {2011, 4, TUESDAY, date(2011, 4, 26)}, + {2011, 5, TUESDAY, date(2011, 5, 31)}, + {2011, 6, TUESDAY, date(2011, 6, 28)}, + {2011, 7, TUESDAY, date(2011, 7, 26)}, + {2011, 8, TUESDAY, date(2011, 8, 30)}, + {2011, 9, TUESDAY, date(2011, 9, 27)}, + {2011, 10, TUESDAY, date(2011, 10, 25)}, + {2011, 11, TUESDAY, date(2011, 11, 29)}, + {2011, 12, TUESDAY, date(2011, 12, 27)}, + }; + } + + @Test(groups={"tck"}, dataProvider = "dayOfWeekInMonth_negative") + public void test_dayOfWeekInMonth_negative(int year, int month, DayOfWeek dow, LocalDate expected) { + for (int ordinal = 0; ordinal < 5; ordinal++) { + for (int day = 1; day <= Month.of(month).length(false); day++) { + LocalDate date = date(year, month, day); + LocalDate test = (LocalDate) Adjusters.dayOfWeekInMonth(-1 - ordinal, dow).adjustInto(date); + assertEquals(test, expected.minusWeeks(ordinal)); + } + } + } + + //----------------------------------------------------------------------- + // firstInMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_firstInMonth() { + assertNotNull(Adjusters.firstInMonth(MONDAY)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_firstInMonth_nullDayOfWeek() { + Adjusters.firstInMonth(null); + } + + @Test(groups={"tck"}, dataProvider = "dayOfWeekInMonth_positive") + public void test_firstInMonth(int year, int month, DayOfWeek dow, LocalDate expected) { + for (int day = 1; day <= Month.of(month).length(false); day++) { + LocalDate date = date(year, month, day); + LocalDate test = (LocalDate) Adjusters.firstInMonth(dow).adjustInto(date); + assertEquals(test, expected, "day-of-month=" + day); + } + } + + //----------------------------------------------------------------------- + // lastInMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_lastInMonth() { + assertNotNull(Adjusters.lastInMonth(MONDAY)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_lastInMonth_nullDayOfWeek() { + Adjusters.lastInMonth(null); + } + + @Test(groups={"tck"}, dataProvider = "dayOfWeekInMonth_negative") + public void test_lastInMonth(int year, int month, DayOfWeek dow, LocalDate expected) { + for (int day = 1; day <= Month.of(month).length(false); day++) { + LocalDate date = date(year, month, day); + LocalDate test = (LocalDate) Adjusters.lastInMonth(dow).adjustInto(date); + assertEquals(test, expected, "day-of-month=" + day); + } + } + + //----------------------------------------------------------------------- + // next() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_next() { + assertNotNull(Adjusters.next(MONDAY)); + } + + @Test(expectedExceptions = NullPointerException.class, groups={"tck"}) + public void factory_next_nullDayOfWeek() { + Adjusters.next(null); + } + + @Test(groups={"tck"}) + public void test_next() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(false); i++) { + LocalDate date = date(2007, month, i); + + for (DayOfWeek dow : DayOfWeek.values()) { + LocalDate test = (LocalDate) Adjusters.next(dow).adjustInto(date); + + assertSame(test.getDayOfWeek(), dow, date + " " + test); + + if (test.getYear() == 2007) { + int dayDiff = test.getDayOfYear() - date.getDayOfYear(); + assertTrue(dayDiff > 0 && dayDiff < 8); + } else { + assertSame(month, Month.DECEMBER); + assertTrue(date.getDayOfMonth() > 24); + assertEquals(test.getYear(), 2008); + assertSame(test.getMonth(), Month.JANUARY); + assertTrue(test.getDayOfMonth() < 8); + } + } + } + } + } + + //----------------------------------------------------------------------- + // nextOrSame() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_nextOrCurrent() { + assertNotNull(Adjusters.nextOrSame(MONDAY)); + } + + @Test(expectedExceptions = NullPointerException.class, groups={"tck"}) + public void factory_nextOrCurrent_nullDayOfWeek() { + Adjusters.nextOrSame(null); + } + + @Test(groups={"tck"}) + public void test_nextOrCurrent() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(false); i++) { + LocalDate date = date(2007, month, i); + + for (DayOfWeek dow : DayOfWeek.values()) { + LocalDate test = (LocalDate) Adjusters.nextOrSame(dow).adjustInto(date); + + assertSame(test.getDayOfWeek(), dow); + + if (test.getYear() == 2007) { + int dayDiff = test.getDayOfYear() - date.getDayOfYear(); + assertTrue(dayDiff < 8); + assertEquals(date.equals(test), date.getDayOfWeek() == dow); + } else { + assertFalse(date.getDayOfWeek() == dow); + assertSame(month, Month.DECEMBER); + assertTrue(date.getDayOfMonth() > 24); + assertEquals(test.getYear(), 2008); + assertSame(test.getMonth(), Month.JANUARY); + assertTrue(test.getDayOfMonth() < 8); + } + } + } + } + } + + //----------------------------------------------------------------------- + // previous() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_previous() { + assertNotNull(Adjusters.previous(MONDAY)); + } + + @Test(expectedExceptions = NullPointerException.class, groups={"tck"}) + public void factory_previous_nullDayOfWeek() { + Adjusters.previous(null); + } + + @Test(groups={"tck"}) + public void test_previous() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(false); i++) { + LocalDate date = date(2007, month, i); + + for (DayOfWeek dow : DayOfWeek.values()) { + LocalDate test = (LocalDate) Adjusters.previous(dow).adjustInto(date); + + assertSame(test.getDayOfWeek(), dow, date + " " + test); + + if (test.getYear() == 2007) { + int dayDiff = test.getDayOfYear() - date.getDayOfYear(); + assertTrue(dayDiff < 0 && dayDiff > -8, dayDiff + " " + test); + } else { + assertSame(month, Month.JANUARY); + assertTrue(date.getDayOfMonth() < 8); + assertEquals(test.getYear(), 2006); + assertSame(test.getMonth(), Month.DECEMBER); + assertTrue(test.getDayOfMonth() > 24); + } + } + } + } + } + + //----------------------------------------------------------------------- + // previousOrSame() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_previousOrCurrent() { + assertNotNull(Adjusters.previousOrSame(MONDAY)); + } + + @Test(expectedExceptions = NullPointerException.class, groups={"tck"}) + public void factory_previousOrCurrent_nullDayOfWeek() { + Adjusters.previousOrSame(null); + } + + @Test(groups={"tck"}) + public void test_previousOrCurrent() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(false); i++) { + LocalDate date = date(2007, month, i); + + for (DayOfWeek dow : DayOfWeek.values()) { + LocalDate test = (LocalDate) Adjusters.previousOrSame(dow).adjustInto(date); + + assertSame(test.getDayOfWeek(), dow); + + if (test.getYear() == 2007) { + int dayDiff = test.getDayOfYear() - date.getDayOfYear(); + assertTrue(dayDiff <= 0 && dayDiff > -7); + assertEquals(date.equals(test), date.getDayOfWeek() == dow); + } else { + assertFalse(date.getDayOfWeek() == dow); + assertSame(month, Month.JANUARY); + assertTrue(date.getDayOfMonth() < 7); + assertEquals(test.getYear(), 2006); + assertSame(test.getMonth(), Month.DECEMBER); + assertTrue(test.getDayOfMonth() > 25); + } + } + } + } + } + + private LocalDate date(int year, Month month, int day) { + return LocalDate.of(year, month, day); + } + + private LocalDate date(int year, int month, int day) { + return LocalDate.of(year, month, day); + } + +} diff --git a/test/java/time/tck/java/time/temporal/TCKISOFields.java b/test/java/time/tck/java/time/temporal/TCKISOFields.java new file mode 100644 index 0000000000000000000000000000000000000000..c3acbdb6f82f24452d6df659d66aa450f6b09a24 --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TCKISOFields.java @@ -0,0 +1,233 @@ +/* + * 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) 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 tck.java.time.temporal; + +import java.time.format.DateTimeBuilder; +import java.time.temporal.*; + +import static java.time.DayOfWeek.FRIDAY; +import static java.time.DayOfWeek.MONDAY; +import static java.time.DayOfWeek.SATURDAY; +import static java.time.DayOfWeek.SUNDAY; +import static java.time.DayOfWeek.THURSDAY; +import static java.time.DayOfWeek.TUESDAY; +import static java.time.DayOfWeek.WEDNESDAY; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.YEAR; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.time.DayOfWeek; +import java.time.LocalDate; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test. + */ +@Test(groups={"tck"}) +public class TCKISOFields { + + @DataProvider(name="quarter") + Object[][] data_quarter() { + return new Object[][] { + {LocalDate.of(1969, 12, 29), 90, 4}, + {LocalDate.of(1969, 12, 30), 91, 4}, + {LocalDate.of(1969, 12, 31), 92, 4}, + + {LocalDate.of(1970, 1, 1), 1, 1}, + {LocalDate.of(1970, 1, 2), 2, 1}, + {LocalDate.of(1970, 2, 28), 59, 1}, + {LocalDate.of(1970, 3, 1), 60, 1}, + {LocalDate.of(1970, 3, 31), 90, 1}, + + {LocalDate.of(1970, 4, 1), 1, 2}, + {LocalDate.of(1970, 6, 30), 91, 2}, + + {LocalDate.of(1970, 7, 1), 1, 3}, + {LocalDate.of(1970, 9, 30), 92, 3}, + + {LocalDate.of(1970, 10, 1), 1, 4}, + {LocalDate.of(1970, 12, 31), 92, 4}, + + {LocalDate.of(1972, 2, 28), 59, 1}, + {LocalDate.of(1972, 2, 29), 60, 1}, + {LocalDate.of(1972, 3, 1), 61, 1}, + {LocalDate.of(1972, 3, 31), 91, 1}, + }; + } + + //----------------------------------------------------------------------- + // DAY_OF_QUARTER + //----------------------------------------------------------------------- + @Test(dataProvider="quarter") + public void test_DOQ(LocalDate date, int doq, int qoy) { + assertEquals(ISOFields.DAY_OF_QUARTER.doGet(date), doq); + assertEquals(date.get(ISOFields.DAY_OF_QUARTER), doq); + } + + //----------------------------------------------------------------------- + // QUARTER_OF_YEAR + //----------------------------------------------------------------------- + @Test(dataProvider="quarter") + public void test_QOY(LocalDate date, int doq, int qoy) { + assertEquals(ISOFields.QUARTER_OF_YEAR.doGet(date), qoy); + assertEquals(date.get(ISOFields.QUARTER_OF_YEAR), qoy); + } + + //----------------------------------------------------------------------- + // builder + //----------------------------------------------------------------------- + @Test(dataProvider="quarter") + public void test_builder_quarters(LocalDate date, int doq, int qoy) { + DateTimeBuilder builder = new DateTimeBuilder(); + builder.addFieldValue(ISOFields.DAY_OF_QUARTER, doq); + builder.addFieldValue(ISOFields.QUARTER_OF_YEAR, qoy); + builder.addFieldValue(YEAR, date.getYear()); + builder.resolve(); + assertEquals(builder.query(LocalDate::from), date); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + @DataProvider(name="week") + Object[][] data_week() { + return new Object[][] { + {LocalDate.of(1969, 12, 29), MONDAY, 1, 1970}, + {LocalDate.of(2012, 12, 23), SUNDAY, 51, 2012}, + {LocalDate.of(2012, 12, 24), MONDAY, 52, 2012}, + {LocalDate.of(2012, 12, 27), THURSDAY, 52, 2012}, + {LocalDate.of(2012, 12, 28), FRIDAY, 52, 2012}, + {LocalDate.of(2012, 12, 29), SATURDAY, 52, 2012}, + {LocalDate.of(2012, 12, 30), SUNDAY, 52, 2012}, + {LocalDate.of(2012, 12, 31), MONDAY, 1, 2013}, + {LocalDate.of(2013, 1, 1), TUESDAY, 1, 2013}, + {LocalDate.of(2013, 1, 2), WEDNESDAY, 1, 2013}, + {LocalDate.of(2013, 1, 6), SUNDAY, 1, 2013}, + {LocalDate.of(2013, 1, 7), MONDAY, 2, 2013}, + }; + } + + //----------------------------------------------------------------------- + // WEEK_OF_WEEK_BASED_YEAR + //----------------------------------------------------------------------- + @Test(dataProvider="week") + public void test_WOWBY(LocalDate date, DayOfWeek dow, int week, int wby) { + assertEquals(date.getDayOfWeek(), dow); + assertEquals(ISOFields.WEEK_OF_WEEK_BASED_YEAR.doGet(date), week); + assertEquals(date.get(ISOFields.WEEK_OF_WEEK_BASED_YEAR), week); + } + + //----------------------------------------------------------------------- + // WEEK_BASED_YEAR + //----------------------------------------------------------------------- + @Test(dataProvider="week") + public void test_WBY(LocalDate date, DayOfWeek dow, int week, int wby) { + assertEquals(date.getDayOfWeek(), dow); + assertEquals(ISOFields.WEEK_BASED_YEAR.doGet(date), wby); + assertEquals(date.get(ISOFields.WEEK_BASED_YEAR), wby); + } + + //----------------------------------------------------------------------- + // builder + //----------------------------------------------------------------------- + @Test(dataProvider="week") + public void test_builder_weeks(LocalDate date, DayOfWeek dow, int week, int wby) { + DateTimeBuilder builder = new DateTimeBuilder(); + builder.addFieldValue(ISOFields.WEEK_BASED_YEAR, wby); + builder.addFieldValue(ISOFields.WEEK_OF_WEEK_BASED_YEAR, week); + builder.addFieldValue(DAY_OF_WEEK, dow.getValue()); + builder.resolve(); + assertEquals(builder.query(LocalDate::from), date); + } + + //----------------------------------------------------------------------- + public void test_loop() { + // loop round at least one 400 year cycle, including before 1970 + LocalDate date = LocalDate.of(1960, 1, 5); // Tuseday of week 1 1960 + int year = 1960; + int wby = 1960; + int weekLen = 52; + int week = 1; + while (date.getYear() < 2400) { + DayOfWeek loopDow = date.getDayOfWeek(); + if (date.getYear() != year) { + year = date.getYear(); + } + if (loopDow == MONDAY) { + week++; + if ((week == 53 && weekLen == 52) || week == 54) { + week = 1; + LocalDate firstDayOfWeekBasedYear = date.plusDays(14).withDayOfYear(1); + DayOfWeek firstDay = firstDayOfWeekBasedYear.getDayOfWeek(); + weekLen = (firstDay == THURSDAY || (firstDay == WEDNESDAY && firstDayOfWeekBasedYear.isLeapYear()) ? 53 : 52); + wby++; + } + } + assertEquals(ISOFields.WEEK_OF_WEEK_BASED_YEAR.doRange(date), ValueRange.of(1, weekLen), "Failed on " + date + " " + date.getDayOfWeek()); + assertEquals(ISOFields.WEEK_OF_WEEK_BASED_YEAR.doGet(date), week, "Failed on " + date + " " + date.getDayOfWeek()); + assertEquals(date.get(ISOFields.WEEK_OF_WEEK_BASED_YEAR), week, "Failed on " + date + " " + date.getDayOfWeek()); + assertEquals(ISOFields.WEEK_BASED_YEAR.doGet(date), wby, "Failed on " + date + " " + date.getDayOfWeek()); + assertEquals(date.get(ISOFields.WEEK_BASED_YEAR), wby, "Failed on " + date + " " + date.getDayOfWeek()); + date = date.plusDays(1); + } + } + + // TODO: more tests +} diff --git a/test/java/time/tck/java/time/temporal/TCKJulianFields.java b/test/java/time/tck/java/time/temporal/TCKJulianFields.java new file mode 100644 index 0000000000000000000000000000000000000000..81e8109c329b97ce8a743ec9f36acdda3ac4be55 --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TCKJulianFields.java @@ -0,0 +1,168 @@ +/* + * 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. + * + * 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 tck.java.time.temporal; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import java.time.LocalDate; + +import java.time.temporal.*; + + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test. + */ +@Test +public class TCKJulianFields { + + private static final LocalDate JAN01_1970 = LocalDate.of(1970, 1, 1); + private static final LocalDate DEC31_1969 = LocalDate.of(1969, 12, 31); + private static final LocalDate NOV12_1945 = LocalDate.of(1945, 11, 12); + private static final LocalDate JAN01_0001 = LocalDate.of(1, 1, 1); + + @BeforeMethod + public void setUp() { + } + + //----------------------------------------------------------------------- + @DataProvider(name="julian_fields") + Object[][] julian_samples() { + return new Object[][] { + {JulianFields.JULIAN_DAY}, + {JulianFields.MODIFIED_JULIAN_DAY}, + {JulianFields.RATA_DIE}, + }; + } + + @DataProvider(name="samples") + Object[][] data_samples() { + return new Object[][] { + {ChronoField.EPOCH_DAY, JAN01_1970, 0L}, + {JulianFields.JULIAN_DAY, JAN01_1970, 2400001L + 40587L}, + {JulianFields.MODIFIED_JULIAN_DAY, JAN01_1970, 40587L}, + {JulianFields.RATA_DIE, JAN01_1970, 710347L + (40587L - 31771L)}, + + {ChronoField.EPOCH_DAY, DEC31_1969, -1L}, + {JulianFields.JULIAN_DAY, DEC31_1969, 2400001L + 40586L}, + {JulianFields.MODIFIED_JULIAN_DAY, DEC31_1969, 40586L}, + {JulianFields.RATA_DIE, DEC31_1969, 710347L + (40586L - 31771L)}, + + {ChronoField.EPOCH_DAY, NOV12_1945, (-24 * 365 - 6) - 31 - 30 + 11}, + {JulianFields.JULIAN_DAY, NOV12_1945, 2431772L}, + {JulianFields.MODIFIED_JULIAN_DAY, NOV12_1945, 31771L}, + {JulianFields.RATA_DIE, NOV12_1945, 710347L}, + + {ChronoField.EPOCH_DAY, JAN01_0001, (-24 * 365 - 6) - 31 - 30 + 11 - 710346L}, + {JulianFields.JULIAN_DAY, JAN01_0001, 2431772L - 710346L}, + {JulianFields.MODIFIED_JULIAN_DAY, JAN01_0001, 31771L - 710346L}, + {JulianFields.RATA_DIE, JAN01_0001, 1}, + }; + } + + @Test(dataProvider="samples", groups={"tck"}) + public void test_samples_get(TemporalField field, LocalDate date, long expected) { + assertEquals(date.getLong(field), expected); + } + + @Test(dataProvider="samples", groups={"tck"}) + public void test_samples_set(TemporalField field, LocalDate date, long value) { + assertEquals(field.doWith(LocalDate.MAX, value), date); + assertEquals(field.doWith(LocalDate.MIN, value), date); + assertEquals(field.doWith(JAN01_1970, value), date); + assertEquals(field.doWith(DEC31_1969, value), date); + assertEquals(field.doWith(NOV12_1945, value), date); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString() { + assertEquals(JulianFields.JULIAN_DAY.toString(), "JulianDay"); + assertEquals(JulianFields.MODIFIED_JULIAN_DAY.toString(), "ModifiedJulianDay"); + assertEquals(JulianFields.RATA_DIE.toString(), "RataDie"); + } + + @Test(groups = {"tck"},dataProvider="julian_fields") + public void test_JulianFieldsSingleton(TemporalField field) throws IOException, ClassNotFoundException { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(field); + + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream( + baos.toByteArray())); + TemporalField result = (TemporalField)ois.readObject(); + assertSame(result, field, "Deserialized object same as serialized."); + } + // Exceptions will be handled as failures by TestNG + } + +} diff --git a/test/java/time/tck/java/time/temporal/TCKMonthDay.java b/test/java/time/tck/java/time/temporal/TCKMonthDay.java new file mode 100644 index 0000000000000000000000000000000000000000..774777cab034064710ef0b867c37d9774ee17b45 --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TCKMonthDay.java @@ -0,0 +1,756 @@ +/* + * 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. + * + * 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 tck.java.time.temporal; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import java.time.Clock; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.JulianFields; +import java.time.temporal.MonthDay; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; +import java.time.temporal.YearMonth; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import tck.java.time.AbstractDateTimeTest; + +/** + * Test MonthDay. + */ +@Test +public class TCKMonthDay extends AbstractDateTimeTest { + + private MonthDay TEST_07_15; + + @BeforeMethod(groups={"tck","implementation"}) + public void setUp() { + TEST_07_15 = MonthDay.of(7, 15); + } + + //----------------------------------------------------------------------- + @Override + protected List samples() { + TemporalAccessor[] array = {TEST_07_15, }; + return Arrays.asList(array); + } + + @Override + protected List validFields() { + TemporalField[] array = { + DAY_OF_MONTH, + MONTH_OF_YEAR, + }; + return Arrays.asList(array); + } + + @Override + protected List invalidFields() { + List list = new ArrayList<>(Arrays.asList(ChronoField.values())); + list.removeAll(validFields()); + list.add(JulianFields.JULIAN_DAY); + list.add(JulianFields.MODIFIED_JULIAN_DAY); + list.add(JulianFields.RATA_DIE); + return list; + } + + //----------------------------------------------------------------------- + @Test + public void test_serialization() throws ClassNotFoundException, IOException { + assertSerializable(TEST_07_15); + } + + @Test + public void test_serialization_format() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(6); + dos.writeByte(9); + dos.writeByte(16); + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(MonthDay.of(9, 16), bytes); + } + + //----------------------------------------------------------------------- + void check(MonthDay test, int m, int d) { + assertEquals(test.getMonth().getValue(), m); + assertEquals(test.getDayOfMonth(), d); + } + + //----------------------------------------------------------------------- + // now() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now() { + MonthDay expected = MonthDay.now(Clock.systemDefaultZone()); + MonthDay test = MonthDay.now(); + for (int i = 0; i < 100; i++) { + if (expected.equals(test)) { + return; + } + expected = MonthDay.now(Clock.systemDefaultZone()); + test = MonthDay.now(); + } + assertEquals(test, expected); + } + + //----------------------------------------------------------------------- + // now(ZoneId) + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_ZoneId_nullZoneId() { + MonthDay.now((ZoneId) null); + } + + @Test(groups={"tck"}) + public void now_ZoneId() { + ZoneId zone = ZoneId.of("UTC+01:02:03"); + MonthDay expected = MonthDay.now(Clock.system(zone)); + MonthDay test = MonthDay.now(zone); + for (int i = 0; i < 100; i++) { + if (expected.equals(test)) { + return; + } + expected = MonthDay.now(Clock.system(zone)); + test = MonthDay.now(zone); + } + assertEquals(test, expected); + } + + //----------------------------------------------------------------------- + // now(Clock) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now_Clock() { + Instant instant = LocalDateTime.of(2010, 12, 31, 0, 0).toInstant(ZoneOffset.UTC); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + MonthDay test = MonthDay.now(clock); + assertEquals(test.getMonth(), Month.DECEMBER); + assertEquals(test.getDayOfMonth(), 31); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_Clock_nullClock() { + MonthDay.now((Clock) null); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_intMonth() { + assertEquals(TEST_07_15, MonthDay.of(Month.JULY, 15)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_intMonth_dayTooLow() { + MonthDay.of(Month.JANUARY, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_intMonth_dayTooHigh() { + MonthDay.of(Month.JANUARY, 32); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_intMonth_nullMonth() { + MonthDay.of(null, 15); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_ints() { + check(TEST_07_15, 7, 15); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_ints_dayTooLow() { + MonthDay.of(1, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_ints_dayTooHigh() { + MonthDay.of(1, 32); + } + + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_ints_monthTooLow() { + MonthDay.of(0, 1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_ints_monthTooHigh() { + MonthDay.of(13, 1); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_CalendricalObject() { + assertEquals(MonthDay.from(LocalDate.of(2007, 7, 15)), TEST_07_15); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_CalendricalObject_invalid_noDerive() { + MonthDay.from(LocalTime.of(12, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_CalendricalObject_null() { + MonthDay.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // parse() + //----------------------------------------------------------------------- + @DataProvider(name="goodParseData") + Object[][] provider_goodParseData() { + return new Object[][] { + {"--01-01", MonthDay.of(1, 1)}, + {"--01-31", MonthDay.of(1, 31)}, + {"--02-01", MonthDay.of(2, 1)}, + {"--02-29", MonthDay.of(2, 29)}, + {"--03-01", MonthDay.of(3, 1)}, + {"--03-31", MonthDay.of(3, 31)}, + {"--04-01", MonthDay.of(4, 1)}, + {"--04-30", MonthDay.of(4, 30)}, + {"--05-01", MonthDay.of(5, 1)}, + {"--05-31", MonthDay.of(5, 31)}, + {"--06-01", MonthDay.of(6, 1)}, + {"--06-30", MonthDay.of(6, 30)}, + {"--07-01", MonthDay.of(7, 1)}, + {"--07-31", MonthDay.of(7, 31)}, + {"--08-01", MonthDay.of(8, 1)}, + {"--08-31", MonthDay.of(8, 31)}, + {"--09-01", MonthDay.of(9, 1)}, + {"--09-30", MonthDay.of(9, 30)}, + {"--10-01", MonthDay.of(10, 1)}, + {"--10-31", MonthDay.of(10, 31)}, + {"--11-01", MonthDay.of(11, 1)}, + {"--11-30", MonthDay.of(11, 30)}, + {"--12-01", MonthDay.of(12, 1)}, + {"--12-31", MonthDay.of(12, 31)}, + }; + } + + @Test(dataProvider="goodParseData", groups={"tck"}) + public void factory_parse_success(String text, MonthDay expected) { + MonthDay monthDay = MonthDay.parse(text); + assertEquals(monthDay, expected); + } + + //----------------------------------------------------------------------- + @DataProvider(name="badParseData") + Object[][] provider_badParseData() { + return new Object[][] { + {"", 0}, + {"-00", 0}, + {"--FEB-23", 2}, + {"--01-0", 5}, + {"--01-3A", 5}, + }; + } + + @Test(dataProvider="badParseData", expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_fail(String text, int pos) { + try { + MonthDay.parse(text); + fail(String.format("Parse should have failed for %s at position %d", text, pos)); + } + catch (DateTimeParseException ex) { + assertEquals(ex.getParsedString(), text); + assertEquals(ex.getErrorIndex(), pos); + throw ex; + } + } + + //----------------------------------------------------------------------- + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_illegalValue_Day() { + MonthDay.parse("--06-32"); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_invalidValue_Day() { + MonthDay.parse("--06-31"); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_illegalValue_Month() { + MonthDay.parse("--13-25"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_nullText() { + MonthDay.parse(null); + } + + //----------------------------------------------------------------------- + // parse(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_parse_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("M d"); + MonthDay test = MonthDay.parse("12 3", f); + assertEquals(test, MonthDay.of(12, 3)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullText() { + DateTimeFormatter f = DateTimeFormatters.pattern("M d"); + MonthDay.parse((String) null, f); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullFormatter() { + MonthDay.parse("ANY", null); + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @Test + public void test_get_TemporalField() { + assertEquals(TEST_07_15.get(ChronoField.DAY_OF_MONTH), 15); + assertEquals(TEST_07_15.get(ChronoField.MONTH_OF_YEAR), 7); + } + + @Test + public void test_getLong_TemporalField() { + assertEquals(TEST_07_15.getLong(ChronoField.DAY_OF_MONTH), 15); + assertEquals(TEST_07_15.getLong(ChronoField.MONTH_OF_YEAR), 7); + } + + //----------------------------------------------------------------------- + // get*() + //----------------------------------------------------------------------- + @DataProvider(name="sampleDates") + Object[][] provider_sampleDates() { + return new Object[][] { + {1, 1}, + {1, 31}, + {2, 1}, + {2, 28}, + {2, 29}, + {7, 4}, + {7, 5}, + }; + } + + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_get(int m, int d) { + MonthDay a = MonthDay.of(m, d); + assertEquals(a.getMonth(), Month.of(m)); + assertEquals(a.getDayOfMonth(), d); + } + + //----------------------------------------------------------------------- + // with(Month) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_with_Month() { + assertEquals(MonthDay.of(6, 30).with(Month.JANUARY), MonthDay.of(1, 30)); + } + + @Test(groups={"tck"}) + public void test_with_Month_adjustToValid() { + assertEquals(MonthDay.of(7, 31).with(Month.JUNE), MonthDay.of(6, 30)); + } + + @Test(groups={"tck"}) + public void test_with_Month_adjustToValidFeb() { + assertEquals(MonthDay.of(7, 31).with(Month.FEBRUARY), MonthDay.of(2, 29)); + } + + @Test(groups={"tck"}) + public void test_with_Month_noChangeEqual() { + MonthDay test = MonthDay.of(6, 30); + assertEquals(test.with(Month.JUNE), test); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_with_Month_null() { + MonthDay.of(6, 30).with((Month) null); + } + + //----------------------------------------------------------------------- + // withMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withMonth() { + assertEquals(MonthDay.of(6, 30).withMonth(1), MonthDay.of(1, 30)); + } + + @Test(groups={"tck"}) + public void test_withMonth_adjustToValid() { + assertEquals(MonthDay.of(7, 31).withMonth(6), MonthDay.of(6, 30)); + } + + @Test(groups={"tck"}) + public void test_withMonth_adjustToValidFeb() { + assertEquals(MonthDay.of(7, 31).withMonth(2), MonthDay.of(2, 29)); + } + + @Test(groups={"tck"}) + public void test_withMonth_int_noChangeEqual() { + MonthDay test = MonthDay.of(6, 30); + assertEquals(test.withMonth(6), test); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withMonth_tooLow() { + MonthDay.of(6, 30).withMonth(0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withMonth_tooHigh() { + MonthDay.of(6, 30).withMonth(13); + } + + //----------------------------------------------------------------------- + // withDayOfMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withDayOfMonth() { + assertEquals(MonthDay.of(6, 30).withDayOfMonth(1), MonthDay.of(6, 1)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfMonth_invalid() { + MonthDay.of(6, 30).withDayOfMonth(31); + } + + @Test(groups={"tck"}) + public void test_withDayOfMonth_adjustToValidFeb() { + assertEquals(MonthDay.of(2, 1).withDayOfMonth(29), MonthDay.of(2, 29)); + } + + @Test(groups={"tck"}) + public void test_withDayOfMonth_noChangeEqual() { + MonthDay test = MonthDay.of(6, 30); + assertEquals(test.withDayOfMonth(30), test); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfMonth_tooLow() { + MonthDay.of(6, 30).withDayOfMonth(0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfMonth_tooHigh() { + MonthDay.of(6, 30).withDayOfMonth(32); + } + + //----------------------------------------------------------------------- + // adjustInto() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjustDate() { + MonthDay test = MonthDay.of(6, 30); + LocalDate date = LocalDate.of(2007, 1, 1); + assertEquals(test.adjustInto(date), LocalDate.of(2007, 6, 30)); + } + + @Test(groups={"tck"}) + public void test_adjustDate_resolve() { + MonthDay test = MonthDay.of(2, 29); + LocalDate date = LocalDate.of(2007, 6, 30); + assertEquals(test.adjustInto(date), LocalDate.of(2007, 2, 28)); + } + + @Test(groups={"tck"}) + public void test_adjustDate_equal() { + MonthDay test = MonthDay.of(6, 30); + LocalDate date = LocalDate.of(2007, 6, 30); + assertEquals(test.adjustInto(date), date); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_adjustDate_null() { + TEST_07_15.adjustInto((LocalDate) null); + } + + //----------------------------------------------------------------------- + // isValidYear(int) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_isValidYear_june() { + MonthDay test = MonthDay.of(6, 30); + assertEquals(test.isValidYear(2007), true); + } + + @Test(groups={"tck"}) + public void test_isValidYear_febNonLeap() { + MonthDay test = MonthDay.of(2, 29); + assertEquals(test.isValidYear(2007), false); + } + + @Test(groups={"tck"}) + public void test_isValidYear_febLeap() { + MonthDay test = MonthDay.of(2, 29); + assertEquals(test.isValidYear(2008), true); + } + + //----------------------------------------------------------------------- + // atYear(int) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atYear_int() { + MonthDay test = MonthDay.of(6, 30); + assertEquals(test.atYear(2008), LocalDate.of(2008, 6, 30)); + } + + @Test(groups={"tck"}) + public void test_atYear_int_leapYearAdjust() { + MonthDay test = MonthDay.of(2, 29); + assertEquals(test.atYear(2005), LocalDate.of(2005, 2, 28)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atYear_int_invalidYear() { + MonthDay test = MonthDay.of(6, 30); + test.atYear(Integer.MIN_VALUE); + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_comparisons() { + doTest_comparisons_MonthDay( + MonthDay.of(1, 1), + MonthDay.of(1, 31), + MonthDay.of(2, 1), + MonthDay.of(2, 29), + MonthDay.of(3, 1), + MonthDay.of(12, 31) + ); + } + + void doTest_comparisons_MonthDay(MonthDay... localDates) { + for (int i = 0; i < localDates.length; i++) { + MonthDay a = localDates[i]; + for (int j = 0; j < localDates.length; j++) { + MonthDay b = localDates[j]; + if (i < j) { + assertTrue(a.compareTo(b) < 0, a + " <=> " + b); + assertEquals(a.isBefore(b), true, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else if (i > j) { + assertTrue(a.compareTo(b) > 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), true, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else { + assertEquals(a.compareTo(b), 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), true, a + " <=> " + b); + } + } + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_compareTo_ObjectNull() { + TEST_07_15.compareTo(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isBefore_ObjectNull() { + TEST_07_15.isBefore(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isAfter_ObjectNull() { + TEST_07_15.isAfter(null); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_equals() { + MonthDay a = MonthDay.of(1, 1); + MonthDay b = MonthDay.of(1, 1); + MonthDay c = MonthDay.of(2, 1); + MonthDay d = MonthDay.of(1, 2); + + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), true); + assertEquals(a.equals(c), false); + assertEquals(a.equals(d), false); + + assertEquals(b.equals(a), true); + assertEquals(b.equals(b), true); + assertEquals(b.equals(c), false); + assertEquals(b.equals(d), false); + + assertEquals(c.equals(a), false); + assertEquals(c.equals(b), false); + assertEquals(c.equals(c), true); + assertEquals(c.equals(d), false); + + assertEquals(d.equals(a), false); + assertEquals(d.equals(b), false); + assertEquals(d.equals(c), false); + assertEquals(d.equals(d), true); + } + + @Test(groups={"tck"}) + public void test_equals_itself_true() { + assertEquals(TEST_07_15.equals(TEST_07_15), true); + } + + @Test(groups={"tck"}) + public void test_equals_string_false() { + assertEquals(TEST_07_15.equals("2007-07-15"), false); + } + + @Test(groups={"tck"}) + public void test_equals_null_false() { + assertEquals(TEST_07_15.equals(null), false); + } + + //----------------------------------------------------------------------- + // hashCode() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_hashCode(int m, int d) { + MonthDay a = MonthDay.of(m, d); + assertEquals(a.hashCode(), a.hashCode()); + MonthDay b = MonthDay.of(m, d); + assertEquals(a.hashCode(), b.hashCode()); + } + + @Test(groups={"tck"}) + public void test_hashCode_unique() { + int leapYear = 2008; + Set uniques = new HashSet(366); + for (int i = 1; i <= 12; i++) { + for (int j = 1; j <= 31; j++) { + if (YearMonth.of(leapYear, i).isValidDay(j)) { + assertTrue(uniques.add(MonthDay.of(i, j).hashCode())); + } + } + } + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="sampleToString") + Object[][] provider_sampleToString() { + return new Object[][] { + {7, 5, "--07-05"}, + {12, 31, "--12-31"}, + {1, 2, "--01-02"}, + }; + } + + @Test(dataProvider="sampleToString", groups={"tck"}) + public void test_toString(int m, int d, String expected) { + MonthDay test = MonthDay.of(m, d); + String str = test.toString(); + assertEquals(str, expected); + } + + //----------------------------------------------------------------------- + // toString(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("M d"); + String t = MonthDay.of(12, 3).toString(f); + assertEquals(t, "12 3"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_toString_formatter_null() { + MonthDay.of(12, 3).toString(null); + } + +} diff --git a/test/java/time/tck/java/time/temporal/TCKOffsetDate.java b/test/java/time/tck/java/time/temporal/TCKOffsetDate.java new file mode 100644 index 0000000000000000000000000000000000000000..77720220d699d820bc49f9fceb7acb4f2c7b3880 --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TCKOffsetDate.java @@ -0,0 +1,1949 @@ +/* + * 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. + * + * 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: + * +9 * 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 tck.java.time.temporal; + +import static java.time.Month.DECEMBER; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.EPOCH_MONTH; +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.OFFSET_SECONDS; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import java.time.Clock; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.Period; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.ISOChrono; +import java.time.temporal.JulianFields; +import java.time.temporal.OffsetDate; +import java.time.temporal.OffsetDateTime; +import java.time.temporal.Queries; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.Year; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import tck.java.time.AbstractDateTimeTest; +import test.java.time.MockSimplePeriod; + +/** + * Test OffsetDate. + */ +@Test +public class TCKOffsetDate extends AbstractDateTimeTest { + private static final ZoneOffset OFFSET_PONE = ZoneOffset.ofHours(1); + private static final ZoneOffset OFFSET_PTWO = ZoneOffset.ofHours(2); + + private OffsetDate TEST_2007_07_15_PONE; + + @BeforeMethod(groups={"tck","implementation"}) + public void setUp() { + TEST_2007_07_15_PONE = OffsetDate.of(LocalDate.of(2007, 7, 15), OFFSET_PONE); + } + + //----------------------------------------------------------------------- + @Override + protected List samples() { + TemporalAccessor[] array = {TEST_2007_07_15_PONE, OffsetDate.MIN, OffsetDate.MAX}; + return Arrays.asList(array); + } + + @Override + protected List validFields() { + TemporalField[] array = { + DAY_OF_WEEK, + ALIGNED_DAY_OF_WEEK_IN_MONTH, + ALIGNED_DAY_OF_WEEK_IN_YEAR, + DAY_OF_MONTH, + DAY_OF_YEAR, + EPOCH_DAY, + ALIGNED_WEEK_OF_MONTH, + ALIGNED_WEEK_OF_YEAR, + MONTH_OF_YEAR, + EPOCH_MONTH, + YEAR_OF_ERA, + YEAR, + ERA, + OFFSET_SECONDS, + JulianFields.JULIAN_DAY, + JulianFields.MODIFIED_JULIAN_DAY, + JulianFields.RATA_DIE, + }; + return Arrays.asList(array); + } + + @Override + protected List invalidFields() { + List list = new ArrayList<>(Arrays.asList(ChronoField.values())); + list.removeAll(validFields()); + return list; + } + + //----------------------------------------------------------------------- + @Test + public void test_serialization() throws ClassNotFoundException, IOException { + assertSerializable(TEST_2007_07_15_PONE); + assertSerializable(OffsetDate.MIN); + assertSerializable(OffsetDate.MAX); + } + + @Test + public void test_serialization_format() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(1); + } + byte[] bytes = baos.toByteArray(); + ByteArrayOutputStream baosDate = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baosDate) ) { + dos.writeByte(3); + dos.writeInt(2012); + dos.writeByte(9); + dos.writeByte(16); + } + byte[] bytesDate = baosDate.toByteArray(); + ByteArrayOutputStream baosOffset = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baosOffset) ) { + dos.writeByte(8); + dos.writeByte(4); // quarter hours stored: 3600 / 900 + } + byte[] bytesOffset = baosOffset.toByteArray(); + assertSerializedBySer(OffsetDate.of(LocalDate.of(2012, 9, 16), ZoneOffset.ofHours(1)), bytes, + bytesDate, bytesOffset); + } + + //----------------------------------------------------------------------- + // constants + //----------------------------------------------------------------------- + @Test + public void constant_MIN() { + check(OffsetDate.MIN, Year.MIN_VALUE, 1, 1, ZoneOffset.MAX); + } + + @Test + public void constant_MAX() { + check(OffsetDate.MAX, Year.MAX_VALUE, 12, 31, ZoneOffset.MIN); + } + + //----------------------------------------------------------------------- + // now() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now() { + OffsetDate expected = OffsetDate.now(Clock.systemDefaultZone()); + OffsetDate test = OffsetDate.now(); + for (int i = 0; i < 100; i++) { + if (expected.equals(test)) { + return; + } + expected = OffsetDate.now(Clock.systemDefaultZone()); + test = OffsetDate.now(); + } + assertEquals(test, expected); + } + + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay_utc() { + for (int i = 0; i < (2 * 24 * 60 * 60); i++) { + Instant instant = Instant.ofEpochSecond(i); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + OffsetDate test = OffsetDate.now(clock); + check(test, 1970, 1, (i < 24 * 60 * 60 ? 1 : 2), ZoneOffset.UTC); + } + } + + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay_beforeEpoch() { + for (int i =-1; i >= -(2 * 24 * 60 * 60); i--) { + Instant instant = Instant.ofEpochSecond(i); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + OffsetDate test = OffsetDate.now(clock); + check(test, 1969, 12, (i >= -24 * 60 * 60 ? 31 : 30), ZoneOffset.UTC); + } + } + + @Test(groups={"tck"}) + public void now_Clock_offsets() { + Instant base = LocalDateTime.of(1970, 1, 1, 12, 0).toInstant(ZoneOffset.UTC); + for (int i = -9; i < 15; i++) { + ZoneOffset offset = ZoneOffset.ofHours(i); + Clock clock = Clock.fixed(base, offset); + OffsetDate test = OffsetDate.now(clock); + check(test, 1970, 1, (i >= 12 ? 2 : 1), offset); + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_Clock_nullZoneId() { + OffsetDate.now((ZoneId) null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_Clock_nullClock() { + OffsetDate.now((Clock) null); + } + + //----------------------------------------------------------------------- + // factories + //----------------------------------------------------------------------- + private void check(OffsetDate test, int y, int mo, int d, ZoneOffset offset) { + assertEquals(test.getDate(), LocalDate.of(y, mo, d)); + assertEquals(test.getOffset(), offset); + + assertEquals(test.getYear(), y); + assertEquals(test.getMonth().getValue(), mo); + assertEquals(test.getDayOfMonth(), d); + + assertEquals(test, test); + assertEquals(test.hashCode(), test.hashCode()); + assertEquals(OffsetDate.of(LocalDate.of(y, mo, d), offset), test); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_intMonthInt() { + OffsetDate test = OffsetDate.of(LocalDate.of(2007, Month.JULY, 15), OFFSET_PONE); + check(test, 2007, 7, 15, OFFSET_PONE); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_ints() { + OffsetDate test = OffsetDate.of(LocalDate.of(2007, 7, 15), OFFSET_PONE); + check(test, 2007, 7, 15, OFFSET_PONE); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_intsMonthOffset() { + assertEquals(TEST_2007_07_15_PONE, OffsetDate.of(LocalDate.of(2007, Month.JULY, 15), OFFSET_PONE)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_intsMonthOffset_dayTooLow() { + OffsetDate.of(LocalDate.of(2007, Month.JANUARY, 0), OFFSET_PONE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_intsMonthOffset_dayTooHigh() { + OffsetDate.of(LocalDate.of(2007, Month.JANUARY, 32), OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_intsMonthOffset_nullMonth() { + OffsetDate.of(LocalDate.of(2007, null, 30), OFFSET_PONE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_intsMonthOffset_yearTooLow() { + OffsetDate.of(LocalDate.of(Integer.MIN_VALUE, Month.JANUARY, 1), OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_intsMonthOffset_nullOffset() { + OffsetDate.of(LocalDate.of(2007, Month.JANUARY, 30), null); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_intsOffset() { + OffsetDate test = OffsetDate.of(LocalDate.of(2007, 7, 15), OFFSET_PONE); + check(test, 2007, 7, 15, OFFSET_PONE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_ints_dayTooLow() { + OffsetDate.of(LocalDate.of(2007, 1, 0), OFFSET_PONE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_ints_dayTooHigh() { + OffsetDate.of(LocalDate.of(2007, 1, 32), OFFSET_PONE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_ints_monthTooLow() { + OffsetDate.of(LocalDate.of(2007, 0, 1), OFFSET_PONE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_ints_monthTooHigh() { + OffsetDate.of(LocalDate.of(2007, 13, 1), OFFSET_PONE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_of_ints_yearTooLow() { + OffsetDate.of(LocalDate.of(Integer.MIN_VALUE, 1, 1), OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_ints_nullOffset() { + OffsetDate.of(LocalDate.of(2007, 1, 1), (ZoneOffset) null); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_LocalDateZoneOffset() { + LocalDate localDate = LocalDate.of(2008, 6, 30); + OffsetDate test = OffsetDate.of(localDate, OFFSET_PONE); + check(test, 2008, 6, 30, OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_LocalDateZoneOffset_nullDate() { + OffsetDate.of((LocalDate) null, OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_LocalDateZoneOffset_nullOffset() { + LocalDate localDate = LocalDate.of(2008, 6, 30); + OffsetDate.of(localDate, (ZoneOffset) null); + } + + //----------------------------------------------------------------------- + // from(TemporalAccessor) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_from_TemporalAccessor_OD() { + assertEquals(OffsetDate.from(TEST_2007_07_15_PONE), TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_from_TemporalAccessor_ZDT() { + ZonedDateTime base = LocalDateTime.of(2007, 7, 15, 17, 30).atZone(OFFSET_PONE); + assertEquals(OffsetDate.from(base), TEST_2007_07_15_PONE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_from_TemporalAccessor_invalid_noDerive() { + OffsetDate.from(LocalTime.of(12, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_from_TemporalAccessor_null() { + OffsetDate.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // parse() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleToString", groups={"tck"}) + public void factory_parse_validText(int y, int m, int d, String offsetId, String parsable) { + OffsetDate t = OffsetDate.parse(parsable); + assertNotNull(t, parsable); + assertEquals(t.getYear(), y, parsable); + assertEquals(t.getMonth().getValue(), m, parsable); + assertEquals(t.getDayOfMonth(), d, parsable); + assertEquals(t.getOffset(), ZoneOffset.of(offsetId)); + } + + @DataProvider(name="sampleBadParse") + Object[][] provider_sampleBadParse() { + return new Object[][]{ + {"2008/07/05"}, + {"10000-01-01"}, + {"2008-1-1"}, + {"2008--01"}, + {"ABCD-02-01"}, + {"2008-AB-01"}, + {"2008-02-AB"}, + {"-0000-02-01"}, + {"2008-02-01Y"}, + {"2008-02-01+19:00"}, + {"2008-02-01+01/00"}, + {"2008-02-01+1900"}, + {"2008-02-01+01:60"}, + {"2008-02-01+01:30:123"}, + {"2008-02-01"}, + {"2008-02-01+01:00[Europe/Paris]"}, + }; + } + + @Test(dataProvider="sampleBadParse", expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_invalidText(String unparsable) { + OffsetDate.parse(unparsable); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_illegalValue() { + OffsetDate.parse("2008-06-32+01:00"); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_invalidValue() { + OffsetDate.parse("2008-06-31+01:00"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_nullText() { + OffsetDate.parse((String) null); + } + + //----------------------------------------------------------------------- + // parse(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_parse_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d XXX"); + OffsetDate test = OffsetDate.parse("2010 12 3 +01:00", f); + assertEquals(test, OffsetDate.of(LocalDate.of(2010, 12, 3), ZoneOffset.ofHours(1))); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullText() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d"); + OffsetDate.parse((String) null, f); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullFormatter() { + OffsetDate.parse("ANY", null); + } + + //----------------------------------------------------------------------- + // constructor + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void constructor_nullDate() throws Throwable { + Constructor con = OffsetDate.class.getDeclaredConstructor(LocalDate.class, ZoneOffset.class); + con.setAccessible(true); + try { + con.newInstance(null, OFFSET_PONE); + } catch (InvocationTargetException ex) { + throw ex.getCause(); + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void constructor_nullOffset() throws Throwable { + Constructor con = OffsetDate.class.getDeclaredConstructor(LocalDate.class, ZoneOffset.class); + con.setAccessible(true); + try { + con.newInstance(LocalDate.of(2008, 6, 30), null); + } catch (InvocationTargetException ex) { + throw ex.getCause(); + } + } + + //----------------------------------------------------------------------- + // basics + //----------------------------------------------------------------------- + @DataProvider(name="sampleDates") + Object[][] provider_sampleDates() { + return new Object[][] { + {2008, 7, 5, OFFSET_PTWO}, + {2007, 7, 5, OFFSET_PONE}, + {2006, 7, 5, OFFSET_PTWO}, + {2005, 7, 5, OFFSET_PONE}, + {2004, 1, 1, OFFSET_PTWO}, + {-1, 1, 2, OFFSET_PONE}, + {999999, 11, 20, ZoneOffset.ofHoursMinutesSeconds(6, 9, 12)}, + }; + } + + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_get_OffsetDate(int y, int m, int d, ZoneOffset offset) { + LocalDate localDate = LocalDate.of(y, m, d); + OffsetDate a = OffsetDate.of(localDate, offset); + + assertEquals(a.getDate(), localDate); + assertEquals(a.getOffset(), offset); + assertEquals(a.toString(), localDate.toString() + offset.toString()); + assertEquals(a.getYear(), localDate.getYear()); + assertEquals(a.getMonth(), localDate.getMonth()); + assertEquals(a.getDayOfMonth(), localDate.getDayOfMonth()); + assertEquals(a.getDayOfYear(), localDate.getDayOfYear()); + assertEquals(a.getDayOfWeek(), localDate.getDayOfWeek()); + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @Test + public void test_get_TemporalField() { + OffsetDate test = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); + assertEquals(test.get(ChronoField.YEAR), 2008); + assertEquals(test.get(ChronoField.MONTH_OF_YEAR), 6); + assertEquals(test.get(ChronoField.DAY_OF_MONTH), 30); + assertEquals(test.get(ChronoField.DAY_OF_WEEK), 1); + assertEquals(test.get(ChronoField.DAY_OF_YEAR), 182); + + assertEquals(test.get(ChronoField.OFFSET_SECONDS), 3600); + } + + @Test + public void test_getLong_TemporalField() { + OffsetDate test = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); + assertEquals(test.getLong(ChronoField.YEAR), 2008); + assertEquals(test.getLong(ChronoField.MONTH_OF_YEAR), 6); + assertEquals(test.getLong(ChronoField.DAY_OF_MONTH), 30); + assertEquals(test.getLong(ChronoField.DAY_OF_WEEK), 1); + assertEquals(test.getLong(ChronoField.DAY_OF_YEAR), 182); + + assertEquals(test.getLong(ChronoField.OFFSET_SECONDS), 3600); + } + + //----------------------------------------------------------------------- + // query(TemporalQuery) + //----------------------------------------------------------------------- + @Test + public void test_query_chrono() { + assertEquals(TEST_2007_07_15_PONE.query(Queries.chrono()), ISOChrono.INSTANCE); + assertEquals(Queries.chrono().queryFrom(TEST_2007_07_15_PONE), ISOChrono.INSTANCE); + } + + @Test + public void test_query_zoneId() { + assertEquals(TEST_2007_07_15_PONE.query(Queries.zoneId()), null); + assertEquals(Queries.zoneId().queryFrom(TEST_2007_07_15_PONE), null); + } + + @Test + public void test_query_precision() { + assertEquals(TEST_2007_07_15_PONE.query(Queries.precision()), ChronoUnit.DAYS); + assertEquals(Queries.precision().queryFrom(TEST_2007_07_15_PONE), ChronoUnit.DAYS); + } + + @Test + public void test_query_offset() { + assertEquals(TEST_2007_07_15_PONE.query(Queries.offset()), OFFSET_PONE); + assertEquals(Queries.offset().queryFrom(TEST_2007_07_15_PONE), OFFSET_PONE); + } + + @Test + public void test_query_zone() { + assertEquals(TEST_2007_07_15_PONE.query(Queries.zone()), OFFSET_PONE); + assertEquals(Queries.zone().queryFrom(TEST_2007_07_15_PONE), OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_query_null() { + TEST_2007_07_15_PONE.query(null); + } + + //----------------------------------------------------------------------- + // withOffset() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withOffset() { + OffsetDate base = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); + OffsetDate test = base.withOffset(OFFSET_PTWO); + assertEquals(test.getDate(), base.getDate()); + assertEquals(test.getOffset(), OFFSET_PTWO); + } + + @Test(groups={"tck"}) + public void test_withOffset_noChange() { + OffsetDate base = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); + OffsetDate test = base.withOffset(OFFSET_PONE); + assertEquals(test, base); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_withOffset_null() { + TEST_2007_07_15_PONE.withOffset(null); + } + + //----------------------------------------------------------------------- + // with(WithAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_with_adjustment() { + final OffsetDate sample = OffsetDate.of(LocalDate.of(2012, 3, 4), OFFSET_PONE); + TemporalAdjuster adjuster = new TemporalAdjuster() { + @Override + public Temporal adjustInto(Temporal dateTime) { + return sample; + } + }; + assertEquals(TEST_2007_07_15_PONE.with(adjuster), sample); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_LocalDate() { + OffsetDate test = TEST_2007_07_15_PONE.with(LocalDate.of(2008, 6, 30)); + assertEquals(test, OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_OffsetDate() { + OffsetDate test = TEST_2007_07_15_PONE.with(OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PTWO)); + assertEquals(test, OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PTWO)); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_ZoneOffset() { + OffsetDate test = TEST_2007_07_15_PONE.with(OFFSET_PTWO); + assertEquals(test, OffsetDate.of(LocalDate.of(2007, 7, 15), OFFSET_PTWO)); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_Month() { + OffsetDate test = TEST_2007_07_15_PONE.with(DECEMBER); + assertEquals(test, OffsetDate.of(LocalDate.of(2007, 12, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_offsetUnchanged() { + OffsetDate base = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); + OffsetDate test = base.with(Year.of(2008)); + assertEquals(test, base); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_noChange() { + LocalDate date = LocalDate.of(2008, 6, 30); + OffsetDate base = OffsetDate.of(date, OFFSET_PONE); + OffsetDate test = base.with(date); + assertEquals(test, base); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_with_adjustment_null() { + TEST_2007_07_15_PONE.with((TemporalAdjuster) null); + } + + //----------------------------------------------------------------------- + // with(TemporalField, long) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_with_TemporalField() { + OffsetDate test = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); + assertEquals(test.with(ChronoField.YEAR, 2009), OffsetDate.of(LocalDate.of(2009, 6, 30), OFFSET_PONE)); + assertEquals(test.with(ChronoField.MONTH_OF_YEAR, 7), OffsetDate.of(LocalDate.of(2008, 7, 30), OFFSET_PONE)); + assertEquals(test.with(ChronoField.DAY_OF_MONTH, 1), OffsetDate.of(LocalDate.of(2008, 6, 1), OFFSET_PONE)); + assertEquals(test.with(ChronoField.DAY_OF_WEEK, 2), OffsetDate.of(LocalDate.of(2008, 7, 1), OFFSET_PONE)); + assertEquals(test.with(ChronoField.DAY_OF_YEAR, 183), OffsetDate.of(LocalDate.of(2008, 7, 1), OFFSET_PONE)); + + assertEquals(test.with(ChronoField.OFFSET_SECONDS, 7205), OffsetDate.of(LocalDate.of(2008, 6, 30), ZoneOffset.ofHoursMinutesSeconds(2, 0, 5))); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"} ) + public void test_with_TemporalField_null() { + TEST_2007_07_15_PONE.with((TemporalField) null, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"} ) + public void test_with_TemporalField_invalidField() { + TEST_2007_07_15_PONE.with(ChronoField.AMPM_OF_DAY, 0); + } + + //----------------------------------------------------------------------- + // withYear() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withYear_int_normal() { + OffsetDate t = TEST_2007_07_15_PONE.withYear(2008); + assertEquals(t, OffsetDate.of(LocalDate.of(2008, 7, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_withYear_int_noChange() { + OffsetDate t = TEST_2007_07_15_PONE.withYear(2007); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withYear_int_invalid() { + TEST_2007_07_15_PONE.withYear(Year.MIN_VALUE - 1); + } + + @Test(groups={"tck"}) + public void test_withYear_int_adjustDay() { + OffsetDate t = OffsetDate.of(LocalDate.of(2008, 2, 29), OFFSET_PONE).withYear(2007); + OffsetDate expected = OffsetDate.of(LocalDate.of(2007, 2, 28), OFFSET_PONE); + assertEquals(t, expected); + } + + //----------------------------------------------------------------------- + // withMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withMonth_int_normal() { + OffsetDate t = TEST_2007_07_15_PONE.withMonth(1); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 1, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_withMonth_int_noChange() { + OffsetDate t = TEST_2007_07_15_PONE.withMonth(7); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withMonth_int_invalid() { + TEST_2007_07_15_PONE.withMonth(13); + } + + @Test(groups={"tck"}) + public void test_withMonth_int_adjustDay() { + OffsetDate t = OffsetDate.of(LocalDate.of(2007, 12, 31), OFFSET_PONE).withMonth(11); + OffsetDate expected = OffsetDate.of(LocalDate.of(2007, 11, 30), OFFSET_PONE); + assertEquals(t, expected); + } + + //----------------------------------------------------------------------- + // withDayOfMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withDayOfMonth_normal() { + OffsetDate t = TEST_2007_07_15_PONE.withDayOfMonth(1); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 7, 1), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_withDayOfMonth_noChange() { + OffsetDate t = TEST_2007_07_15_PONE.withDayOfMonth(15); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 7, 15), OFFSET_PONE)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfMonth_invalidForMonth() { + OffsetDate.of(LocalDate.of(2007, 11, 30), OFFSET_PONE).withDayOfMonth(31); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfMonth_invalidAlways() { + OffsetDate.of(LocalDate.of(2007, 11, 30), OFFSET_PONE).withDayOfMonth(32); + } + + //----------------------------------------------------------------------- + // withDayOfYear(int) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withDayOfYear_normal() { + OffsetDate t = TEST_2007_07_15_PONE.withDayOfYear(33); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 2, 2), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_withDayOfYear_noChange() { + OffsetDate t = TEST_2007_07_15_PONE.withDayOfYear(31 + 28 + 31 + 30 + 31 + 30 + 15); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfYear_illegal() { + TEST_2007_07_15_PONE.withDayOfYear(367); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfYear_invalid() { + TEST_2007_07_15_PONE.withDayOfYear(366); + } + + //----------------------------------------------------------------------- + // plus(PlusAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plus_PlusAdjuster() { + MockSimplePeriod period = MockSimplePeriod.of(7, ChronoUnit.MONTHS); + OffsetDate t = TEST_2007_07_15_PONE.plus(period); + assertEquals(t, OffsetDate.of(LocalDate.of(2008, 2, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plus_PlusAdjuster_noChange() { + MockSimplePeriod period = MockSimplePeriod.of(0, ChronoUnit.MONTHS); + OffsetDate t = TEST_2007_07_15_PONE.plus(period); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_plus_PlusAdjuster_zero() { + OffsetDate t = TEST_2007_07_15_PONE.plus(Period.ZERO); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_plus_PlusAdjuster_null() { + TEST_2007_07_15_PONE.plus((TemporalAdder) null); + } + + //----------------------------------------------------------------------- + // plusYears() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusYears_long_normal() { + OffsetDate t = TEST_2007_07_15_PONE.plusYears(1); + assertEquals(t, OffsetDate.of(LocalDate.of(2008, 7, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusYears_long_negative() { + OffsetDate t = TEST_2007_07_15_PONE.plusYears(-1); + assertEquals(t, OffsetDate.of(LocalDate.of(2006, 7, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusYears_long_noChange() { + OffsetDate t = TEST_2007_07_15_PONE.plusYears(0); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_plusYears_long_adjustDay() { + OffsetDate t = OffsetDate.of(LocalDate.of(2008, 2, 29), OFFSET_PONE).plusYears(1); + OffsetDate expected = OffsetDate.of(LocalDate.of(2009, 2, 28), OFFSET_PONE); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_plusYears_long_big() { + long years = 20L + Year.MAX_VALUE; + OffsetDate test = OffsetDate.of(LocalDate.of(-40, 6, 1), OFFSET_PONE).plusYears(years); + assertEquals(test, OffsetDate.of(LocalDate.of((int) (-40L + years), 6, 1), OFFSET_PONE)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_long_invalidTooLarge() { + OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 1, 1), OFFSET_PONE).plusYears(1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_long_invalidTooLargeMaxAddMax() { + OffsetDate test = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 1), OFFSET_PONE); + test.plusYears(Long.MAX_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_long_invalidTooLargeMaxAddMin() { + OffsetDate test = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 1), OFFSET_PONE); + test.plusYears(Long.MIN_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_long_invalidTooSmall() { + OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 1), OFFSET_PONE).plusYears(-1); + } + + //----------------------------------------------------------------------- + // plusMonths() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusMonths_long_normal() { + OffsetDate t = TEST_2007_07_15_PONE.plusMonths(1); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 8, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_overYears() { + OffsetDate t = TEST_2007_07_15_PONE.plusMonths(25); + assertEquals(t, OffsetDate.of(LocalDate.of(2009, 8, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_negative() { + OffsetDate t = TEST_2007_07_15_PONE.plusMonths(-1); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 6, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_negativeAcrossYear() { + OffsetDate t = TEST_2007_07_15_PONE.plusMonths(-7); + assertEquals(t, OffsetDate.of(LocalDate.of(2006, 12, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_negativeOverYears() { + OffsetDate t = TEST_2007_07_15_PONE.plusMonths(-31); + assertEquals(t, OffsetDate.of(LocalDate.of(2004, 12, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_noChange() { + OffsetDate t = TEST_2007_07_15_PONE.plusMonths(0); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_adjustDayFromLeapYear() { + OffsetDate t = OffsetDate.of(LocalDate.of(2008, 2, 29), OFFSET_PONE).plusMonths(12); + OffsetDate expected = OffsetDate.of(LocalDate.of(2009, 2, 28), OFFSET_PONE); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_adjustDayFromMonthLength() { + OffsetDate t = OffsetDate.of(LocalDate.of(2007, 3, 31), OFFSET_PONE).plusMonths(1); + OffsetDate expected = OffsetDate.of(LocalDate.of(2007, 4, 30), OFFSET_PONE); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_big() { + long months = 20L + Integer.MAX_VALUE; + OffsetDate test = OffsetDate.of(LocalDate.of(-40, 6, 1), OFFSET_PONE).plusMonths(months); + assertEquals(test, OffsetDate.of(LocalDate.of((int) (-40L + months / 12), 6 + (int) (months % 12), 1), OFFSET_PONE)); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_plusMonths_long_invalidTooLarge() { + OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 1), OFFSET_PONE).plusMonths(1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusMonths_long_invalidTooLargeMaxAddMax() { + OffsetDate test = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 1), OFFSET_PONE); + test.plusMonths(Long.MAX_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusMonths_long_invalidTooLargeMaxAddMin() { + OffsetDate test = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 1), OFFSET_PONE); + test.plusMonths(Long.MIN_VALUE); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_plusMonths_long_invalidTooSmall() { + OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 1), OFFSET_PONE).plusMonths(-1); + } + + //----------------------------------------------------------------------- + // plusWeeks() + //----------------------------------------------------------------------- + @DataProvider(name="samplePlusWeeksSymmetry") + Object[][] provider_samplePlusWeeksSymmetry() { + return new Object[][] { + {OffsetDate.of(LocalDate.of(-1, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(-1, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(-1, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(-1, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(0, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(0, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(0, 2, 29), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(0, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(0, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2007, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2007, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2007, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2007, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2008, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2008, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2008, 2, 29), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2008, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2008, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2099, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2099, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2099, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2099, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2100, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2100, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2100, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2100, 12, 31), OFFSET_PTWO)}, + }; + } + + @Test(dataProvider="samplePlusWeeksSymmetry", groups={"tck"}) + public void test_plusWeeks_symmetry(OffsetDate reference) { + for (int weeks = 0; weeks < 365 * 8; weeks++) { + OffsetDate t = reference.plusWeeks(weeks).plusWeeks(-weeks); + assertEquals(t, reference); + + t = reference.plusWeeks(-weeks).plusWeeks(weeks); + assertEquals(t, reference); + } + } + + @Test(groups={"tck"}) + public void test_plusWeeks_normal() { + OffsetDate t = TEST_2007_07_15_PONE.plusWeeks(1); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 7, 22), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_overMonths() { + OffsetDate t = TEST_2007_07_15_PONE.plusWeeks(9); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 9, 16), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_overYears() { + OffsetDate t = OffsetDate.of(LocalDate.of(2006, 7, 16), OFFSET_PONE).plusWeeks(52); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_overLeapYears() { + OffsetDate t = TEST_2007_07_15_PONE.plusYears(-1).plusWeeks(104); + assertEquals(t, OffsetDate.of(LocalDate.of(2008, 7, 12), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_negative() { + OffsetDate t = TEST_2007_07_15_PONE.plusWeeks(-1); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 7, 8), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_negativeAcrossYear() { + OffsetDate t = TEST_2007_07_15_PONE.plusWeeks(-28); + assertEquals(t, OffsetDate.of(LocalDate.of(2006, 12, 31), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_negativeOverYears() { + OffsetDate t = TEST_2007_07_15_PONE.plusWeeks(-104); + assertEquals(t, OffsetDate.of(LocalDate.of(2005, 7, 17), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_noChange() { + OffsetDate t = TEST_2007_07_15_PONE.plusWeeks(0); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_maximum() { + OffsetDate t = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 24), OFFSET_PONE).plusWeeks(1); + OffsetDate expected = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 31), OFFSET_PONE); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_plusWeeks_minimum() { + OffsetDate t = OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 8), OFFSET_PONE).plusWeeks(-1); + OffsetDate expected = OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 1), OFFSET_PONE); + assertEquals(t, expected); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_plusWeeks_invalidTooLarge() { + OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 25), OFFSET_PONE).plusWeeks(1); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_plusWeeks_invalidTooSmall() { + OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 7), OFFSET_PONE).plusWeeks(-1); + } + + @Test(expectedExceptions={ArithmeticException.class}, groups={"tck"}) + public void test_plusWeeks_invalidMaxMinusMax() { + OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 25), OFFSET_PONE).plusWeeks(Long.MAX_VALUE); + } + + @Test(expectedExceptions={ArithmeticException.class}, groups={"tck"}) + public void test_plusWeeks_invalidMaxMinusMin() { + OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 25), OFFSET_PONE).plusWeeks(Long.MIN_VALUE); + } + + //----------------------------------------------------------------------- + // plusDays() + //----------------------------------------------------------------------- + @DataProvider(name="samplePlusDaysSymmetry") + Object[][] provider_samplePlusDaysSymmetry() { + return new Object[][] { + {OffsetDate.of(LocalDate.of(-1, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(-1, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(-1, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(-1, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(0, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(0, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(0, 2, 29), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(0, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(0, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2007, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2007, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2007, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2007, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2008, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2008, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2008, 2, 29), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2008, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2008, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2099, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2099, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2099, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2099, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2100, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2100, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2100, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2100, 12, 31), OFFSET_PTWO)}, + }; + } + + @Test(dataProvider="samplePlusDaysSymmetry", groups={"tck"}) + public void test_plusDays_symmetry(OffsetDate reference) { + for (int days = 0; days < 365 * 8; days++) { + OffsetDate t = reference.plusDays(days).plusDays(-days); + assertEquals(t, reference); + + t = reference.plusDays(-days).plusDays(days); + assertEquals(t, reference); + } + } + + @Test(groups={"tck"}) + public void test_plusDays_normal() { + OffsetDate t = TEST_2007_07_15_PONE.plusDays(1); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 7, 16), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusDays_overMonths() { + OffsetDate t = TEST_2007_07_15_PONE.plusDays(62); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 9, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusDays_overYears() { + OffsetDate t = OffsetDate.of(LocalDate.of(2006, 7, 14), OFFSET_PONE).plusDays(366); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_plusDays_overLeapYears() { + OffsetDate t = TEST_2007_07_15_PONE.plusYears(-1).plusDays(365 + 366); + assertEquals(t, OffsetDate.of(LocalDate.of(2008, 7, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusDays_negative() { + OffsetDate t = TEST_2007_07_15_PONE.plusDays(-1); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 7, 14), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusDays_negativeAcrossYear() { + OffsetDate t = TEST_2007_07_15_PONE.plusDays(-196); + assertEquals(t, OffsetDate.of(LocalDate.of(2006, 12, 31), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusDays_negativeOverYears() { + OffsetDate t = TEST_2007_07_15_PONE.plusDays(-730); + assertEquals(t, OffsetDate.of(LocalDate.of(2005, 7, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusDays_noChange() { + OffsetDate t = TEST_2007_07_15_PONE.plusDays(0); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_plusDays_maximum() { + OffsetDate t = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 30), OFFSET_PONE).plusDays(1); + OffsetDate expected = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 31), OFFSET_PONE); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_plusDays_minimum() { + OffsetDate t = OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 2), OFFSET_PONE).plusDays(-1); + OffsetDate expected = OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 1), OFFSET_PONE); + assertEquals(t, expected); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_plusDays_invalidTooLarge() { + OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 31), OFFSET_PONE).plusDays(1); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_plusDays_invalidTooSmall() { + OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 1), OFFSET_PONE).plusDays(-1); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_plusDays_overflowTooLarge() { + OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 31), OFFSET_PONE).plusDays(Long.MAX_VALUE); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_plusDays_overflowTooSmall() { + OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 1), OFFSET_PONE).plusDays(Long.MIN_VALUE); + } + + //----------------------------------------------------------------------- + // minus(MinusAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minus_MinusAdjuster() { + MockSimplePeriod period = MockSimplePeriod.of(7, ChronoUnit.MONTHS); + OffsetDate t = TEST_2007_07_15_PONE.minus(period); + assertEquals(t, OffsetDate.of(LocalDate.of(2006, 12, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minus_MinusAdjuster_noChange() { + MockSimplePeriod period = MockSimplePeriod.of(0, ChronoUnit.MONTHS); + OffsetDate t = TEST_2007_07_15_PONE.minus(period); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_minus_MinusAdjuster_zero() { + OffsetDate t = TEST_2007_07_15_PONE.minus(Period.ZERO); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_plus_MinusAdjuster_null() { + TEST_2007_07_15_PONE.minus((TemporalSubtractor) null); + } + + //----------------------------------------------------------------------- + // minusYears() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusYears_long_normal() { + OffsetDate t = TEST_2007_07_15_PONE.minusYears(1); + assertEquals(t, OffsetDate.of(LocalDate.of(2006, 7, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusYears_long_negative() { + OffsetDate t = TEST_2007_07_15_PONE.minusYears(-1); + assertEquals(t, OffsetDate.of(LocalDate.of(2008, 7, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusYears_long_noChange() { + OffsetDate t = TEST_2007_07_15_PONE.minusYears(0); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_minusYears_long_adjustDay() { + OffsetDate t = OffsetDate.of(LocalDate.of(2008, 2, 29), OFFSET_PONE).minusYears(1); + OffsetDate expected = OffsetDate.of(LocalDate.of(2007, 2, 28), OFFSET_PONE); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_minusYears_long_big() { + long years = 20L + Year.MAX_VALUE; + OffsetDate test = OffsetDate.of(LocalDate.of(40, 6, 1), OFFSET_PONE).minusYears(years); + assertEquals(test, OffsetDate.of(LocalDate.of((int) (40L - years), 6, 1), OFFSET_PONE)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_long_invalidTooLarge() { + OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 1, 1), OFFSET_PONE).minusYears(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_long_invalidTooLargeMaxAddMax() { + OffsetDate test = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 1), OFFSET_PONE); + test.minusYears(Long.MAX_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_long_invalidTooLargeMaxAddMin() { + OffsetDate test = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 1), OFFSET_PONE); + test.minusYears(Long.MIN_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_long_invalidTooSmall() { + OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 1), OFFSET_PONE).minusYears(1); + } + + //----------------------------------------------------------------------- + // minusMonths() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusMonths_long_normal() { + OffsetDate t = TEST_2007_07_15_PONE.minusMonths(1); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 6, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_overYears() { + OffsetDate t = TEST_2007_07_15_PONE.minusMonths(25); + assertEquals(t, OffsetDate.of(LocalDate.of(2005, 6, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_negative() { + OffsetDate t = TEST_2007_07_15_PONE.minusMonths(-1); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 8, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_negativeAcrossYear() { + OffsetDate t = TEST_2007_07_15_PONE.minusMonths(-7); + assertEquals(t, OffsetDate.of(LocalDate.of(2008, 2, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_negativeOverYears() { + OffsetDate t = TEST_2007_07_15_PONE.minusMonths(-31); + assertEquals(t, OffsetDate.of(LocalDate.of(2010, 2, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_noChange() { + OffsetDate t = TEST_2007_07_15_PONE.minusMonths(0); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_adjustDayFromLeapYear() { + OffsetDate t = OffsetDate.of(LocalDate.of(2008, 2, 29), OFFSET_PONE).minusMonths(12); + OffsetDate expected = OffsetDate.of(LocalDate.of(2007, 2, 28), OFFSET_PONE); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_adjustDayFromMonthLength() { + OffsetDate t = OffsetDate.of(LocalDate.of(2007, 3, 31), OFFSET_PONE).minusMonths(1); + OffsetDate expected = OffsetDate.of(LocalDate.of(2007, 2, 28), OFFSET_PONE); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_big() { + long months = 20L + Integer.MAX_VALUE; + OffsetDate test = OffsetDate.of(LocalDate.of(40, 6, 1), OFFSET_PONE).minusMonths(months); + assertEquals(test, OffsetDate.of(LocalDate.of((int) (40L - months / 12), 6 - (int) (months % 12), 1), OFFSET_PONE)); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_minusMonths_long_invalidTooLarge() { + OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 1), OFFSET_PONE).minusMonths(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusMonths_long_invalidTooLargeMaxAddMax() { + OffsetDate test = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 1), OFFSET_PONE); + test.minusMonths(Long.MAX_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusMonths_long_invalidTooLargeMaxAddMin() { + OffsetDate test = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 1), OFFSET_PONE); + test.minusMonths(Long.MIN_VALUE); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_minusMonths_long_invalidTooSmall() { + OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 1), OFFSET_PONE).minusMonths(1); + } + + //----------------------------------------------------------------------- + // minusWeeks() + //----------------------------------------------------------------------- + @DataProvider(name="sampleMinusWeeksSymmetry") + Object[][] provider_sampleMinusWeeksSymmetry() { + return new Object[][] { + {OffsetDate.of(LocalDate.of(-1, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(-1, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(-1, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(-1, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(0, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(0, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(0, 2, 29), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(0, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(0, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2007, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2007, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2007, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2007, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2008, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2008, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2008, 2, 29), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2008, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2008, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2099, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2099, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2099, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2099, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2100, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2100, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2100, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2100, 12, 31), OFFSET_PTWO)}, + }; + } + + @Test(dataProvider="sampleMinusWeeksSymmetry", groups={"tck"}) + public void test_minusWeeks_symmetry(OffsetDate reference) { + for (int weeks = 0; weeks < 365 * 8; weeks++) { + OffsetDate t = reference.minusWeeks(weeks).minusWeeks(-weeks); + assertEquals(t, reference); + + t = reference.minusWeeks(-weeks).minusWeeks(weeks); + assertEquals(t, reference); + } + } + + @Test(groups={"tck"}) + public void test_minusWeeks_normal() { + OffsetDate t = TEST_2007_07_15_PONE.minusWeeks(1); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 7, 8), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_overMonths() { + OffsetDate t = TEST_2007_07_15_PONE.minusWeeks(9); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 5, 13), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_overYears() { + OffsetDate t = OffsetDate.of(LocalDate.of(2008, 7, 13), OFFSET_PONE).minusWeeks(52); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_overLeapYears() { + OffsetDate t = TEST_2007_07_15_PONE.minusYears(-1).minusWeeks(104); + assertEquals(t, OffsetDate.of(LocalDate.of(2006, 7, 18), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_negative() { + OffsetDate t = TEST_2007_07_15_PONE.minusWeeks(-1); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 7, 22), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_negativeAcrossYear() { + OffsetDate t = TEST_2007_07_15_PONE.minusWeeks(-28); + assertEquals(t, OffsetDate.of(LocalDate.of(2008, 1, 27), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_negativeOverYears() { + OffsetDate t = TEST_2007_07_15_PONE.minusWeeks(-104); + assertEquals(t, OffsetDate.of(LocalDate.of(2009, 7, 12), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_noChange() { + OffsetDate t = TEST_2007_07_15_PONE.minusWeeks(0); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_maximum() { + OffsetDate t = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 24), OFFSET_PONE).minusWeeks(-1); + OffsetDate expected = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 31), OFFSET_PONE); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_minusWeeks_minimum() { + OffsetDate t = OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 8), OFFSET_PONE).minusWeeks(1); + OffsetDate expected = OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 1), OFFSET_PONE); + assertEquals(t, expected); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_minusWeeks_invalidTooLarge() { + OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 25), OFFSET_PONE).minusWeeks(-1); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_minusWeeks_invalidTooSmall() { + OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 7), OFFSET_PONE).minusWeeks(1); + } + + @Test(expectedExceptions={ArithmeticException.class}, groups={"tck"}) + public void test_minusWeeks_invalidMaxMinusMax() { + OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 25), OFFSET_PONE).minusWeeks(Long.MAX_VALUE); + } + + @Test(expectedExceptions={ArithmeticException.class}, groups={"tck"}) + public void test_minusWeeks_invalidMaxMinusMin() { + OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 25), OFFSET_PONE).minusWeeks(Long.MIN_VALUE); + } + + //----------------------------------------------------------------------- + // minusDays() + //----------------------------------------------------------------------- + @DataProvider(name="sampleMinusDaysSymmetry") + Object[][] provider_sampleMinusDaysSymmetry() { + return new Object[][] { + {OffsetDate.of(LocalDate.of(-1, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(-1, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(-1, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(-1, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(0, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(0, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(0, 2, 29), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(0, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(0, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2007, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2007, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2007, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2007, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2008, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2008, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2008, 2, 29), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2008, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2008, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2099, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2099, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2099, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2099, 12, 31), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2100, 1, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2100, 2, 28), OFFSET_PTWO)}, + {OffsetDate.of(LocalDate.of(2100, 3, 1), OFFSET_PONE)}, + {OffsetDate.of(LocalDate.of(2100, 12, 31), OFFSET_PTWO)}, + }; + } + + @Test(dataProvider="sampleMinusDaysSymmetry", groups={"tck"}) + public void test_minusDays_symmetry(OffsetDate reference) { + for (int days = 0; days < 365 * 8; days++) { + OffsetDate t = reference.minusDays(days).minusDays(-days); + assertEquals(t, reference); + + t = reference.minusDays(-days).minusDays(days); + assertEquals(t, reference); + } + } + + @Test(groups={"tck"}) + public void test_minusDays_normal() { + OffsetDate t = TEST_2007_07_15_PONE.minusDays(1); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 7, 14), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusDays_overMonths() { + OffsetDate t = TEST_2007_07_15_PONE.minusDays(62); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 5, 14), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusDays_overYears() { + OffsetDate t = OffsetDate.of(LocalDate.of(2008, 7, 16), OFFSET_PONE).minusDays(367); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_minusDays_overLeapYears() { + OffsetDate t = TEST_2007_07_15_PONE.plusYears(2).minusDays(365 + 366); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_minusDays_negative() { + OffsetDate t = TEST_2007_07_15_PONE.minusDays(-1); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 7, 16), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusDays_negativeAcrossYear() { + OffsetDate t = TEST_2007_07_15_PONE.minusDays(-169); + assertEquals(t, OffsetDate.of(LocalDate.of(2007, 12, 31), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusDays_negativeOverYears() { + OffsetDate t = TEST_2007_07_15_PONE.minusDays(-731); + assertEquals(t, OffsetDate.of(LocalDate.of(2009, 7, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusDays_noChange() { + OffsetDate t = TEST_2007_07_15_PONE.minusDays(0); + assertEquals(t, TEST_2007_07_15_PONE); + } + + @Test(groups={"tck"}) + public void test_minusDays_maximum() { + OffsetDate t = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 30), OFFSET_PONE).minusDays(-1); + OffsetDate expected = OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 31), OFFSET_PONE); + assertEquals(t, expected); + } + + @Test(groups={"tck"}) + public void test_minusDays_minimum() { + OffsetDate t = OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 2), OFFSET_PONE).minusDays(1); + OffsetDate expected = OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 1), OFFSET_PONE); + assertEquals(t, expected); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_minusDays_invalidTooLarge() { + OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 31), OFFSET_PONE).minusDays(-1); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_minusDays_invalidTooSmall() { + OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 1), OFFSET_PONE).minusDays(1); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_minusDays_overflowTooLarge() { + OffsetDate.of(LocalDate.of(Year.MAX_VALUE, 12, 31), OFFSET_PONE).minusDays(Long.MIN_VALUE); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_minusDays_overflowTooSmall() { + OffsetDate.of(LocalDate.of(Year.MIN_VALUE, 1, 1), OFFSET_PONE).minusDays(Long.MAX_VALUE); + } + + //----------------------------------------------------------------------- + // atTime() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atTime_Local() { + OffsetDate t = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PTWO); + assertEquals(t.atTime(LocalTime.of(11, 30)), + OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30), OFFSET_PTWO)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_atTime_Local_nullLocalTime() { + OffsetDate t = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PTWO); + t.atTime((LocalTime) null); + } + + //----------------------------------------------------------------------- + // getDate() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_getDate(int year, int month, int day, ZoneOffset offset) { + LocalDate t = LocalDate.of(year, month, day); + assertEquals(OffsetDate.of(LocalDate.of(year, month, day), offset).getDate(), t); + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_compareTo_date() { + OffsetDate a = OffsetDate.of(LocalDate.of(2008, 6, 29), OFFSET_PONE); + OffsetDate b = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); // a is before b due to date + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(a.atTime(LocalTime.MIDNIGHT).toInstant().compareTo(b.atTime(LocalTime.MIDNIGHT).toInstant()) < 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_offset() { + OffsetDate a = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PTWO); + OffsetDate b = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); // a is before b due to offset + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(a.atTime(LocalTime.MIDNIGHT).toInstant().compareTo(b.atTime(LocalTime.MIDNIGHT).toInstant()) < 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_both() { + OffsetDate a = OffsetDate.of(LocalDate.of(2008, 6, 29), OFFSET_PTWO); + OffsetDate b = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); // a is before b on instant scale + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(a.atTime(LocalTime.MIDNIGHT).toInstant().compareTo(b.atTime(LocalTime.MIDNIGHT).toInstant()) < 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_24hourDifference() { + OffsetDate a = OffsetDate.of(LocalDate.of(2008, 6, 29), ZoneOffset.ofHours(-12)); + OffsetDate b = OffsetDate.of(LocalDate.of(2008, 6, 30), ZoneOffset.ofHours(12)); // a is before b despite being same time-line time + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(a.atTime(LocalTime.MIDNIGHT).toInstant().compareTo(b.atTime(LocalTime.MIDNIGHT).toInstant()) == 0, true); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_compareTo_null() { + OffsetDate a = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); + a.compareTo(null); + } + + @Test(expectedExceptions=ClassCastException.class, groups={"tck"}) + @SuppressWarnings({"unchecked", "rawtypes"}) + public void compareToNonOffsetDate() { + Comparable c = TEST_2007_07_15_PONE; + c.compareTo(new Object()); + } + + //----------------------------------------------------------------------- + // isAfter() / isBefore() / isEqual() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_isBeforeIsAfterIsEqual1() { + OffsetDate a = OffsetDate.of(LocalDate.of(2008, 6, 29), OFFSET_PONE); + OffsetDate b = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); // a is before b due to time + assertEquals(a.isBefore(b), true); + assertEquals(a.isEqual(b), false); + assertEquals(a.isAfter(b), false); + + assertEquals(b.isBefore(a), false); + assertEquals(b.isEqual(a), false); + assertEquals(b.isAfter(a), true); + + assertEquals(a.isBefore(a), false); + assertEquals(b.isBefore(b), false); + + assertEquals(a.isEqual(a), true); + assertEquals(b.isEqual(b), true); + + assertEquals(a.isAfter(a), false); + assertEquals(b.isAfter(b), false); + } + + @Test(groups={"tck"}) + public void test_isBeforeIsAfterIsEqual2() { + OffsetDate a = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PTWO); + OffsetDate b = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); // a is before b due to offset + assertEquals(a.isBefore(b), true); + assertEquals(a.isEqual(b), false); + assertEquals(a.isAfter(b), false); + + assertEquals(b.isBefore(a), false); + assertEquals(b.isEqual(a), false); + assertEquals(b.isAfter(a), true); + + assertEquals(a.isBefore(a), false); + assertEquals(b.isBefore(b), false); + + assertEquals(a.isEqual(a), true); + assertEquals(b.isEqual(b), true); + + assertEquals(a.isAfter(a), false); + assertEquals(b.isAfter(b), false); + } + + @Test(groups={"tck"}) + public void test_isBeforeIsAfterIsEqual_instantComparison() { + OffsetDate a = OffsetDate.of(LocalDate.of(2008, 6, 30), ZoneOffset.ofHours(12)); + OffsetDate b = OffsetDate.of(LocalDate.of(2008, 6, 29), ZoneOffset.ofHours(-12)); // a is same instant as b + assertEquals(a.isBefore(b), false); + assertEquals(a.isEqual(b), true); + assertEquals(a.isAfter(b), false); + + assertEquals(b.isBefore(a), false); + assertEquals(b.isEqual(a), true); + assertEquals(b.isAfter(a), false); + + assertEquals(a.isBefore(a), false); + assertEquals(b.isBefore(b), false); + + assertEquals(a.isEqual(a), true); + assertEquals(b.isEqual(b), true); + + assertEquals(a.isAfter(a), false); + assertEquals(b.isAfter(b), false); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isBefore_null() { + OffsetDate a = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); + a.isBefore(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isAfter_null() { + OffsetDate a = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); + a.isAfter(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isEqual_null() { + OffsetDate a = OffsetDate.of(LocalDate.of(2008, 6, 30), OFFSET_PONE); + a.isEqual(null); + } + + //----------------------------------------------------------------------- + // equals() / hashCode() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_equals_true(int y, int m, int d, ZoneOffset offset) { + OffsetDate a = OffsetDate.of(LocalDate.of(y, m, d), offset); + OffsetDate b = OffsetDate.of(LocalDate.of(y, m, d), offset); + assertEquals(a.equals(b), true); + assertEquals(a.hashCode() == b.hashCode(), true); + } + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_equals_false_year_differs(int y, int m, int d, ZoneOffset offset) { + OffsetDate a = OffsetDate.of(LocalDate.of(y, m, d), offset); + OffsetDate b = OffsetDate.of(LocalDate.of(y + 1, m, d), offset); + assertEquals(a.equals(b), false); + } + + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_equals_false_month_differs(int y, int m, int d, ZoneOffset offset) { + OffsetDate a = OffsetDate.of(LocalDate.of(y, m, d), offset); + OffsetDate b = OffsetDate.of(LocalDate.of(y, m + 1, d), offset); + assertEquals(a.equals(b), false); + } + + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_equals_false_day_differs(int y, int m, int d, ZoneOffset offset) { + OffsetDate a = OffsetDate.of(LocalDate.of(y, m, d), offset); + OffsetDate b = OffsetDate.of(LocalDate.of(y, m, d + 1), offset); + assertEquals(a.equals(b), false); + } + + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_equals_false_offset_differs(int y, int m, int d, ZoneOffset ignored) { + OffsetDate a = OffsetDate.of(LocalDate.of(y, m, d), OFFSET_PONE); + OffsetDate b = OffsetDate.of(LocalDate.of(y, m, d), OFFSET_PTWO); + assertEquals(a.equals(b), false); + } + + @Test(groups={"tck"}) + public void test_equals_itself_true() { + assertEquals(TEST_2007_07_15_PONE.equals(TEST_2007_07_15_PONE), true); + } + + @Test(groups={"tck"}) + public void test_equals_string_false() { + assertEquals(TEST_2007_07_15_PONE.equals("2007-07-15"), false); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="sampleToString") + Object[][] provider_sampleToString() { + return new Object[][] { + {2008, 7, 5, "Z", "2008-07-05Z"}, + {2008, 7, 5, "+00", "2008-07-05Z"}, + {2008, 7, 5, "+0000", "2008-07-05Z"}, + {2008, 7, 5, "+00:00", "2008-07-05Z"}, + {2008, 7, 5, "+000000", "2008-07-05Z"}, + {2008, 7, 5, "+00:00:00", "2008-07-05Z"}, + {2008, 7, 5, "-00", "2008-07-05Z"}, + {2008, 7, 5, "-0000", "2008-07-05Z"}, + {2008, 7, 5, "-00:00", "2008-07-05Z"}, + {2008, 7, 5, "-000000", "2008-07-05Z"}, + {2008, 7, 5, "-00:00:00", "2008-07-05Z"}, + {2008, 7, 5, "+01", "2008-07-05+01:00"}, + {2008, 7, 5, "+0100", "2008-07-05+01:00"}, + {2008, 7, 5, "+01:00", "2008-07-05+01:00"}, + {2008, 7, 5, "+010000", "2008-07-05+01:00"}, + {2008, 7, 5, "+01:00:00", "2008-07-05+01:00"}, + {2008, 7, 5, "+0130", "2008-07-05+01:30"}, + {2008, 7, 5, "+01:30", "2008-07-05+01:30"}, + {2008, 7, 5, "+013000", "2008-07-05+01:30"}, + {2008, 7, 5, "+01:30:00", "2008-07-05+01:30"}, + {2008, 7, 5, "+013040", "2008-07-05+01:30:40"}, + {2008, 7, 5, "+01:30:40", "2008-07-05+01:30:40"}, + }; + } + + @Test(dataProvider="sampleToString", groups={"tck"}) + public void test_toString(int y, int m, int d, String offsetId, String expected) { + OffsetDate t = OffsetDate.of(LocalDate.of(y, m, d), ZoneOffset.of(offsetId)); + String str = t.toString(); + assertEquals(str, expected); + } + + //----------------------------------------------------------------------- + // toString(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d"); + String t = OffsetDate.of(LocalDate.of(2010, 12, 3), OFFSET_PONE).toString(f); + assertEquals(t, "2010 12 3"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_toString_formatter_null() { + OffsetDate.of(LocalDate.of(2010, 12, 3), OFFSET_PONE).toString(null); + } + +} diff --git a/test/java/time/tck/java/time/temporal/TCKOffsetDateTime.java b/test/java/time/tck/java/time/temporal/TCKOffsetDateTime.java new file mode 100644 index 0000000000000000000000000000000000000000..01d72a4a661a6fc6bb510e45eb8cab4963d3acef --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TCKOffsetDateTime.java @@ -0,0 +1,1488 @@ +/* + * 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. + * + * 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 tck.java.time.temporal; + +import static java.time.Month.DECEMBER; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; +import static java.time.temporal.ChronoField.AMPM_OF_DAY; +import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM; +import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.EPOCH_MONTH; +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoField.HOUR_OF_AMPM; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.INSTANT_SECONDS; +import static java.time.temporal.ChronoField.MICRO_OF_DAY; +import static java.time.temporal.ChronoField.MICRO_OF_SECOND; +import static java.time.temporal.ChronoField.MILLI_OF_DAY; +import static java.time.temporal.ChronoField.MILLI_OF_SECOND; +import static java.time.temporal.ChronoField.MINUTE_OF_DAY; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.NANO_OF_DAY; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.OFFSET_SECONDS; +import static java.time.temporal.ChronoField.SECOND_OF_DAY; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import java.time.Clock; +import java.time.DateTimeException; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.ISOChrono; +import java.time.temporal.JulianFields; +import java.time.temporal.OffsetDate; +import java.time.temporal.OffsetDateTime; +import java.time.temporal.OffsetTime; +import java.time.temporal.Queries; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.Year; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import tck.java.time.AbstractDateTimeTest; +import test.java.time.MockSimplePeriod; + +/** + * Test OffsetDateTime. + */ +@Test +public class TCKOffsetDateTime extends AbstractDateTimeTest { + + private static final ZoneId ZONE_PARIS = ZoneId.of("Europe/Paris"); + private static final ZoneId ZONE_GAZA = ZoneId.of("Asia/Gaza"); + private static final ZoneOffset OFFSET_PONE = ZoneOffset.ofHours(1); + private static final ZoneOffset OFFSET_PTWO = ZoneOffset.ofHours(2); + private static final ZoneOffset OFFSET_MONE = ZoneOffset.ofHours(-1); + private static final ZoneOffset OFFSET_MTWO = ZoneOffset.ofHours(-2); + private OffsetDateTime TEST_2008_6_30_11_30_59_000000500; + + @BeforeMethod(groups={"tck","implementation"}) + public void setUp() { + TEST_2008_6_30_11_30_59_000000500 = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59, 500), OFFSET_PONE); + } + + //----------------------------------------------------------------------- + @Override + protected List samples() { + TemporalAccessor[] array = {TEST_2008_6_30_11_30_59_000000500, OffsetDateTime.MIN, OffsetDateTime.MAX}; + return Arrays.asList(array); + } + + @Override + protected List validFields() { + TemporalField[] array = { + NANO_OF_SECOND, + NANO_OF_DAY, + MICRO_OF_SECOND, + MICRO_OF_DAY, + MILLI_OF_SECOND, + MILLI_OF_DAY, + SECOND_OF_MINUTE, + SECOND_OF_DAY, + MINUTE_OF_HOUR, + MINUTE_OF_DAY, + CLOCK_HOUR_OF_AMPM, + HOUR_OF_AMPM, + CLOCK_HOUR_OF_DAY, + HOUR_OF_DAY, + AMPM_OF_DAY, + DAY_OF_WEEK, + ALIGNED_DAY_OF_WEEK_IN_MONTH, + ALIGNED_DAY_OF_WEEK_IN_YEAR, + DAY_OF_MONTH, + DAY_OF_YEAR, + EPOCH_DAY, + ALIGNED_WEEK_OF_MONTH, + ALIGNED_WEEK_OF_YEAR, + MONTH_OF_YEAR, + EPOCH_MONTH, + YEAR_OF_ERA, + YEAR, + ERA, + OFFSET_SECONDS, + INSTANT_SECONDS, + JulianFields.JULIAN_DAY, + JulianFields.MODIFIED_JULIAN_DAY, + JulianFields.RATA_DIE, + }; + return Arrays.asList(array); + } + + @Override + protected List invalidFields() { + List list = new ArrayList<>(Arrays.asList(ChronoField.values())); + list.removeAll(validFields()); + return list; + } + + //----------------------------------------------------------------------- + @Test + public void test_serialization() throws Exception { + assertSerializable(TEST_2008_6_30_11_30_59_000000500); + assertSerializable(OffsetDateTime.MIN); + assertSerializable(OffsetDateTime.MAX); + } + + @Test + public void test_serialization_format() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(3); + } + byte[] bytes = baos.toByteArray(); + ByteArrayOutputStream baosDateTime = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baosDateTime) ) { + dos.writeByte(5); + dos.writeInt(2012); + dos.writeByte(9); + dos.writeByte(16); + dos.writeByte(22); + dos.writeByte(17); + dos.writeByte(59); + dos.writeInt(464_000_000); + } + byte[] bytesDateTime = baosDateTime.toByteArray(); + ByteArrayOutputStream baosOffset = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baosOffset) ) { + dos.writeByte(8); + dos.writeByte(4); // quarter hours stored: 3600 / 900 + } + byte[] bytesOffset = baosOffset.toByteArray(); + LocalDateTime ldt = LocalDateTime.of(2012, 9, 16, 22, 17, 59, 464_000_000); + assertSerializedBySer(OffsetDateTime.of(ldt, ZoneOffset.ofHours(1)), bytes, bytesDateTime, bytesOffset); + } + + //----------------------------------------------------------------------- + // constants + //----------------------------------------------------------------------- + @Test + public void constant_MIN() { + check(OffsetDateTime.MIN, Year.MIN_VALUE, 1, 1, 0, 0, 0, 0, ZoneOffset.MAX); + } + + @Test + public void constant_MAX() { + check(OffsetDateTime.MAX, Year.MAX_VALUE, 12, 31, 23, 59, 59, 999999999, ZoneOffset.MIN); + } + + //----------------------------------------------------------------------- + // now() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now() { + OffsetDateTime expected = OffsetDateTime.now(Clock.systemDefaultZone()); + OffsetDateTime test = OffsetDateTime.now(); + long diff = Math.abs(test.getTime().toNanoOfDay() - expected.getTime().toNanoOfDay()); + if (diff >= 100000000) { + // may be date change + expected = OffsetDateTime.now(Clock.systemDefaultZone()); + test = OffsetDateTime.now(); + diff = Math.abs(test.getTime().toNanoOfDay() - expected.getTime().toNanoOfDay()); + } + assertTrue(diff < 100000000); // less than 0.1 secs + } + + //----------------------------------------------------------------------- + // now(Clock) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay_utc() { + for (int i = 0; i < (2 * 24 * 60 * 60); i++) { + Instant instant = Instant.ofEpochSecond(i).plusNanos(123456789L); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + OffsetDateTime test = OffsetDateTime.now(clock); + assertEquals(test.getYear(), 1970); + assertEquals(test.getMonth(), Month.JANUARY); + assertEquals(test.getDayOfMonth(), (i < 24 * 60 * 60 ? 1 : 2)); + assertEquals(test.getHour(), (i / (60 * 60)) % 24); + assertEquals(test.getMinute(), (i / 60) % 60); + assertEquals(test.getSecond(), i % 60); + assertEquals(test.getNano(), 123456789); + assertEquals(test.getOffset(), ZoneOffset.UTC); + } + } + + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay_offset() { + for (int i = 0; i < (2 * 24 * 60 * 60); i++) { + Instant instant = Instant.ofEpochSecond(i).plusNanos(123456789L); + Clock clock = Clock.fixed(instant.minusSeconds(OFFSET_PONE.getTotalSeconds()), OFFSET_PONE); + OffsetDateTime test = OffsetDateTime.now(clock); + assertEquals(test.getYear(), 1970); + assertEquals(test.getMonth(), Month.JANUARY); + assertEquals(test.getDayOfMonth(), (i < 24 * 60 * 60) ? 1 : 2); + assertEquals(test.getHour(), (i / (60 * 60)) % 24); + assertEquals(test.getMinute(), (i / 60) % 60); + assertEquals(test.getSecond(), i % 60); + assertEquals(test.getNano(), 123456789); + assertEquals(test.getOffset(), OFFSET_PONE); + } + } + + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay_beforeEpoch() { + LocalTime expected = LocalTime.MIDNIGHT.plusNanos(123456789L); + for (int i =-1; i >= -(24 * 60 * 60); i--) { + Instant instant = Instant.ofEpochSecond(i).plusNanos(123456789L); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + OffsetDateTime test = OffsetDateTime.now(clock); + assertEquals(test.getYear(), 1969); + assertEquals(test.getMonth(), Month.DECEMBER); + assertEquals(test.getDayOfMonth(), 31); + expected = expected.minusSeconds(1); + assertEquals(test.getTime(), expected); + assertEquals(test.getOffset(), ZoneOffset.UTC); + } + } + + @Test(groups={"tck"}) + public void now_Clock_offsets() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(1970, 1, 1), LocalTime.of(12, 0), ZoneOffset.UTC); + for (int i = -9; i < 15; i++) { + ZoneOffset offset = ZoneOffset.ofHours(i); + Clock clock = Clock.fixed(base.toInstant(), offset); + OffsetDateTime test = OffsetDateTime.now(clock); + assertEquals(test.getHour(), (12 + i) % 24); + assertEquals(test.getMinute(), 0); + assertEquals(test.getSecond(), 0); + assertEquals(test.getNano(), 0); + assertEquals(test.getOffset(), offset); + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_Clock_nullZoneId() { + OffsetDateTime.now((ZoneId) null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_Clock_nullClock() { + OffsetDateTime.now((Clock) null); + } + + //----------------------------------------------------------------------- + private void check(OffsetDateTime test, int y, int mo, int d, int h, int m, int s, int n, ZoneOffset offset) { + assertEquals(test.getYear(), y); + assertEquals(test.getMonth().getValue(), mo); + assertEquals(test.getDayOfMonth(), d); + assertEquals(test.getHour(), h); + assertEquals(test.getMinute(), m); + assertEquals(test.getSecond(), s); + assertEquals(test.getNano(), n); + assertEquals(test.getOffset(), offset); + assertEquals(test, test); + assertEquals(test.hashCode(), test.hashCode()); + assertEquals(OffsetDateTime.of(LocalDateTime.of(y, mo, d, h, m, s, n), offset), test); + } + + //----------------------------------------------------------------------- + // dateTime factories + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_intMonthIntHM() { + OffsetDateTime test = OffsetDateTime.of(LocalDate.of(2008, Month.JUNE, 30), + LocalTime.of(11, 30), OFFSET_PONE); + check(test, 2008, 6, 30, 11, 30, 0, 0, OFFSET_PONE); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_intMonthIntHMS() { + OffsetDateTime test = OffsetDateTime.of(LocalDate.of(2008, Month.JUNE, 30), + LocalTime.of(11, 30, 10), OFFSET_PONE); + check(test, 2008, 6, 30, 11, 30, 10, 0, OFFSET_PONE); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_intMonthIntHMSN() { + OffsetDateTime test = OffsetDateTime.of(LocalDate.of(2008, Month.JUNE, 30), + LocalTime.of(11, 30, 10, 500), OFFSET_PONE); + check(test, 2008, 6, 30, 11, 30, 10, 500, OFFSET_PONE); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_intsHM() { + OffsetDateTime test = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30), OFFSET_PONE); + check(test, 2008, 6, 30, 11, 30, 0, 0, OFFSET_PONE); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_intsHMS() { + OffsetDateTime test = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 10), OFFSET_PONE); + check(test, 2008, 6, 30, 11, 30, 10, 0, OFFSET_PONE); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_intsHMSN() { + OffsetDateTime test = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 10, 500), OFFSET_PONE); + check(test, 2008, 6, 30, 11, 30, 10, 500, OFFSET_PONE); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_LocalDateLocalTimeZoneOffset() { + LocalDate date = LocalDate.of(2008, 6, 30); + LocalTime time = LocalTime.of(11, 30, 10, 500); + OffsetDateTime test = OffsetDateTime.of(date, time, OFFSET_PONE); + check(test, 2008, 6, 30, 11, 30, 10, 500, OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_LocalDateLocalTimeZoneOffset_nullLocalDate() { + LocalTime time = LocalTime.of(11, 30, 10, 500); + OffsetDateTime.of((LocalDate) null, time, OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_LocalDateLocalTimeZoneOffset_nullLocalTime() { + LocalDate date = LocalDate.of(2008, 6, 30); + OffsetDateTime.of(date, (LocalTime) null, OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_LocalDateLocalTimeZoneOffset_nullOffset() { + LocalDate date = LocalDate.of(2008, 6, 30); + LocalTime time = LocalTime.of(11, 30, 10, 500); + OffsetDateTime.of(date, time, (ZoneOffset) null); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_LocalDateTimeZoneOffset() { + LocalDateTime dt = LocalDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 10, 500)); + OffsetDateTime test = OffsetDateTime.of(dt, OFFSET_PONE); + check(test, 2008, 6, 30, 11, 30, 10, 500, OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_LocalDateTimeZoneOffset_nullProvider() { + OffsetDateTime.of((LocalDateTime) null, OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_LocalDateTimeZoneOffset_nullOffset() { + LocalDateTime dt = LocalDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 10, 500)); + OffsetDateTime.of(dt, (ZoneOffset) null); + } + + //----------------------------------------------------------------------- + // from() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_CalendricalObject() { + assertEquals(OffsetDateTime.from( + OffsetDateTime.of(LocalDate.of(2007, 7, 15), LocalTime.of(17, 30), OFFSET_PONE)), + OffsetDateTime.of(LocalDate.of(2007, 7, 15), LocalTime.of(17, 30), OFFSET_PONE)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_CalendricalObject_invalid_noDerive() { + OffsetDateTime.from(LocalTime.of(12, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_Calendricals_null() { + OffsetDateTime.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // parse() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleToString", groups={"tck"}) + public void test_parse(int y, int month, int d, int h, int m, int s, int n, String offsetId, String text) { + OffsetDateTime t = OffsetDateTime.parse(text); + assertEquals(t.getYear(), y); + assertEquals(t.getMonth().getValue(), month); + assertEquals(t.getDayOfMonth(), d); + assertEquals(t.getHour(), h); + assertEquals(t.getMinute(), m); + assertEquals(t.getSecond(), s); + assertEquals(t.getNano(), n); + assertEquals(t.getOffset().getId(), offsetId); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_illegalValue() { + OffsetDateTime.parse("2008-06-32T11:15+01:00"); + } + + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_invalidValue() { + OffsetDateTime.parse("2008-06-31T11:15+01:00"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_nullText() { + OffsetDateTime.parse((String) null); + } + + //----------------------------------------------------------------------- + // parse(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_parse_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d H m s XXX"); + OffsetDateTime test = OffsetDateTime.parse("2010 12 3 11 30 0 +01:00", f); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2010, 12, 3), LocalTime.of(11, 30), ZoneOffset.ofHours(1))); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullText() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d H m s"); + OffsetDateTime.parse((String) null, f); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullFormatter() { + OffsetDateTime.parse("ANY", null); + } + + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void constructor_nullTime() throws Throwable { + Constructor con = OffsetDateTime.class.getDeclaredConstructor(LocalDateTime.class, ZoneOffset.class); + con.setAccessible(true); + try { + con.newInstance(null, OFFSET_PONE); + } catch (InvocationTargetException ex) { + throw ex.getCause(); + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void constructor_nullOffset() throws Throwable { + Constructor con = OffsetDateTime.class.getDeclaredConstructor(LocalDateTime.class, ZoneOffset.class); + con.setAccessible(true); + try { + con.newInstance(LocalDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30)), null); + } catch (InvocationTargetException ex) { + throw ex.getCause(); + } + } + + //----------------------------------------------------------------------- + // basics + //----------------------------------------------------------------------- + @DataProvider(name="sampleTimes") + Object[][] provider_sampleTimes() { + return new Object[][] { + {2008, 6, 30, 11, 30, 20, 500, OFFSET_PONE}, + {2008, 6, 30, 11, 0, 0, 0, OFFSET_PONE}, + {2008, 6, 30, 23, 59, 59, 999999999, OFFSET_PONE}, + {-1, 1, 1, 0, 0, 0, 0, OFFSET_PONE}, + }; + } + + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_get(int y, int o, int d, int h, int m, int s, int n, ZoneOffset offset) { + LocalDate localDate = LocalDate.of(y, o, d); + LocalTime localTime = LocalTime.of(h, m, s, n); + LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime); + OffsetDateTime a = OffsetDateTime.of(localDateTime, offset); + + assertEquals(a.getYear(), localDate.getYear()); + assertEquals(a.getMonth(), localDate.getMonth()); + assertEquals(a.getDayOfMonth(), localDate.getDayOfMonth()); + assertEquals(a.getDayOfYear(), localDate.getDayOfYear()); + assertEquals(a.getDayOfWeek(), localDate.getDayOfWeek()); + + assertEquals(a.getHour(), localDateTime.getHour()); + assertEquals(a.getMinute(), localDateTime.getMinute()); + assertEquals(a.getSecond(), localDateTime.getSecond()); + assertEquals(a.getNano(), localDateTime.getNano()); + + assertEquals(a.toOffsetDate(), OffsetDate.of(localDate, offset)); + assertEquals(a.toOffsetTime(), OffsetTime.of(localTime, offset)); + assertEquals(a.toString(), localDateTime.toString() + offset.toString()); + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @Test + public void test_get_TemporalField() { + OffsetDateTime test = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(12, 30, 40, 987654321), OFFSET_PONE); + assertEquals(test.get(ChronoField.YEAR), 2008); + assertEquals(test.get(ChronoField.MONTH_OF_YEAR), 6); + assertEquals(test.get(ChronoField.DAY_OF_MONTH), 30); + assertEquals(test.get(ChronoField.DAY_OF_WEEK), 1); + assertEquals(test.get(ChronoField.DAY_OF_YEAR), 182); + + assertEquals(test.get(ChronoField.HOUR_OF_DAY), 12); + assertEquals(test.get(ChronoField.MINUTE_OF_HOUR), 30); + assertEquals(test.get(ChronoField.SECOND_OF_MINUTE), 40); + assertEquals(test.get(ChronoField.NANO_OF_SECOND), 987654321); + assertEquals(test.get(ChronoField.HOUR_OF_AMPM), 0); + assertEquals(test.get(ChronoField.AMPM_OF_DAY), 1); + + assertEquals(test.get(ChronoField.OFFSET_SECONDS), 3600); + } + + @Test + public void test_getLong_TemporalField() { + OffsetDateTime test = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(12, 30, 40, 987654321), OFFSET_PONE); + assertEquals(test.getLong(ChronoField.YEAR), 2008); + assertEquals(test.getLong(ChronoField.MONTH_OF_YEAR), 6); + assertEquals(test.getLong(ChronoField.DAY_OF_MONTH), 30); + assertEquals(test.getLong(ChronoField.DAY_OF_WEEK), 1); + assertEquals(test.getLong(ChronoField.DAY_OF_YEAR), 182); + + assertEquals(test.getLong(ChronoField.HOUR_OF_DAY), 12); + assertEquals(test.getLong(ChronoField.MINUTE_OF_HOUR), 30); + assertEquals(test.getLong(ChronoField.SECOND_OF_MINUTE), 40); + assertEquals(test.getLong(ChronoField.NANO_OF_SECOND), 987654321); + assertEquals(test.getLong(ChronoField.HOUR_OF_AMPM), 0); + assertEquals(test.getLong(ChronoField.AMPM_OF_DAY), 1); + + assertEquals(test.getLong(ChronoField.INSTANT_SECONDS), test.toEpochSecond()); + assertEquals(test.getLong(ChronoField.OFFSET_SECONDS), 3600); + } + + //----------------------------------------------------------------------- + // query(TemporalQuery) + //----------------------------------------------------------------------- + @Test + public void test_query_chrono() { + assertEquals(TEST_2008_6_30_11_30_59_000000500.query(Queries.chrono()), ISOChrono.INSTANCE); + assertEquals(Queries.chrono().queryFrom(TEST_2008_6_30_11_30_59_000000500), ISOChrono.INSTANCE); + } + + @Test + public void test_query_zoneId() { + assertEquals(TEST_2008_6_30_11_30_59_000000500.query(Queries.zoneId()), null); + assertEquals(Queries.zoneId().queryFrom(TEST_2008_6_30_11_30_59_000000500), null); + } + + @Test + public void test_query_precision() { + assertEquals(TEST_2008_6_30_11_30_59_000000500.query(Queries.precision()), NANOS); + assertEquals(Queries.precision().queryFrom(TEST_2008_6_30_11_30_59_000000500), NANOS); + } + + @Test + public void test_query_offset() { + assertEquals(TEST_2008_6_30_11_30_59_000000500.query(Queries.offset()), OFFSET_PONE); + assertEquals(Queries.offset().queryFrom(TEST_2008_6_30_11_30_59_000000500), OFFSET_PONE); + } + + @Test + public void test_query_zone() { + assertEquals(TEST_2008_6_30_11_30_59_000000500.query(Queries.zone()), OFFSET_PONE); + assertEquals(Queries.zone().queryFrom(TEST_2008_6_30_11_30_59_000000500), OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_query_null() { + TEST_2008_6_30_11_30_59_000000500.query(null); + } + + //----------------------------------------------------------------------- + // with(WithAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_with_adjustment() { + final OffsetDateTime sample = OffsetDateTime.of(LocalDate.of(2012, 3, 4), LocalTime.of(23, 5), OFFSET_PONE); + TemporalAdjuster adjuster = new TemporalAdjuster() { + @Override + public Temporal adjustInto(Temporal dateTime) { + return sample; + } + }; + assertEquals(TEST_2008_6_30_11_30_59_000000500.with(adjuster), sample); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_LocalDate() { + OffsetDateTime test = TEST_2008_6_30_11_30_59_000000500.with(LocalDate.of(2012, 9, 3)); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2012, 9, 3), LocalTime.of(11, 30, 59, 500), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_LocalTime() { + OffsetDateTime test = TEST_2008_6_30_11_30_59_000000500.with(LocalTime.of(19, 15)); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(19, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_LocalDateTime() { + OffsetDateTime test = TEST_2008_6_30_11_30_59_000000500.with(LocalDateTime.of(LocalDate.of(2012, 9, 3), LocalTime.of(19, 15))); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2012, 9, 3), LocalTime.of(19, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_OffsetDate() { + OffsetDateTime test = TEST_2008_6_30_11_30_59_000000500.with(OffsetDate.of(LocalDate.of(2012, 9, 3), OFFSET_PTWO)); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2012, 9, 3), LocalTime.of(11, 30, 59, 500), OFFSET_PTWO)); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_OffsetTime() { + OffsetDateTime test = TEST_2008_6_30_11_30_59_000000500.with(OffsetTime.of(LocalTime.of(19, 15), OFFSET_PTWO)); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(19, 15), OFFSET_PTWO)); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_OffsetDateTime() { + OffsetDateTime test = TEST_2008_6_30_11_30_59_000000500.with(OffsetDateTime.of(LocalDate.of(2012, 9, 3), LocalTime.of(19, 15), OFFSET_PTWO)); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2012, 9, 3), LocalTime.of(19, 15), OFFSET_PTWO)); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_Month() { + OffsetDateTime test = TEST_2008_6_30_11_30_59_000000500.with(DECEMBER); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 12, 30),LocalTime.of(11, 30, 59, 500), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_ZoneOffset() { + OffsetDateTime test = TEST_2008_6_30_11_30_59_000000500.with(OFFSET_PTWO); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59, 500), OFFSET_PTWO)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_with_adjustment_null() { + TEST_2008_6_30_11_30_59_000000500.with((TemporalAdjuster) null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_withOffsetSameLocal_null() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + base.withOffsetSameLocal(null); + } + + //----------------------------------------------------------------------- + // withOffsetSameInstant() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withOffsetSameInstant() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withOffsetSameInstant(OFFSET_PTWO); + OffsetDateTime expected = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(12, 30, 59), OFFSET_PTWO); + assertEquals(test, expected); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_withOffsetSameInstant_null() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + base.withOffsetSameInstant(null); + } + + //----------------------------------------------------------------------- + // withYear() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withYear_normal() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withYear(2007); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2007, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // withMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withMonth_normal() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withMonth(1); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 1, 30), LocalTime.of(11, 30, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // withDayOfMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withDayOfMonth_normal() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withDayOfMonth(15); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 15), LocalTime.of(11, 30, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // withDayOfYear(int) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withDayOfYear_normal() { + OffsetDateTime t = TEST_2008_6_30_11_30_59_000000500.withDayOfYear(33); + assertEquals(t, OffsetDateTime.of(LocalDate.of(2008, 2, 2), LocalTime.of(11, 30, 59, 500), OFFSET_PONE)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfYear_illegal() { + TEST_2008_6_30_11_30_59_000000500.withDayOfYear(367); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withDayOfYear_invalid() { + OffsetDateTime.of(LocalDate.of(2007, 2, 2), LocalTime.of(11, 30), OFFSET_PONE).withDayOfYear(366); + } + + //----------------------------------------------------------------------- + // withHour() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withHour_normal() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withHour(15); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(15, 30, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // withMinute() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withMinute_normal() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withMinute(15); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 15, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // withSecond() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withSecond_normal() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withSecond(15); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 15), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // withNano() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withNanoOfSecond_normal() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59, 1), OFFSET_PONE); + OffsetDateTime test = base.withNano(15); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59, 15), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // truncatedTo(TemporalUnit) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_truncatedTo_normal() { + assertEquals(TEST_2008_6_30_11_30_59_000000500.truncatedTo(NANOS), TEST_2008_6_30_11_30_59_000000500); + assertEquals(TEST_2008_6_30_11_30_59_000000500.truncatedTo(SECONDS), TEST_2008_6_30_11_30_59_000000500.withNano(0)); + assertEquals(TEST_2008_6_30_11_30_59_000000500.truncatedTo(DAYS), TEST_2008_6_30_11_30_59_000000500.with(LocalTime.MIDNIGHT)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_truncatedTo_null() { + TEST_2008_6_30_11_30_59_000000500.truncatedTo(null); + } + + //----------------------------------------------------------------------- + // plus(Period) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plus_Period() { + MockSimplePeriod period = MockSimplePeriod.of(7, ChronoUnit.MONTHS); + OffsetDateTime t = TEST_2008_6_30_11_30_59_000000500.plus(period); + assertEquals(t, OffsetDateTime.of(LocalDate.of(2009, 1, 30), LocalTime.of(11, 30, 59, 500), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // plus(Duration) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plus_Duration() { + Duration dur = Duration.ofSeconds(62, 3); + OffsetDateTime t = TEST_2008_6_30_11_30_59_000000500.plus(dur); + assertEquals(t, OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 32, 1, 503), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plus_Duration_zero() { + OffsetDateTime t = TEST_2008_6_30_11_30_59_000000500.plus(Duration.ZERO); + assertEquals(t, TEST_2008_6_30_11_30_59_000000500); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_plus_Duration_null() { + TEST_2008_6_30_11_30_59_000000500.plus((Duration) null); + } + + //----------------------------------------------------------------------- + // plusYears() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusYears() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.plusYears(1); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2009, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // plusMonths() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusMonths() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.plusMonths(1); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 7, 30), LocalTime.of(11, 30, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // plusWeeks() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusWeeks() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.plusWeeks(1); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 7, 7), LocalTime.of(11, 30, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // plusDays() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusDays() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.plusDays(1); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 7, 1), LocalTime.of(11, 30, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // plusHours() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusHours() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.plusHours(13); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 7, 1), LocalTime.of(0, 30, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // plusMinutes() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusMinutes() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.plusMinutes(30); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(12, 0, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // plusSeconds() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusSeconds() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.plusSeconds(1); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 31, 0), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // plusNanos() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusNanos() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59, 0), OFFSET_PONE); + OffsetDateTime test = base.plusNanos(1); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59, 1), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // minus(Period) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minus_Period() { + MockSimplePeriod period = MockSimplePeriod.of(7, ChronoUnit.MONTHS); + OffsetDateTime t = TEST_2008_6_30_11_30_59_000000500.minus(period); + assertEquals(t, OffsetDateTime.of(LocalDate.of(2007, 11, 30), LocalTime.of(11, 30, 59, 500), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // minus(Duration) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minus_Duration() { + Duration dur = Duration.ofSeconds(62, 3); + OffsetDateTime t = TEST_2008_6_30_11_30_59_000000500.minus(dur); + assertEquals(t, OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 29, 57, 497), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minus_Duration_zero() { + OffsetDateTime t = TEST_2008_6_30_11_30_59_000000500.minus(Duration.ZERO); + assertEquals(t, TEST_2008_6_30_11_30_59_000000500); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_minus_Duration_null() { + TEST_2008_6_30_11_30_59_000000500.minus((Duration) null); + } + + //----------------------------------------------------------------------- + // minusYears() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusYears() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.minusYears(1); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2007, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // minusMonths() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusMonths() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.minusMonths(1); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 5, 30), LocalTime.of(11, 30, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // minusWeeks() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusWeeks() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.minusWeeks(1); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 23), LocalTime.of(11, 30, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // minusDays() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusDays() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.minusDays(1); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 29), LocalTime.of(11, 30, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // minusHours() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusHours() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.minusHours(13); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 29), LocalTime.of(22, 30, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // minusMinutes() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusMinutes() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.minusMinutes(30); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 0, 59), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // minusSeconds() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusSeconds() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.minusSeconds(1); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 58), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // minusNanos() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusNanos() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59, 0), OFFSET_PONE); + OffsetDateTime test = base.minusNanos(1); + assertEquals(test, OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 58, 999999999), OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // atZoneSameInstant() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atZone() { + OffsetDateTime t = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30), OFFSET_MTWO); + assertEquals(t.atZoneSameInstant(ZONE_PARIS), + ZonedDateTime.of(LocalDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(15, 30)), ZONE_PARIS)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_atZone_nullTimeZone() { + OffsetDateTime t = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30), OFFSET_PTWO); + t.atZoneSameInstant((ZoneId) null); + } + + //----------------------------------------------------------------------- + // atZoneSimilarLocal() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atZoneSimilarLocal() { + OffsetDateTime t = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30), OFFSET_MTWO); + assertEquals(t.atZoneSimilarLocal(ZONE_PARIS), + ZonedDateTime.of(LocalDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30)), ZONE_PARIS)); + } + + @Test(groups={"tck"}) + public void test_atZoneSimilarLocal_dstGap() { + OffsetDateTime t = OffsetDateTime.of(LocalDate.of(2007, 4, 1), LocalTime.of(0, 0), OFFSET_MTWO); + assertEquals(t.atZoneSimilarLocal(ZONE_GAZA), + ZonedDateTime.of(LocalDateTime.of(LocalDate.of(2007, 4, 1), LocalTime.of(1, 0)), ZONE_GAZA)); + } + + @Test(groups={"tck"}) + public void test_atZone_dstOverlapSummer() { + OffsetDateTime t = OffsetDateTime.of(LocalDate.of(2007, 10, 28), LocalTime.of(2, 30), OFFSET_PTWO); + assertEquals(t.atZoneSimilarLocal(ZONE_PARIS).getDateTime(), t.getDateTime()); + assertEquals(t.atZoneSimilarLocal(ZONE_PARIS).getOffset(), OFFSET_PTWO); + assertEquals(t.atZoneSimilarLocal(ZONE_PARIS).getZone(), ZONE_PARIS); + } + + @Test(groups={"tck"}) + public void test_atZone_dstOverlapWinter() { + OffsetDateTime t = OffsetDateTime.of(LocalDate.of(2007, 10, 28), LocalTime.of(2, 30), OFFSET_PONE); + assertEquals(t.atZoneSimilarLocal(ZONE_PARIS).getDateTime(), t.getDateTime()); + assertEquals(t.atZoneSimilarLocal(ZONE_PARIS).getOffset(), OFFSET_PONE); + assertEquals(t.atZoneSimilarLocal(ZONE_PARIS).getZone(), ZONE_PARIS); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_atZoneSimilarLocal_nullTimeZone() { + OffsetDateTime t = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30), OFFSET_PTWO); + t.atZoneSimilarLocal((ZoneId) null); + } + + //----------------------------------------------------------------------- + // toEpochSecond() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toEpochSecond_afterEpoch() { + for (int i = 0; i < 100000; i++) { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(1970, 1, 1), LocalTime.of(0, 0), ZoneOffset.UTC).plusSeconds(i); + assertEquals(a.toEpochSecond(), i); + } + } + + @Test(groups={"tck"}) + public void test_toEpochSecond_beforeEpoch() { + for (int i = 0; i < 100000; i++) { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(1970, 1, 1), LocalTime.of(0, 0), ZoneOffset.UTC).minusSeconds(i); + assertEquals(a.toEpochSecond(), -i); + } + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_compareTo_timeMins() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 29, 3), OFFSET_PONE); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 2), OFFSET_PONE); // a is before b due to time + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(a.toInstant().compareTo(b.toInstant()) < 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_timeSecs() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 29, 2), OFFSET_PONE); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 29, 3), OFFSET_PONE); // a is before b due to time + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(a.toInstant().compareTo(b.toInstant()) < 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_timeNanos() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 29, 40, 4), OFFSET_PONE); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 29, 40, 5), OFFSET_PONE); // a is before b due to time + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(a.toInstant().compareTo(b.toInstant()) < 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_offset() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30), OFFSET_PTWO); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30), OFFSET_PONE); // a is before b due to offset + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(a.toInstant().compareTo(b.toInstant()) < 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_offsetNanos() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 40, 6), OFFSET_PTWO); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 40, 5), OFFSET_PONE); // a is before b due to offset + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(a.toInstant().compareTo(b.toInstant()) < 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_both() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 50), OFFSET_PTWO); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 20), OFFSET_PONE); // a is before b on instant scale + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(a.toInstant().compareTo(b.toInstant()) < 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_bothNanos() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 20, 40, 4), OFFSET_PTWO); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(10, 20, 40, 5), OFFSET_PONE); // a is before b on instant scale + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(a.toInstant().compareTo(b.toInstant()) < 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_hourDifference() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(10, 0), OFFSET_PONE); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 0), OFFSET_PTWO); // a is before b despite being same time-line time + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(a.toInstant().compareTo(b.toInstant()) == 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_max() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(Year.MAX_VALUE, 12, 31), LocalTime.of(23, 59), OFFSET_MONE); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(Year.MAX_VALUE, 12, 31), LocalTime.of(23, 59), OFFSET_MTWO); // a is before b due to offset + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_min() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(Year.MIN_VALUE, 1, 1), LocalTime.of(0, 0), OFFSET_PTWO); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(Year.MIN_VALUE, 1, 1), LocalTime.of(0, 0), OFFSET_PONE); // a is before b due to offset + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_compareTo_null() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + a.compareTo(null); + } + + @Test(expectedExceptions=ClassCastException.class, groups={"tck"}) + @SuppressWarnings({"unchecked", "rawtypes"}) + public void compareToNonOffsetDateTime() { + Comparable c = TEST_2008_6_30_11_30_59_000000500; + c.compareTo(new Object()); + } + + //----------------------------------------------------------------------- + // isAfter() / isBefore() / isEqual() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_isBeforeIsAfterIsEqual1() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 58, 3), OFFSET_PONE); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59, 2), OFFSET_PONE); // a is before b due to time + assertEquals(a.isBefore(b), true); + assertEquals(a.isEqual(b), false); + assertEquals(a.isAfter(b), false); + + assertEquals(b.isBefore(a), false); + assertEquals(b.isEqual(a), false); + assertEquals(b.isAfter(a), true); + + assertEquals(a.isBefore(a), false); + assertEquals(b.isBefore(b), false); + + assertEquals(a.isEqual(a), true); + assertEquals(b.isEqual(b), true); + + assertEquals(a.isAfter(a), false); + assertEquals(b.isAfter(b), false); + } + + @Test(groups={"tck"}) + public void test_isBeforeIsAfterIsEqual2() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59, 2), OFFSET_PONE); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59, 3), OFFSET_PONE); // a is before b due to time + assertEquals(a.isBefore(b), true); + assertEquals(a.isEqual(b), false); + assertEquals(a.isAfter(b), false); + + assertEquals(b.isBefore(a), false); + assertEquals(b.isEqual(a), false); + assertEquals(b.isAfter(a), true); + + assertEquals(a.isBefore(a), false); + assertEquals(b.isBefore(b), false); + + assertEquals(a.isEqual(a), true); + assertEquals(b.isEqual(b), true); + + assertEquals(a.isAfter(a), false); + assertEquals(b.isAfter(b), false); + } + + @Test(groups={"tck"}) + public void test_isBeforeIsAfterIsEqual_instantComparison() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(10, 0), OFFSET_PONE); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 0), OFFSET_PTWO); // a is same instant as b + assertEquals(a.isBefore(b), false); + assertEquals(a.isEqual(b), true); + assertEquals(a.isAfter(b), false); + + assertEquals(b.isBefore(a), false); + assertEquals(b.isEqual(a), true); + assertEquals(b.isAfter(a), false); + + assertEquals(a.isBefore(a), false); + assertEquals(b.isBefore(b), false); + + assertEquals(a.isEqual(a), true); + assertEquals(b.isEqual(b), true); + + assertEquals(a.isAfter(a), false); + assertEquals(b.isAfter(b), false); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isBefore_null() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + a.isBefore(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isEqual_null() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + a.isEqual(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isAfter_null() { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + a.isAfter(null); + } + + //----------------------------------------------------------------------- + // equals() / hashCode() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_true(int y, int o, int d, int h, int m, int s, int n, ZoneOffset ignored) { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(y, o, d), LocalTime.of(h, m, s, n), OFFSET_PONE); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(y, o, d), LocalTime.of(h, m, s, n), OFFSET_PONE); + assertEquals(a.equals(b), true); + assertEquals(a.hashCode() == b.hashCode(), true); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_year_differs(int y, int o, int d, int h, int m, int s, int n, ZoneOffset ignored) { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(y, o, d), LocalTime.of(h, m, s, n), OFFSET_PONE); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(y + 1, o, d), LocalTime.of(h, m, s, n), OFFSET_PONE); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_hour_differs(int y, int o, int d, int h, int m, int s, int n, ZoneOffset ignored) { + h = (h == 23 ? 22 : h); + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(y, o, d), LocalTime.of(h, m, s, n), OFFSET_PONE); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(y, o, d), LocalTime.of(h + 1, m, s, n), OFFSET_PONE); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_minute_differs(int y, int o, int d, int h, int m, int s, int n, ZoneOffset ignored) { + m = (m == 59 ? 58 : m); + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(y, o, d), LocalTime.of(h, m, s, n), OFFSET_PONE); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(y, o, d), LocalTime.of(h, m + 1, s, n), OFFSET_PONE); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_second_differs(int y, int o, int d, int h, int m, int s, int n, ZoneOffset ignored) { + s = (s == 59 ? 58 : s); + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(y, o, d), LocalTime.of(h, m, s, n), OFFSET_PONE); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(y, o, d), LocalTime.of(h, m, s + 1, n), OFFSET_PONE); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_nano_differs(int y, int o, int d, int h, int m, int s, int n, ZoneOffset ignored) { + n = (n == 999999999 ? 999999998 : n); + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(y, o, d), LocalTime.of(h, m, s, n), OFFSET_PONE); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(y, o, d), LocalTime.of(h, m, s, n + 1), OFFSET_PONE); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_offset_differs(int y, int o, int d, int h, int m, int s, int n, ZoneOffset ignored) { + OffsetDateTime a = OffsetDateTime.of(LocalDate.of(y, o, d), LocalTime.of(h, m, s, n), OFFSET_PONE); + OffsetDateTime b = OffsetDateTime.of(LocalDate.of(y, o, d), LocalTime.of(h, m, s, n), OFFSET_PTWO); + assertEquals(a.equals(b), false); + } + + @Test(groups={"tck"}) + public void test_equals_itself_true() { + assertEquals(TEST_2008_6_30_11_30_59_000000500.equals(TEST_2008_6_30_11_30_59_000000500), true); + } + + @Test(groups={"tck"}) + public void test_equals_string_false() { + assertEquals(TEST_2008_6_30_11_30_59_000000500.equals("2007-07-15"), false); + } + + @Test(groups={"tck"}) + public void test_equals_null_false() { + assertEquals(TEST_2008_6_30_11_30_59_000000500.equals(null), false); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="sampleToString") + Object[][] provider_sampleToString() { + return new Object[][] { + {2008, 6, 30, 11, 30, 59, 0, "Z", "2008-06-30T11:30:59Z"}, + {2008, 6, 30, 11, 30, 59, 0, "+01:00", "2008-06-30T11:30:59+01:00"}, + {2008, 6, 30, 11, 30, 59, 999000000, "Z", "2008-06-30T11:30:59.999Z"}, + {2008, 6, 30, 11, 30, 59, 999000000, "+01:00", "2008-06-30T11:30:59.999+01:00"}, + {2008, 6, 30, 11, 30, 59, 999000, "Z", "2008-06-30T11:30:59.000999Z"}, + {2008, 6, 30, 11, 30, 59, 999000, "+01:00", "2008-06-30T11:30:59.000999+01:00"}, + {2008, 6, 30, 11, 30, 59, 999, "Z", "2008-06-30T11:30:59.000000999Z"}, + {2008, 6, 30, 11, 30, 59, 999, "+01:00", "2008-06-30T11:30:59.000000999+01:00"}, + }; + } + + @Test(dataProvider="sampleToString", groups={"tck"}) + public void test_toString(int y, int o, int d, int h, int m, int s, int n, String offsetId, String expected) { + OffsetDateTime t = OffsetDateTime.of(LocalDate.of(y, o, d), LocalTime.of(h, m, s, n), ZoneOffset.of(offsetId)); + String str = t.toString(); + assertEquals(str, expected); + } + + //----------------------------------------------------------------------- + // toString(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d H m s"); + String t = OffsetDateTime.of(LocalDate.of(2010, 12, 3), LocalTime.of(11, 30), OFFSET_PONE).toString(f); + assertEquals(t, "2010 12 3 11 30 0"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_toString_formatter_null() { + OffsetDateTime.of(LocalDate.of(2010, 12, 3), LocalTime.of(11, 30), OFFSET_PONE).toString(null); + } + +} diff --git a/test/java/time/tck/java/time/temporal/TCKOffsetTime.java b/test/java/time/tck/java/time/temporal/TCKOffsetTime.java new file mode 100644 index 0000000000000000000000000000000000000000..374268589adc82f9c9a3bbd1bee1d70ac6012af0 --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TCKOffsetTime.java @@ -0,0 +1,1325 @@ +/* + * 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. + * + * 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 tck.java.time.temporal; + +import static java.time.temporal.ChronoField.AMPM_OF_DAY; +import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM; +import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY; +import static java.time.temporal.ChronoField.HOUR_OF_AMPM; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.MICRO_OF_DAY; +import static java.time.temporal.ChronoField.MICRO_OF_SECOND; +import static java.time.temporal.ChronoField.MILLI_OF_DAY; +import static java.time.temporal.ChronoField.MILLI_OF_SECOND; +import static java.time.temporal.ChronoField.MINUTE_OF_DAY; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.NANO_OF_DAY; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.OFFSET_SECONDS; +import static java.time.temporal.ChronoField.SECOND_OF_DAY; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import java.time.Clock; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.JulianFields; +import java.time.temporal.OffsetDate; +import java.time.temporal.OffsetDateTime; +import java.time.temporal.OffsetTime; +import java.time.temporal.Queries; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQuery; +import java.time.temporal.TemporalSubtractor; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import tck.java.time.AbstractDateTimeTest; +import test.java.time.MockSimplePeriod; + +/** + * Test OffsetTime. + */ +@Test +public class TCKOffsetTime extends AbstractDateTimeTest { + + private static final ZoneOffset OFFSET_PONE = ZoneOffset.ofHours(1); + private static final ZoneOffset OFFSET_PTWO = ZoneOffset.ofHours(2); + private static final LocalDate DATE = LocalDate.of(2008, 12, 3); + private OffsetTime TEST_11_30_59_500_PONE; + + @BeforeMethod(groups={"tck","implementation"}) + public void setUp() { + TEST_11_30_59_500_PONE = OffsetTime.of(LocalTime.of(11, 30, 59, 500), OFFSET_PONE); + } + + //----------------------------------------------------------------------- + @Override + protected List samples() { + TemporalAccessor[] array = {TEST_11_30_59_500_PONE, OffsetTime.MIN, OffsetTime.MAX}; + return Arrays.asList(array); + } + + @Override + protected List validFields() { + TemporalField[] array = { + NANO_OF_SECOND, + NANO_OF_DAY, + MICRO_OF_SECOND, + MICRO_OF_DAY, + MILLI_OF_SECOND, + MILLI_OF_DAY, + SECOND_OF_MINUTE, + SECOND_OF_DAY, + MINUTE_OF_HOUR, + MINUTE_OF_DAY, + CLOCK_HOUR_OF_AMPM, + HOUR_OF_AMPM, + CLOCK_HOUR_OF_DAY, + HOUR_OF_DAY, + AMPM_OF_DAY, + OFFSET_SECONDS, + }; + return Arrays.asList(array); + } + + @Override + protected List invalidFields() { + List list = new ArrayList<>(Arrays.asList(ChronoField.values())); + list.removeAll(validFields()); + list.add(JulianFields.JULIAN_DAY); + list.add(JulianFields.MODIFIED_JULIAN_DAY); + list.add(JulianFields.RATA_DIE); + return list; + } + + //----------------------------------------------------------------------- + @Test + public void test_serialization() throws Exception { + assertSerializable(TEST_11_30_59_500_PONE); + assertSerializable(OffsetTime.MIN); + assertSerializable(OffsetTime.MAX); + } + + @Test + public void test_serialization_format() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(2); + } + byte[] bytes = baos.toByteArray(); + ByteArrayOutputStream baosTime = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baosTime) ) { + dos.writeByte(4); + dos.writeByte(22); + dos.writeByte(17); + dos.writeByte(59); + dos.writeInt(464_000_000); + } + byte[] bytesTime = baosTime.toByteArray(); + ByteArrayOutputStream baosOffset = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baosOffset) ) { + dos.writeByte(8); + dos.writeByte(4); // quarter hours stored: 3600 / 900 + } + byte[] bytesOffset = baosOffset.toByteArray(); + assertSerializedBySer(OffsetTime.of(LocalTime.of(22, 17, 59, 464_000_000), ZoneOffset.ofHours(1)), bytes, + bytesTime, bytesOffset); + } + + //----------------------------------------------------------------------- + // constants + //----------------------------------------------------------------------- + @Test + public void constant_MIN() { + check(OffsetTime.MIN, 0, 0, 0, 0, ZoneOffset.MAX); + } + + @Test + public void constant_MAX() { + check(OffsetTime.MAX, 23, 59, 59, 999999999, ZoneOffset.MIN); + } + + //----------------------------------------------------------------------- + // now() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now() { + ZonedDateTime nowDT = ZonedDateTime.now(); + + OffsetTime expected = OffsetTime.now(Clock.systemDefaultZone()); + OffsetTime test = OffsetTime.now(); + long diff = Math.abs(test.getTime().toNanoOfDay() - expected.getTime().toNanoOfDay()); + assertTrue(diff < 100000000); // less than 0.1 secs + assertEquals(test.getOffset(), nowDT.getOffset()); + } + + //----------------------------------------------------------------------- + // now(Clock) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now_Clock_allSecsInDay() { + for (int i = 0; i < (2 * 24 * 60 * 60); i++) { + Instant instant = Instant.ofEpochSecond(i, 8); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + OffsetTime test = OffsetTime.now(clock); + assertEquals(test.getHour(), (i / (60 * 60)) % 24); + assertEquals(test.getMinute(), (i / 60) % 60); + assertEquals(test.getSecond(), i % 60); + assertEquals(test.getNano(), 8); + assertEquals(test.getOffset(), ZoneOffset.UTC); + } + } + + @Test(groups={"tck"}) + public void now_Clock_beforeEpoch() { + for (int i =-1; i >= -(24 * 60 * 60); i--) { + Instant instant = Instant.ofEpochSecond(i, 8); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + OffsetTime test = OffsetTime.now(clock); + assertEquals(test.getHour(), ((i + 24 * 60 * 60) / (60 * 60)) % 24); + assertEquals(test.getMinute(), ((i + 24 * 60 * 60) / 60) % 60); + assertEquals(test.getSecond(), (i + 24 * 60 * 60) % 60); + assertEquals(test.getNano(), 8); + assertEquals(test.getOffset(), ZoneOffset.UTC); + } + } + + @Test(groups={"tck"}) + public void now_Clock_offsets() { + Instant base = LocalDateTime.of(1970, 1, 1, 12, 0).toInstant(ZoneOffset.UTC); + for (int i = -9; i < 15; i++) { + ZoneOffset offset = ZoneOffset.ofHours(i); + Clock clock = Clock.fixed(base, offset); + OffsetTime test = OffsetTime.now(clock); + assertEquals(test.getHour(), (12 + i) % 24); + assertEquals(test.getMinute(), 0); + assertEquals(test.getSecond(), 0); + assertEquals(test.getNano(), 0); + assertEquals(test.getOffset(), offset); + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_Clock_nullZoneId() { + OffsetTime.now((ZoneId) null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_Clock_nullClock() { + OffsetTime.now((Clock) null); + } + + //----------------------------------------------------------------------- + // factories + //----------------------------------------------------------------------- + private void check(OffsetTime test, int h, int m, int s, int n, ZoneOffset offset) { + assertEquals(test.getTime(), LocalTime.of(h, m, s, n)); + assertEquals(test.getOffset(), offset); + + assertEquals(test.getHour(), h); + assertEquals(test.getMinute(), m); + assertEquals(test.getSecond(), s); + assertEquals(test.getNano(), n); + + assertEquals(test, test); + assertEquals(test.hashCode(), test.hashCode()); + assertEquals(OffsetTime.of(LocalTime.of(h, m, s, n), offset), test); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_intsHM() { + OffsetTime test = OffsetTime.of(LocalTime.of(11, 30), OFFSET_PONE); + check(test, 11, 30, 0, 0, OFFSET_PONE); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_intsHMS() { + OffsetTime test = OffsetTime.of(LocalTime.of(11, 30, 10), OFFSET_PONE); + check(test, 11, 30, 10, 0, OFFSET_PONE); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_intsHMSN() { + OffsetTime test = OffsetTime.of(LocalTime.of(11, 30, 10, 500), OFFSET_PONE); + check(test, 11, 30, 10, 500, OFFSET_PONE); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_LocalTimeZoneOffset() { + LocalTime localTime = LocalTime.of(11, 30, 10, 500); + OffsetTime test = OffsetTime.of(localTime, OFFSET_PONE); + check(test, 11, 30, 10, 500, OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_LocalTimeZoneOffset_nullTime() { + OffsetTime.of((LocalTime) null, OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_LocalTimeZoneOffset_nullOffset() { + LocalTime localTime = LocalTime.of(11, 30, 10, 500); + OffsetTime.of(localTime, (ZoneOffset) null); + } + + //----------------------------------------------------------------------- + // ofInstant() + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_ofInstant_nullInstant() { + OffsetTime.ofInstant((Instant) null, ZoneOffset.UTC); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_ofInstant_nullOffset() { + Instant instant = Instant.ofEpochSecond(0L); + OffsetTime.ofInstant(instant, (ZoneOffset) null); + } + + @Test(groups={"tck"}) + public void factory_ofInstant_allSecsInDay() { + for (int i = 0; i < (2 * 24 * 60 * 60); i++) { + Instant instant = Instant.ofEpochSecond(i, 8); + OffsetTime test = OffsetTime.ofInstant(instant, ZoneOffset.UTC); + assertEquals(test.getHour(), (i / (60 * 60)) % 24); + assertEquals(test.getMinute(), (i / 60) % 60); + assertEquals(test.getSecond(), i % 60); + assertEquals(test.getNano(), 8); + } + } + + @Test(groups={"tck"}) + public void factory_ofInstant_beforeEpoch() { + for (int i =-1; i >= -(24 * 60 * 60); i--) { + Instant instant = Instant.ofEpochSecond(i, 8); + OffsetTime test = OffsetTime.ofInstant(instant, ZoneOffset.UTC); + assertEquals(test.getHour(), ((i + 24 * 60 * 60) / (60 * 60)) % 24); + assertEquals(test.getMinute(), ((i + 24 * 60 * 60) / 60) % 60); + assertEquals(test.getSecond(), (i + 24 * 60 * 60) % 60); + assertEquals(test.getNano(), 8); + } + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_ofInstant_maxYear() { + OffsetTime test = OffsetTime.ofInstant(Instant.MAX, ZoneOffset.UTC); + assertEquals(test.getHour(), 23); + assertEquals(test.getMinute(), 59); + assertEquals(test.getSecond(), 59); + assertEquals(test.getNano(), 999_999_999); + } + + @Test(groups={"tck"}) + public void factory_ofInstant_minYear() { + OffsetTime test = OffsetTime.ofInstant(Instant.MIN, ZoneOffset.UTC); + assertEquals(test.getHour(), 0); + assertEquals(test.getMinute(), 0); + assertEquals(test.getSecond(), 0); + assertEquals(test.getNano(), 0); + } + + //----------------------------------------------------------------------- + // from(TemporalAccessor) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_from_TemporalAccessor_OT() { + assertEquals(OffsetTime.from(OffsetTime.of(LocalTime.of(17, 30), OFFSET_PONE)), OffsetTime.of(LocalTime.of(17, 30), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_from_TemporalAccessor_ZDT() { + ZonedDateTime base = LocalDateTime.of(2007, 7, 15, 11, 30, 59, 500).atZone(OFFSET_PONE); + assertEquals(OffsetTime.from(base), TEST_11_30_59_500_PONE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void factory_from_TemporalAccessor_invalid_noDerive() { + OffsetTime.from(LocalDate.of(2007, 7, 15)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_from_TemporalAccessor_null() { + OffsetTime.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // parse() + //----------------------------------------------------------------------- + @Test(dataProvider = "sampleToString", groups={"tck"}) + public void factory_parse_validText(int h, int m, int s, int n, String offsetId, String parsable) { + OffsetTime t = OffsetTime.parse(parsable); + assertNotNull(t, parsable); + check(t, h, m, s, n, ZoneOffset.of(offsetId)); + } + + @DataProvider(name="sampleBadParse") + Object[][] provider_sampleBadParse() { + return new Object[][]{ + {"00;00"}, + {"12-00"}, + {"-01:00"}, + {"00:00:00-09"}, + {"00:00:00,09"}, + {"00:00:abs"}, + {"11"}, + {"11:30"}, + {"11:30+01:00[Europe/Paris]"}, + }; + } + + @Test(dataProvider = "sampleBadParse", expectedExceptions={DateTimeParseException.class}, groups={"tck"}) + public void factory_parse_invalidText(String unparsable) { + OffsetTime.parse(unparsable); + } + + //-----------------------------------------------------------------------s + @Test(expectedExceptions={DateTimeParseException.class}, groups={"tck"}) + public void factory_parse_illegalHour() { + OffsetTime.parse("25:00+01:00"); + } + + @Test(expectedExceptions={DateTimeParseException.class}, groups={"tck"}) + public void factory_parse_illegalMinute() { + OffsetTime.parse("12:60+01:00"); + } + + @Test(expectedExceptions={DateTimeParseException.class}, groups={"tck"}) + public void factory_parse_illegalSecond() { + OffsetTime.parse("12:12:60+01:00"); + } + + //----------------------------------------------------------------------- + // parse(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_parse_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("H m s XXX"); + OffsetTime test = OffsetTime.parse("11 30 0 +01:00", f); + assertEquals(test, OffsetTime.of(LocalTime.of(11, 30), ZoneOffset.ofHours(1))); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullText() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M d H m s"); + OffsetTime.parse((String) null, f); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullFormatter() { + OffsetTime.parse("ANY", null); + } + + //----------------------------------------------------------------------- + // constructor + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void constructor_nullTime() throws Throwable { + Constructor con = OffsetTime.class.getDeclaredConstructor(LocalTime.class, ZoneOffset.class); + con.setAccessible(true); + try { + con.newInstance(null, OFFSET_PONE); + } catch (InvocationTargetException ex) { + throw ex.getCause(); + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void constructor_nullOffset() throws Throwable { + Constructor con = OffsetTime.class.getDeclaredConstructor(LocalTime.class, ZoneOffset.class); + con.setAccessible(true); + try { + con.newInstance(LocalTime.of(11, 30), null); + } catch (InvocationTargetException ex) { + throw ex.getCause(); + } + } + + //----------------------------------------------------------------------- + // basics + //----------------------------------------------------------------------- + @DataProvider(name="sampleTimes") + Object[][] provider_sampleTimes() { + return new Object[][] { + {11, 30, 20, 500, OFFSET_PONE}, + {11, 0, 0, 0, OFFSET_PONE}, + {23, 59, 59, 999999999, OFFSET_PONE}, + }; + } + + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_get(int h, int m, int s, int n, ZoneOffset offset) { + LocalTime localTime = LocalTime.of(h, m, s, n); + OffsetTime a = OffsetTime.of(localTime, offset); + + assertEquals(a.getTime(), localTime); + assertEquals(a.getOffset(), offset); + assertEquals(a.toString(), localTime.toString() + offset.toString()); + assertEquals(a.getHour(), localTime.getHour()); + assertEquals(a.getMinute(), localTime.getMinute()); + assertEquals(a.getSecond(), localTime.getSecond()); + assertEquals(a.getNano(), localTime.getNano()); + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @Test + public void test_get_TemporalField() { + OffsetTime test = OffsetTime.of(LocalTime.of(12, 30, 40, 987654321), OFFSET_PONE); + assertEquals(test.get(ChronoField.HOUR_OF_DAY), 12); + assertEquals(test.get(ChronoField.MINUTE_OF_HOUR), 30); + assertEquals(test.get(ChronoField.SECOND_OF_MINUTE), 40); + assertEquals(test.get(ChronoField.NANO_OF_SECOND), 987654321); + assertEquals(test.get(ChronoField.HOUR_OF_AMPM), 0); + assertEquals(test.get(ChronoField.AMPM_OF_DAY), 1); + + assertEquals(test.get(ChronoField.OFFSET_SECONDS), 3600); + } + + @Test + public void test_getLong_TemporalField() { + OffsetTime test = OffsetTime.of(LocalTime.of(12, 30, 40, 987654321), OFFSET_PONE); + assertEquals(test.getLong(ChronoField.HOUR_OF_DAY), 12); + assertEquals(test.getLong(ChronoField.MINUTE_OF_HOUR), 30); + assertEquals(test.getLong(ChronoField.SECOND_OF_MINUTE), 40); + assertEquals(test.getLong(ChronoField.NANO_OF_SECOND), 987654321); + assertEquals(test.getLong(ChronoField.HOUR_OF_AMPM), 0); + assertEquals(test.getLong(ChronoField.AMPM_OF_DAY), 1); + + assertEquals(test.getLong(ChronoField.OFFSET_SECONDS), 3600); + } + + //----------------------------------------------------------------------- + // query(TemporalQuery) + //----------------------------------------------------------------------- + @Test + public void test_query_chrono() { + assertEquals(TEST_11_30_59_500_PONE.query(Queries.chrono()), null); + assertEquals(Queries.chrono().queryFrom(TEST_11_30_59_500_PONE), null); + } + + @Test + public void test_query_zoneId() { + assertEquals(TEST_11_30_59_500_PONE.query(Queries.zoneId()), null); + assertEquals(Queries.zoneId().queryFrom(TEST_11_30_59_500_PONE), null); + } + + @Test + public void test_query_precision() { + assertEquals(TEST_11_30_59_500_PONE.query(Queries.precision()), NANOS); + assertEquals(Queries.precision().queryFrom(TEST_11_30_59_500_PONE), NANOS); + } + + @Test + public void test_query_offset() { + assertEquals(TEST_11_30_59_500_PONE.query(Queries.offset()), OFFSET_PONE); + assertEquals(Queries.offset().queryFrom(TEST_11_30_59_500_PONE), OFFSET_PONE); + } + + @Test + public void test_query_zone() { + assertEquals(TEST_11_30_59_500_PONE.query(Queries.zone()), OFFSET_PONE); + assertEquals(Queries.zone().queryFrom(TEST_11_30_59_500_PONE), OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_query_null() { + TEST_11_30_59_500_PONE.query(null); + } + + //----------------------------------------------------------------------- + // withOffsetSameLocal() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withOffsetSameLocal() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.withOffsetSameLocal(OFFSET_PTWO); + assertEquals(test.getTime(), base.getTime()); + assertEquals(test.getOffset(), OFFSET_PTWO); + } + + @Test(groups={"tck"}) + public void test_withOffsetSameLocal_noChange() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.withOffsetSameLocal(OFFSET_PONE); + assertEquals(test, base); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_withOffsetSameLocal_null() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + base.withOffsetSameLocal(null); + } + + //----------------------------------------------------------------------- + // withOffsetSameInstant() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withOffsetSameInstant() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.withOffsetSameInstant(OFFSET_PTWO); + OffsetTime expected = OffsetTime.of(LocalTime.of(12, 30, 59), OFFSET_PTWO); + assertEquals(test, expected); + } + + @Test(groups={"tck"}) + public void test_withOffsetSameInstant_noChange() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.withOffsetSameInstant(OFFSET_PONE); + assertEquals(test, base); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_withOffsetSameInstant_null() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + base.withOffsetSameInstant(null); + } + + //----------------------------------------------------------------------- + // with(WithAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_with_adjustment() { + final OffsetTime sample = OffsetTime.of(LocalTime.of(23, 5), OFFSET_PONE); + TemporalAdjuster adjuster = new TemporalAdjuster() { + @Override + public Temporal adjustInto(Temporal dateTime) { + return sample; + } + }; + assertEquals(TEST_11_30_59_500_PONE.with(adjuster), sample); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_LocalTime() { + OffsetTime test = TEST_11_30_59_500_PONE.with(LocalTime.of(13, 30)); + assertEquals(test, OffsetTime.of(LocalTime.of(13, 30), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_OffsetTime() { + OffsetTime test = TEST_11_30_59_500_PONE.with(OffsetTime.of(LocalTime.of(13, 35), OFFSET_PTWO)); + assertEquals(test, OffsetTime.of(LocalTime.of(13, 35), OFFSET_PTWO)); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_ZoneOffset() { + OffsetTime test = TEST_11_30_59_500_PONE.with(OFFSET_PTWO); + assertEquals(test, OffsetTime.of(LocalTime.of(11, 30, 59, 500), OFFSET_PTWO)); + } + + @Test(groups={"tck"}) + public void test_with_adjustment_AmPm() { + OffsetTime test = TEST_11_30_59_500_PONE.with(new TemporalAdjuster() { + @Override + public Temporal adjustInto(Temporal dateTime) { + return dateTime.with(HOUR_OF_DAY, 23); + } + }); + assertEquals(test, OffsetTime.of(LocalTime.of(23, 30, 59, 500), OFFSET_PONE)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_with_adjustment_null() { + TEST_11_30_59_500_PONE.with((TemporalAdjuster) null); + } + + //----------------------------------------------------------------------- + // with(TemporalField, long) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_with_TemporalField() { + OffsetTime test = OffsetTime.of(LocalTime.of(12, 30, 40, 987654321), OFFSET_PONE); + assertEquals(test.with(ChronoField.HOUR_OF_DAY, 15), OffsetTime.of(LocalTime.of(15, 30, 40, 987654321), OFFSET_PONE)); + assertEquals(test.with(ChronoField.MINUTE_OF_HOUR, 50), OffsetTime.of(LocalTime.of(12, 50, 40, 987654321), OFFSET_PONE)); + assertEquals(test.with(ChronoField.SECOND_OF_MINUTE, 50), OffsetTime.of(LocalTime.of(12, 30, 50, 987654321), OFFSET_PONE)); + assertEquals(test.with(ChronoField.NANO_OF_SECOND, 12345), OffsetTime.of(LocalTime.of(12, 30, 40, 12345), OFFSET_PONE)); + assertEquals(test.with(ChronoField.HOUR_OF_AMPM, 6), OffsetTime.of(LocalTime.of(18, 30, 40, 987654321), OFFSET_PONE)); + assertEquals(test.with(ChronoField.AMPM_OF_DAY, 0), OffsetTime.of(LocalTime.of(0, 30, 40, 987654321), OFFSET_PONE)); + + assertEquals(test.with(ChronoField.OFFSET_SECONDS, 7205), OffsetTime.of(LocalTime.of(12, 30, 40, 987654321), ZoneOffset.ofHoursMinutesSeconds(2, 0, 5))); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"} ) + public void test_with_TemporalField_null() { + TEST_11_30_59_500_PONE.with((TemporalField) null, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"} ) + public void test_with_TemporalField_invalidField() { + TEST_11_30_59_500_PONE.with(ChronoField.YEAR, 0); + } + + //----------------------------------------------------------------------- + // withHour() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withHour_normal() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.withHour(15); + assertEquals(test, OffsetTime.of(LocalTime.of(15, 30, 59), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_withHour_noChange() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.withHour(11); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // withMinute() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withMinute_normal() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.withMinute(15); + assertEquals(test, OffsetTime.of(LocalTime.of(11, 15, 59), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_withMinute_noChange() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.withMinute(30); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // withSecond() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withSecond_normal() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.withSecond(15); + assertEquals(test, OffsetTime.of(LocalTime.of(11, 30, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_withSecond_noChange() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.withSecond(59); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // withNano() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withNanoOfSecond_normal() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59, 1), OFFSET_PONE); + OffsetTime test = base.withNano(15); + assertEquals(test, OffsetTime.of(LocalTime.of(11, 30, 59, 15), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_withNanoOfSecond_noChange() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59, 1), OFFSET_PONE); + OffsetTime test = base.withNano(1); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // truncatedTo(TemporalUnit) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_truncatedTo_normal() { + assertEquals(TEST_11_30_59_500_PONE.truncatedTo(NANOS), TEST_11_30_59_500_PONE); + assertEquals(TEST_11_30_59_500_PONE.truncatedTo(SECONDS), TEST_11_30_59_500_PONE.withNano(0)); + assertEquals(TEST_11_30_59_500_PONE.truncatedTo(DAYS), TEST_11_30_59_500_PONE.with(LocalTime.MIDNIGHT)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_truncatedTo_null() { + TEST_11_30_59_500_PONE.truncatedTo(null); + } + + //----------------------------------------------------------------------- + // plus(PlusAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plus_PlusAdjuster() { + MockSimplePeriod period = MockSimplePeriod.of(7, ChronoUnit.MINUTES); + OffsetTime t = TEST_11_30_59_500_PONE.plus(period); + assertEquals(t, OffsetTime.of(LocalTime.of(11, 37, 59, 500), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plus_PlusAdjuster_noChange() { + OffsetTime t = TEST_11_30_59_500_PONE.plus(MockSimplePeriod.of(0, SECONDS)); + assertEquals(t, TEST_11_30_59_500_PONE); + } + + @Test(groups={"tck"}) + public void test_plus_PlusAdjuster_zero() { + OffsetTime t = TEST_11_30_59_500_PONE.plus(Period.ZERO); + assertEquals(t, TEST_11_30_59_500_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_plus_PlusAdjuster_null() { + TEST_11_30_59_500_PONE.plus((TemporalAdder) null); + } + + //----------------------------------------------------------------------- + // plusHours() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusHours() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.plusHours(13); + assertEquals(test, OffsetTime.of(LocalTime.of(0, 30, 59), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusHours_zero() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.plusHours(0); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // plusMinutes() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusMinutes() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.plusMinutes(30); + assertEquals(test, OffsetTime.of(LocalTime.of(12, 0, 59), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusMinutes_zero() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.plusMinutes(0); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // plusSeconds() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusSeconds() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.plusSeconds(1); + assertEquals(test, OffsetTime.of(LocalTime.of(11, 31, 0), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusSeconds_zero() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.plusSeconds(0); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // plusNanos() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusNanos() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59, 0), OFFSET_PONE); + OffsetTime test = base.plusNanos(1); + assertEquals(test, OffsetTime.of(LocalTime.of(11, 30, 59, 1), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_plusNanos_zero() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.plusNanos(0); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // minus(MinusAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minus_MinusAdjuster() { + MockSimplePeriod period = MockSimplePeriod.of(7, ChronoUnit.MINUTES); + OffsetTime t = TEST_11_30_59_500_PONE.minus(period); + assertEquals(t, OffsetTime.of(LocalTime.of(11, 23, 59, 500), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minus_MinusAdjuster_noChange() { + OffsetTime t = TEST_11_30_59_500_PONE.minus(MockSimplePeriod.of(0, SECONDS)); + assertEquals(t, TEST_11_30_59_500_PONE); + } + + @Test(groups={"tck"}) + public void test_minus_MinusAdjuster_zero() { + OffsetTime t = TEST_11_30_59_500_PONE.minus(Period.ZERO); + assertEquals(t, TEST_11_30_59_500_PONE); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_minus_MinusAdjuster_null() { + TEST_11_30_59_500_PONE.minus((TemporalSubtractor) null); + } + + //----------------------------------------------------------------------- + // minusHours() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusHours() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.minusHours(-13); + assertEquals(test, OffsetTime.of(LocalTime.of(0, 30, 59), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusHours_zero() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.minusHours(0); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // minusMinutes() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusMinutes() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.minusMinutes(50); + assertEquals(test, OffsetTime.of(LocalTime.of(10, 40, 59), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusMinutes_zero() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.minusMinutes(0); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // minusSeconds() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusSeconds() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.minusSeconds(60); + assertEquals(test, OffsetTime.of(LocalTime.of(11, 29, 59), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusSeconds_zero() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.minusSeconds(0); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // minusNanos() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusNanos() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59, 0), OFFSET_PONE); + OffsetTime test = base.minusNanos(1); + assertEquals(test, OffsetTime.of(LocalTime.of(11, 30, 58, 999999999), OFFSET_PONE)); + } + + @Test(groups={"tck"}) + public void test_minusNanos_zero() { + OffsetTime base = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetTime test = base.minusNanos(0); + assertEquals(test, base); + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_compareTo_time() { + OffsetTime a = OffsetTime.of(LocalTime.of(11, 29), OFFSET_PONE); + OffsetTime b = OffsetTime.of(LocalTime.of(11, 30), OFFSET_PONE); // a is before b due to time + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(convertInstant(a).compareTo(convertInstant(b)) < 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_offset() { + OffsetTime a = OffsetTime.of(LocalTime.of(11, 30), OFFSET_PTWO); + OffsetTime b = OffsetTime.of(LocalTime.of(11, 30), OFFSET_PONE); // a is before b due to offset + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(convertInstant(a).compareTo(convertInstant(b)) < 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_both() { + OffsetTime a = OffsetTime.of(LocalTime.of(11, 50), OFFSET_PTWO); + OffsetTime b = OffsetTime.of(LocalTime.of(11, 20), OFFSET_PONE); // a is before b on instant scale + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(convertInstant(a).compareTo(convertInstant(b)) < 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_bothNearStartOfDay() { + OffsetTime a = OffsetTime.of(LocalTime.of(0, 10), OFFSET_PONE); + OffsetTime b = OffsetTime.of(LocalTime.of(2, 30), OFFSET_PTWO); // a is before b on instant scale + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(convertInstant(a).compareTo(convertInstant(b)) < 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_hourDifference() { + OffsetTime a = OffsetTime.of(LocalTime.of(10, 0), OFFSET_PONE); + OffsetTime b = OffsetTime.of(LocalTime.of(11, 0), OFFSET_PTWO); // a is before b despite being same time-line time + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(convertInstant(a).compareTo(convertInstant(b)) == 0, true); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_compareTo_null() { + OffsetTime a = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + a.compareTo(null); + } + + @Test(expectedExceptions=ClassCastException.class, groups={"tck"}) + @SuppressWarnings({"unchecked", "rawtypes"}) + public void compareToNonOffsetTime() { + Comparable c = TEST_11_30_59_500_PONE; + c.compareTo(new Object()); + } + + private Instant convertInstant(OffsetTime ot) { + return DATE.atTime(ot.getTime()).toInstant(ot.getOffset()); + } + + //----------------------------------------------------------------------- + // isAfter() / isBefore() / isEqual() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_isBeforeIsAfterIsEqual1() { + OffsetTime a = OffsetTime.of(LocalTime.of(11, 30, 58), OFFSET_PONE); + OffsetTime b = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); // a is before b due to time + assertEquals(a.isBefore(b), true); + assertEquals(a.isEqual(b), false); + assertEquals(a.isAfter(b), false); + + assertEquals(b.isBefore(a), false); + assertEquals(b.isEqual(a), false); + assertEquals(b.isAfter(a), true); + + assertEquals(a.isBefore(a), false); + assertEquals(b.isBefore(b), false); + + assertEquals(a.isEqual(a), true); + assertEquals(b.isEqual(b), true); + + assertEquals(a.isAfter(a), false); + assertEquals(b.isAfter(b), false); + } + + @Test(groups={"tck"}) + public void test_isBeforeIsAfterIsEqual1nanos() { + OffsetTime a = OffsetTime.of(LocalTime.of(11, 30, 59, 3), OFFSET_PONE); + OffsetTime b = OffsetTime.of(LocalTime.of(11, 30, 59, 4), OFFSET_PONE); // a is before b due to time + assertEquals(a.isBefore(b), true); + assertEquals(a.isEqual(b), false); + assertEquals(a.isAfter(b), false); + + assertEquals(b.isBefore(a), false); + assertEquals(b.isEqual(a), false); + assertEquals(b.isAfter(a), true); + + assertEquals(a.isBefore(a), false); + assertEquals(b.isBefore(b), false); + + assertEquals(a.isEqual(a), true); + assertEquals(b.isEqual(b), true); + + assertEquals(a.isAfter(a), false); + assertEquals(b.isAfter(b), false); + } + + @Test(groups={"tck"}) + public void test_isBeforeIsAfterIsEqual2() { + OffsetTime a = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PTWO); + OffsetTime b = OffsetTime.of(LocalTime.of(11, 30, 58), OFFSET_PONE); // a is before b due to offset + assertEquals(a.isBefore(b), true); + assertEquals(a.isEqual(b), false); + assertEquals(a.isAfter(b), false); + + assertEquals(b.isBefore(a), false); + assertEquals(b.isEqual(a), false); + assertEquals(b.isAfter(a), true); + + assertEquals(a.isBefore(a), false); + assertEquals(b.isBefore(b), false); + + assertEquals(a.isEqual(a), true); + assertEquals(b.isEqual(b), true); + + assertEquals(a.isAfter(a), false); + assertEquals(b.isAfter(b), false); + } + + @Test(groups={"tck"}) + public void test_isBeforeIsAfterIsEqual2nanos() { + OffsetTime a = OffsetTime.of(LocalTime.of(11, 30, 59, 4), ZoneOffset.ofTotalSeconds(OFFSET_PONE.getTotalSeconds() + 1)); + OffsetTime b = OffsetTime.of(LocalTime.of(11, 30, 59, 3), OFFSET_PONE); // a is before b due to offset + assertEquals(a.isBefore(b), true); + assertEquals(a.isEqual(b), false); + assertEquals(a.isAfter(b), false); + + assertEquals(b.isBefore(a), false); + assertEquals(b.isEqual(a), false); + assertEquals(b.isAfter(a), true); + + assertEquals(a.isBefore(a), false); + assertEquals(b.isBefore(b), false); + + assertEquals(a.isEqual(a), true); + assertEquals(b.isEqual(b), true); + + assertEquals(a.isAfter(a), false); + assertEquals(b.isAfter(b), false); + } + + @Test(groups={"tck"}) + public void test_isBeforeIsAfterIsEqual_instantComparison() { + OffsetTime a = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PTWO); + OffsetTime b = OffsetTime.of(LocalTime.of(10, 30, 59), OFFSET_PONE); // a is same instant as b + assertEquals(a.isBefore(b), false); + assertEquals(a.isEqual(b), true); + assertEquals(a.isAfter(b), false); + + assertEquals(b.isBefore(a), false); + assertEquals(b.isEqual(a), true); + assertEquals(b.isAfter(a), false); + + assertEquals(a.isBefore(a), false); + assertEquals(b.isBefore(b), false); + + assertEquals(a.isEqual(a), true); + assertEquals(b.isEqual(b), true); + + assertEquals(a.isAfter(a), false); + assertEquals(b.isAfter(b), false); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isBefore_null() { + OffsetTime a = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + a.isBefore(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isAfter_null() { + OffsetTime a = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + a.isAfter(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isEqual_null() { + OffsetTime a = OffsetTime.of(LocalTime.of(11, 30, 59), OFFSET_PONE); + a.isEqual(null); + } + + //----------------------------------------------------------------------- + // equals() / hashCode() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_true(int h, int m, int s, int n, ZoneOffset ignored) { + OffsetTime a = OffsetTime.of(LocalTime.of(h, m, s, n), OFFSET_PONE); + OffsetTime b = OffsetTime.of(LocalTime.of(h, m, s, n), OFFSET_PONE); + assertEquals(a.equals(b), true); + assertEquals(a.hashCode() == b.hashCode(), true); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_hour_differs(int h, int m, int s, int n, ZoneOffset ignored) { + h = (h == 23 ? 22 : h); + OffsetTime a = OffsetTime.of(LocalTime.of(h, m, s, n), OFFSET_PONE); + OffsetTime b = OffsetTime.of(LocalTime.of(h + 1, m, s, n), OFFSET_PONE); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_minute_differs(int h, int m, int s, int n, ZoneOffset ignored) { + m = (m == 59 ? 58 : m); + OffsetTime a = OffsetTime.of(LocalTime.of(h, m, s, n), OFFSET_PONE); + OffsetTime b = OffsetTime.of(LocalTime.of(h, m + 1, s, n), OFFSET_PONE); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_second_differs(int h, int m, int s, int n, ZoneOffset ignored) { + s = (s == 59 ? 58 : s); + OffsetTime a = OffsetTime.of(LocalTime.of(h, m, s, n), OFFSET_PONE); + OffsetTime b = OffsetTime.of(LocalTime.of(h, m, s + 1, n), OFFSET_PONE); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_nano_differs(int h, int m, int s, int n, ZoneOffset ignored) { + n = (n == 999999999 ? 999999998 : n); + OffsetTime a = OffsetTime.of(LocalTime.of(h, m, s, n), OFFSET_PONE); + OffsetTime b = OffsetTime.of(LocalTime.of(h, m, s, n + 1), OFFSET_PONE); + assertEquals(a.equals(b), false); + } + @Test(dataProvider="sampleTimes", groups={"tck"}) + public void test_equals_false_offset_differs(int h, int m, int s, int n, ZoneOffset ignored) { + OffsetTime a = OffsetTime.of(LocalTime.of(h, m, s, n), OFFSET_PONE); + OffsetTime b = OffsetTime.of(LocalTime.of(h, m, s, n), OFFSET_PTWO); + assertEquals(a.equals(b), false); + } + + @Test(groups={"tck"}) + public void test_equals_itself_true() { + assertEquals(TEST_11_30_59_500_PONE.equals(TEST_11_30_59_500_PONE), true); + } + + @Test(groups={"tck"}) + public void test_equals_string_false() { + assertEquals(TEST_11_30_59_500_PONE.equals("2007-07-15"), false); + } + + @Test(groups={"tck"}) + public void test_equals_null_false() { + assertEquals(TEST_11_30_59_500_PONE.equals(null), false); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="sampleToString") + Object[][] provider_sampleToString() { + return new Object[][] { + {11, 30, 59, 0, "Z", "11:30:59Z"}, + {11, 30, 59, 0, "+01:00", "11:30:59+01:00"}, + {11, 30, 59, 999000000, "Z", "11:30:59.999Z"}, + {11, 30, 59, 999000000, "+01:00", "11:30:59.999+01:00"}, + {11, 30, 59, 999000, "Z", "11:30:59.000999Z"}, + {11, 30, 59, 999000, "+01:00", "11:30:59.000999+01:00"}, + {11, 30, 59, 999, "Z", "11:30:59.000000999Z"}, + {11, 30, 59, 999, "+01:00", "11:30:59.000000999+01:00"}, + }; + } + + @Test(dataProvider="sampleToString", groups={"tck"}) + public void test_toString(int h, int m, int s, int n, String offsetId, String expected) { + OffsetTime t = OffsetTime.of(LocalTime.of(h, m, s, n), ZoneOffset.of(offsetId)); + String str = t.toString(); + assertEquals(str, expected); + } + + //----------------------------------------------------------------------- + // toString(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("H m s"); + String t = OffsetTime.of(LocalTime.of(11, 30), OFFSET_PONE).toString(f); + assertEquals(t, "11 30 0"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_toString_formatter_null() { + OffsetTime.of(LocalTime.of(11, 30), OFFSET_PONE).toString(null); + } + +} diff --git a/test/java/time/tck/java/time/temporal/TCKSimplePeriod.java b/test/java/time/tck/java/time/temporal/TCKSimplePeriod.java new file mode 100644 index 0000000000000000000000000000000000000000..d67e6aae297fa4e7267d1c28b8d33e06caf7c402 --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TCKSimplePeriod.java @@ -0,0 +1,286 @@ +/* + * 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. + * + * 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 tck.java.time.temporal; + +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static java.time.temporal.ChronoUnit.YEARS; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import java.time.LocalDate; +import java.time.Period; +import java.time.temporal.ISOFields; +import java.time.temporal.SimplePeriod; +import java.time.temporal.TemporalUnit; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import tck.java.time.AbstractTCKTest; + +/** + * Test. + */ +@Test +public class TCKSimplePeriod extends AbstractTCKTest { + + private static final SimplePeriod TEST_12_MONTHS = SimplePeriod.of(12, MONTHS); + + //----------------------------------------------------------------------- + @Test(dataProvider="samples") + public void test_serialization(long amount, TemporalUnit unit) throws ClassNotFoundException, IOException { + SimplePeriod test = SimplePeriod.of(amount, unit); + assertSerializable(test); + } + + @Test + public void test_serialization_format_zoneOffset() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(10); + dos.writeLong(12); + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(TEST_12_MONTHS, bytes, new byte[0]); + } + + //----------------------------------------------------------------------- + // of(long, TenmporalUnit) + //----------------------------------------------------------------------- + @DataProvider(name="samples") + Object[][] data_samples() { + return new Object[][] { + {0, YEARS}, + {1, YEARS}, + {-1, YEARS}, + {2, MONTHS}, + {-2, MONTHS}, + {43, ISOFields.WEEK_BASED_YEARS}, + {Long.MAX_VALUE, NANOS}, + {Long.MIN_VALUE, NANOS}, + }; + } + + @Test(dataProvider="samples") + public void factory_of(long amount, TemporalUnit unit) { + SimplePeriod test = SimplePeriod.of(amount, unit); + assertEquals(test.getAmount(), amount); + assertEquals(test.getUnit(), unit); + } + + //----------------------------------------------------------------------- + // addTo() + //----------------------------------------------------------------------- + @DataProvider(name="addTo") + Object[][] data_addTo() { + return new Object[][] { + {SimplePeriod.of(0, DAYS), date(2012, 6, 30), date(2012, 6, 30)}, + + {SimplePeriod.of(1, DAYS), date(2012, 6, 30), date(2012, 7, 1)}, + {SimplePeriod.of(-1, DAYS), date(2012, 6, 30), date(2012, 6, 29)}, + + {SimplePeriod.of(2, DAYS), date(2012, 6, 30), date(2012, 7, 2)}, + {SimplePeriod.of(-2, DAYS), date(2012, 6, 30), date(2012, 6, 28)}, + + {SimplePeriod.of(3, MONTHS), date(2012, 5, 31), date(2012, 8, 31)}, + {SimplePeriod.of(4, MONTHS), date(2012, 5, 31), date(2012, 9, 30)}, + {SimplePeriod.of(-3, MONTHS), date(2012, 5, 31), date(2012, 2, 29)}, + {SimplePeriod.of(-4, MONTHS), date(2012, 5, 31), date(2012, 1, 31)}, + }; + } + + @Test(dataProvider="addTo") + public void test_addTo(SimplePeriod period, LocalDate baseDate, LocalDate expected) { + assertEquals(period.addTo(baseDate), expected); + } + + @Test(dataProvider="addTo") + public void test_addTo_usingLocalDatePlus(SimplePeriod period, LocalDate baseDate, LocalDate expected) { + assertEquals(baseDate.plus(period), expected); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_addTo_nullZero() { + SimplePeriod.of(0, DAYS).addTo(null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_addTo_nullNonZero() { + SimplePeriod.of(2, DAYS).addTo(null); + } + + //----------------------------------------------------------------------- + // subtractFrom() + //----------------------------------------------------------------------- + @DataProvider(name="subtractFrom") + Object[][] data_subtractFrom() { + return new Object[][] { + {SimplePeriod.of(0, DAYS), date(2012, 6, 30), date(2012, 6, 30)}, + + {SimplePeriod.of(1, DAYS), date(2012, 6, 30), date(2012, 6, 29)}, + {SimplePeriod.of(-1, DAYS), date(2012, 6, 30), date(2012, 7, 1)}, + + {SimplePeriod.of(2, DAYS), date(2012, 6, 30), date(2012, 6, 28)}, + {SimplePeriod.of(-2, DAYS), date(2012, 6, 30), date(2012, 7, 2)}, + + {SimplePeriod.of(3, MONTHS), date(2012, 5, 31), date(2012, 2, 29)}, + {SimplePeriod.of(4, MONTHS), date(2012, 5, 31), date(2012, 1, 31)}, + {SimplePeriod.of(-3, MONTHS), date(2012, 5, 31), date(2012, 8, 31)}, + {SimplePeriod.of(-4, MONTHS), date(2012, 5, 31), date(2012, 9, 30)}, + }; + } + + @Test(dataProvider="subtractFrom") + public void test_subtractFrom(SimplePeriod period, LocalDate baseDate, LocalDate expected) { + assertEquals(period.subtractFrom(baseDate), expected); + } + + @Test(dataProvider="subtractFrom") + public void test_subtractFrom_usingLocalDateMinus(SimplePeriod period, LocalDate baseDate, LocalDate expected) { + assertEquals(baseDate.minus(period), expected); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_subtractFrom_nullZero() { + SimplePeriod.of(0, DAYS).subtractFrom(null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_subtractFrom_nullNonZero() { + SimplePeriod.of(2, DAYS).subtractFrom(null); + } + + //----------------------------------------------------------------------- + // abs() + //----------------------------------------------------------------------- + @Test(dataProvider="samples") + public void test_abs(long amount, TemporalUnit unit) { + SimplePeriod test = SimplePeriod.of(amount, unit); + if (amount >= 0) { + assertSame(test.abs(), test); // spec requires assertSame + } else if (amount == Long.MIN_VALUE) { + // ignore, separately tested + } else { + assertEquals(test.abs(), SimplePeriod.of(-amount, unit)); + } + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_abs_minValue() { + SimplePeriod.of(Long.MIN_VALUE, SECONDS).abs(); + } + + //----------------------------------------------------------------------- + // equals() / hashCode() + //----------------------------------------------------------------------- + @Test(dataProvider="samples") + public void test_equals(long amount, TemporalUnit unit) { + SimplePeriod test1 = SimplePeriod.of(amount, unit); + SimplePeriod test2 = SimplePeriod.of(amount, unit); + assertEquals(test1, test2); + } + + @Test(dataProvider="samples") + public void test_equals_self(long amount, TemporalUnit unit) { + SimplePeriod test = SimplePeriod.of(amount, unit); + assertEquals(test.equals(test), true); + } + + public void test_equals_null() { + assertEquals(TEST_12_MONTHS.equals(null), false); + } + + public void test_equals_otherClass() { + Period test = Period.of(1, 2, 3, 4, 5, 6); + assertEquals(test.equals(""), false); + } + + //----------------------------------------------------------------------- + public void test_hashCode() { + SimplePeriod test5 = SimplePeriod.of(5, DAYS); + SimplePeriod test6 = SimplePeriod.of(6, DAYS); + SimplePeriod test5M = SimplePeriod.of(5, MONTHS); + SimplePeriod test5Y = SimplePeriod.of(5, YEARS); + assertEquals(test5.hashCode() == test5.hashCode(), true); + assertEquals(test5.hashCode() == test6.hashCode(), false); + assertEquals(test5.hashCode() == test5M.hashCode(), false); + assertEquals(test5.hashCode() == test5Y.hashCode(), false); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @Test(dataProvider="samples") + public void test_toString(long amount, TemporalUnit unit) { + SimplePeriod test = SimplePeriod.of(amount, unit); + assertEquals(test.toString(), amount + " " + unit.getName()); + } + + private static LocalDate date(int y, int m, int d) { + return LocalDate.of(y, m, d); + } + +} diff --git a/test/java/time/tck/java/time/temporal/TCKWeekFields.java b/test/java/time/tck/java/time/temporal/TCKWeekFields.java new file mode 100644 index 0000000000000000000000000000000000000000..9119a70fa655432b295848a16b90bdf970f1429d --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TCKWeekFields.java @@ -0,0 +1,365 @@ +/* + * 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) 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 tck.java.time.temporal; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.fail; + +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.temporal.TemporalField; +import java.time.format.DateTimeBuilder; +import java.time.temporal.ValueRange; +import java.time.temporal.WeekFields; + +import org.testng.annotations.Test; + +/** + * Test WeekFields. + */ +@Test +public class TCKWeekFields { + + @Test(groups={"tck"}) + public void test_WeekFieldsOf() { + for (DayOfWeek dow : DayOfWeek.values()) { + for (int minDays = 1; minDays <= 7; minDays++) { + WeekFields week = WeekFields.of(dow, minDays); + assertEquals(week.getFirstDayOfWeek(), dow, "Incorrect firstDayOfWeek"); + assertEquals(week.getMinimalDaysInFirstWeek(), minDays, "Incorrect MinimalDaysInFirstWeek"); + } + } + } + + @Test(groups={"tck"}) + public void test_DayOfWeek() { + LocalDate day = LocalDate.of(2000, 1, 10); // Known to be ISO Monday + for (DayOfWeek firstDayOfWeek : DayOfWeek.values()) { + for (int minDays = 1; minDays <= 7; minDays++) { + WeekFields week = WeekFields.of(firstDayOfWeek, minDays); + TemporalField f = week.dayOfWeek(); + //System.out.printf(" Week: %s; field: %s%n", week, f); + + for (int i = 1; i <= 7; i++) { + //System.out.printf(" ISO Dow: %s, WeekDOW ordinal: %s%n", day.getDayOfWeek(), day.get(f)); + assertEquals(day.get(f), (7 + day.getDayOfWeek().getValue() - firstDayOfWeek.getValue()) % 7 + 1); + day = day.plusDays(1); + } + } + } + } + + @Test(groups={"tck"}) + public void test_WeekOfMonth() { + for (DayOfWeek firstDayOfWeek : DayOfWeek.values()) { + for (int minDays = 1; minDays <= 7; minDays++) { + LocalDate day = LocalDate.of(2012, 12, 31); // Known to be ISO Monday + WeekFields week = WeekFields.of(firstDayOfWeek, minDays); + TemporalField dowField = week.dayOfWeek(); + TemporalField womField = week.weekOfMonth(); + //System.err.printf("%n Week: %s; dowField: %s, domField: %s%n", week, dowField, womField); + + DayOfWeek isoDOW = day.getDayOfWeek(); + int dow = (7 + isoDOW.getValue() - firstDayOfWeek.getValue()) % 7 + 1; + + for (int i = 1; i <= 15; i++) { + int actualDOW = day.get(dowField); + int actualWOM = day.get(womField); + + // Verify that the combination of day of week and week of month can be used + // to reconstruct the same date. + LocalDate day1 = day.withDayOfMonth(1); + int offset = - (day1.get(dowField) - 1); + //System.err.printf(" refDay: %s%n", day1.plusDays(offset)); + int week1 = day1.get(womField); + if (week1 == 0) { + // week of the 1st is partial; start with first full week + offset += 7; + } + //System.err.printf(" refDay2: %s, offset: %d, week1: %d%n", day1.plusDays(offset), offset, week1); + offset += actualDOW - 1; + //System.err.printf(" refDay3: %s%n", day1.plusDays(offset)); + offset += (actualWOM - 1) * 7; + //System.err.printf(" refDay4: %s%n", day1.plusDays(offset)); + LocalDate result = day1.plusDays(offset); + + if (!day.equals(result)) { + System.err.printf("FAIL ISO Dow: %s, offset: %s, actualDOW: %s, actualWOM: %s, expected: %s, result: %s%n", + day.getDayOfWeek(), offset, actualDOW, actualWOM, day, result); + } + assertEquals(result, day, "Incorrect dayOfWeek or weekOfMonth: " + + String.format("%s, ISO Dow: %s, offset: %s, actualDOW: %s, actualWOM: %s, expected: %s, result: %s%n", + week, day.getDayOfWeek(), offset, actualDOW, actualWOM, day, result)); + day = day.plusDays(1); + } + } + } + } + + @Test(groups={"tck"}) + public void test_WeekOfYear() { + for (DayOfWeek firstDayOfWeek : DayOfWeek.values()) { + for (int minDays = 1; minDays <= 7; minDays++) { + LocalDate day = LocalDate.of(2012, 12, 31); // Known to be ISO Monday + WeekFields week = WeekFields.of(firstDayOfWeek, minDays); + TemporalField dowField = week.dayOfWeek(); + TemporalField woyField = week.weekOfYear(); + //System.err.printf("%n Year: %s; dowField: %s, woyField: %s%n", week, dowField, woyField); + + DayOfWeek isoDOW = day.getDayOfWeek(); + int dow = (7 + isoDOW.getValue() - firstDayOfWeek.getValue()) % 7 + 1; + + for (int i = 1; i <= 15; i++) { + int actualDOW = day.get(dowField); + int actualWOY = day.get(woyField); + + // Verify that the combination of day of week and week of month can be used + // to reconstruct the same date. + LocalDate day1 = day.withDayOfYear(1); + int offset = - (day1.get(dowField) - 1); + //System.err.printf(" refDay: %s%n", day1.plusDays(offset)); + int week1 = day1.get(woyField); + if (week1 == 0) { + // week of the 1st is partial; start with first full week + offset += 7; + } + //System.err.printf(" refDay2: %s, offset: %d, week1: %d%n", day1.plusDays(offset), offset, week1); + offset += actualDOW - 1; + //System.err.printf(" refDay3: %s%n", day1.plusDays(offset)); + offset += (actualWOY - 1) * 7; + //System.err.printf(" refDay4: %s%n", day1.plusDays(offset)); + LocalDate result = day1.plusDays(offset); + + + if (!day.equals(result)) { + System.err.printf("FAIL ISO Dow: %s, offset: %s, actualDOW: %s, actualWOY: %s, expected: %s, result: %s%n", + day.getDayOfWeek(), offset, actualDOW, actualWOY, day, result); + } + assertEquals(result, day, "Incorrect dayOfWeek or weekOfYear " + + String.format("%s, ISO Dow: %s, offset: %s, actualDOW: %s, actualWOM: %s, expected: %s, result: %s%n", + week, day.getDayOfWeek(), offset, actualDOW, actualWOY, day, result)); + day = day.plusDays(1); + } + } + } + } + + @Test(groups={"tck"}) + public void test_fieldRanges() { + for (DayOfWeek firstDayOfWeek : DayOfWeek.values()) { + for (int minDays = 1; minDays <= 7; minDays++) { + WeekFields weekDef = WeekFields.of(firstDayOfWeek, minDays); + TemporalField dowField = weekDef.dayOfWeek(); + TemporalField womField = weekDef.weekOfMonth(); + TemporalField woyField = weekDef.weekOfYear(); + + LocalDate day = LocalDate.of(2012, 11, 30); + LocalDate endDay = LocalDate.of(2013, 1, 2); + while (day.isBefore(endDay)) { + LocalDate last = day.with(DAY_OF_MONTH, day.lengthOfMonth()); + int lastWOM = last.get(womField); + LocalDate first = day.with(DAY_OF_MONTH, 1); + int firstWOM = first.get(womField); + ValueRange rangeWOM = day.range(womField); + assertEquals(rangeWOM.getMinimum(), firstWOM, + "Range min should be same as WeekOfMonth for first day of month: " + + first + ", " + weekDef); + assertEquals(rangeWOM.getMaximum(), lastWOM, + "Range max should be same as WeekOfMonth for last day of month: " + + last + ", " + weekDef); + + last = day.with(DAY_OF_YEAR, day.lengthOfYear()); + int lastWOY = last.get(woyField); + first = day.with(DAY_OF_YEAR, 1); + int firstWOY = first.get(woyField); + ValueRange rangeWOY = day.range(woyField); + assertEquals(rangeWOY.getMinimum(), firstWOY, + "Range min should be same as WeekOfYear for first day of Year: " + + day + ", " + weekDef); + assertEquals(rangeWOY.getMaximum(), lastWOY, + "Range max should be same as WeekOfYear for last day of Year: " + + day + ", " + weekDef); + + day = day.plusDays(1); + } + } + } + } + + //----------------------------------------------------------------------- + // withDayOfWeek() + //----------------------------------------------------------------------- + @Test(groups = {"tck"}) + public void test_withDayOfWeek() { + for (DayOfWeek firstDayOfWeek : DayOfWeek.values()) { + for (int minDays = 1; minDays <= 7; minDays++) { + LocalDate day = LocalDate.of(2012, 12, 15); // Safely in the middle of a month + WeekFields week = WeekFields.of(firstDayOfWeek, minDays); + + TemporalField dowField = week.dayOfWeek(); + TemporalField womField = week.weekOfMonth(); + TemporalField woyField = week.weekOfYear(); + int wom = day.get(womField); + int woy = day.get(woyField); + for (int dow = 1; dow <= 7; dow++) { + LocalDate result = day.with(dowField, dow); + if (result.get(dowField) != dow) { + System.err.printf(" DOW actual: %d, expected: %d, week:%s%n", + result.get(dowField), dow, week); + } + if (result.get(womField) != wom) { + System.err.printf(" WOM actual: %d, expected: %d, week:%s%n", + result.get(womField), wom, week); + } + if (result.get(woyField) != woy) { + System.err.printf(" WOY actual: %d, expected: %d, week:%s%n", + result.get(woyField), woy, week); + } + assertEquals(result.get(dowField), dow, String.format("Incorrect new Day of week: %s", result)); + assertEquals(result.get(womField), wom, "Week of Month should not change"); + assertEquals(result.get(woyField), woy, "Week of Year should not change"); + } + } + } + } + + @Test() + public void test_computedFieldResolver() { + // For all starting days of week, for all minDays, for two weeks in Dec 2012 + // Test that when supplied with partial values, that the resolver + // fills in the month + for (DayOfWeek firstDayOfWeek : DayOfWeek.values()) { + for (int minDays = 1; minDays <= 7; minDays++) { + LocalDate day = LocalDate.of(2012, 12, 15); // Safely in the middle of a month + WeekFields week = WeekFields.of(firstDayOfWeek, minDays); + + TemporalField dowField = week.dayOfWeek(); + TemporalField womField = week.weekOfMonth(); + TemporalField woyField = week.weekOfYear(); + int wom = day.get(womField); + int woy = day.get(woyField); + for (int dow = 1; dow <= 15; dow++) { + // Test that with dayOfWeek and Week of month it computes the day of month + DateTimeBuilder builder = new DateTimeBuilder(); + builder.addFieldValue(YEAR, day.get(YEAR)); + builder.addFieldValue(MONTH_OF_YEAR, day.get(MONTH_OF_YEAR)); + builder.addFieldValue(DAY_OF_WEEK, day.get(DAY_OF_WEEK)); + builder.addFieldValue(dowField, day.get(dowField)); + builder.addFieldValue(womField, day.get(womField)); + + boolean res1 = dowField.resolve(builder, day.get(dowField)); + boolean res2 = womField.resolve(builder, day.get(womField)); + assertEquals(day.get(DAY_OF_MONTH), day.get(DAY_OF_MONTH)); + assertEquals(day.get(DAY_OF_YEAR), day.get(DAY_OF_YEAR)); + + day = day.plusDays(1); + } + day = LocalDate.of(2012, 12, 15); // Safely in the middle of a month + for (int dow = 1; dow <= 15; dow++) { + // Test that with dayOfWeek and Week of year it computes the day of year + DateTimeBuilder builder = new DateTimeBuilder(); + builder.addFieldValue(YEAR, day.get(YEAR)); + builder.addFieldValue(DAY_OF_WEEK, day.get(DAY_OF_WEEK)); + builder.addFieldValue(dowField, day.get(dowField)); + builder.addFieldValue(woyField, day.get(woyField)); + + boolean res1 = dowField.resolve(builder, day.get(dowField)); + boolean res2 = woyField.resolve(builder, day.get(woyField)); + + assertEquals(day.get(DAY_OF_MONTH), day.get(DAY_OF_MONTH)); + assertEquals(day.get(DAY_OF_YEAR), day.get(DAY_OF_YEAR)); + + day = day.plusDays(1); + } + } + } + } + + @Test(groups = {"tck"}) + public void test_WeekFieldsSingleton() throws IOException, ClassNotFoundException { + for (DayOfWeek firstDayOfWeek : DayOfWeek.values()) { + for (int minDays = 1; minDays <= 7; minDays++) { + WeekFields weekDef = WeekFields.of(firstDayOfWeek, minDays); + WeekFields weekDef2 = WeekFields.of(firstDayOfWeek, minDays); + assertSame(weekDef2, weekDef, "WeekFields same parameters should be same instance"); + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(weekDef); + + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream( + baos.toByteArray())); + WeekFields result = (WeekFields)ois.readObject(); + assertSame(result, weekDef, "Deserialized object same as serialized."); + } + // Exceptions will be handled as failures by TestNG + } + } + } +} diff --git a/test/java/time/tck/java/time/temporal/TCKYear.java b/test/java/time/tck/java/time/temporal/TCKYear.java new file mode 100644 index 0000000000000000000000000000000000000000..5464c6b42415d1bed813d11c2bd3452c35eafe45 --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TCKYear.java @@ -0,0 +1,781 @@ +/* + * 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. + * + * 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 tck.java.time.temporal; + +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import java.time.Clock; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.JulianFields; +import java.time.temporal.MonthDay; +import java.time.temporal.OffsetDateTime; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; +import java.time.temporal.Year; +import java.time.temporal.YearMonth; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import tck.java.time.AbstractDateTimeTest; + +/** + * Test Year. + */ +@Test +public class TCKYear extends AbstractDateTimeTest { + + private static final Year TEST_2008 = Year.of(2008); + + @BeforeMethod + public void setUp() { + } + + //----------------------------------------------------------------------- + @Override + protected List samples() { + TemporalAccessor[] array = {TEST_2008, }; + return Arrays.asList(array); + } + + @Override + protected List validFields() { + TemporalField[] array = { + YEAR_OF_ERA, + YEAR, + ERA, + }; + return Arrays.asList(array); + } + + @Override + protected List invalidFields() { + List list = new ArrayList<>(Arrays.asList(ChronoField.values())); + list.removeAll(validFields()); + list.add(JulianFields.JULIAN_DAY); + list.add(JulianFields.MODIFIED_JULIAN_DAY); + list.add(JulianFields.RATA_DIE); + return list; + } + + //----------------------------------------------------------------------- + @Test + public void test_serialization() throws Exception { + assertSerializable(Year.of(2)); + assertSerializable(Year.of(0)); + assertSerializable(Year.of(-2)); + } + + @Test + public void test_serialization_format() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(4); + dos.writeInt(2012); + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(Year.of(2012), bytes); + } + + //----------------------------------------------------------------------- + // now() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now() { + Year expected = Year.now(Clock.systemDefaultZone()); + Year test = Year.now(); + for (int i = 0; i < 100; i++) { + if (expected.equals(test)) { + return; + } + expected = Year.now(Clock.systemDefaultZone()); + test = Year.now(); + } + assertEquals(test, expected); + } + + //----------------------------------------------------------------------- + // now(ZoneId) + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_ZoneId_nullZoneId() { + Year.now((ZoneId) null); + } + + @Test(groups={"tck"}) + public void now_ZoneId() { + ZoneId zone = ZoneId.of("UTC+01:02:03"); + Year expected = Year.now(Clock.system(zone)); + Year test = Year.now(zone); + for (int i = 0; i < 100; i++) { + if (expected.equals(test)) { + return; + } + expected = Year.now(Clock.system(zone)); + test = Year.now(zone); + } + assertEquals(test, expected); + } + + //----------------------------------------------------------------------- + // now(Clock) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now_Clock() { + Instant instant = OffsetDateTime.of(LocalDate.of(2010, 12, 31), LocalTime.of(0, 0), ZoneOffset.UTC).toInstant(); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + Year test = Year.now(clock); + assertEquals(test.getValue(), 2010); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_Clock_nullClock() { + Year.now((Clock) null); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_int_singleton() { + for (int i = -4; i <= 2104; i++) { + Year test = Year.of(i); + assertEquals(test.getValue(), i); + assertEquals(Year.of(i), test); + } + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_tooLow() { + Year.of(Year.MIN_VALUE - 1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_tooHigh() { + Year.of(Year.MAX_VALUE + 1); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_CalendricalObject() { + assertEquals(Year.from(LocalDate.of(2007, 7, 15)), Year.of(2007)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_CalendricalObject_invalid_noDerive() { + Year.from(LocalTime.of(12, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_CalendricalObject_null() { + Year.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // parse() + //----------------------------------------------------------------------- + @DataProvider(name="goodParseData") + Object[][] provider_goodParseData() { + return new Object[][] { + {"0000", Year.of(0)}, + {"9999", Year.of(9999)}, + {"2000", Year.of(2000)}, + + {"+12345678", Year.of(12345678)}, + {"+123456", Year.of(123456)}, + {"-1234", Year.of(-1234)}, + {"-12345678", Year.of(-12345678)}, + + {"+" + Year.MAX_VALUE, Year.of(Year.MAX_VALUE)}, + {"" + Year.MIN_VALUE, Year.of(Year.MIN_VALUE)}, + }; + } + + @Test(dataProvider="goodParseData", groups={"tck"}) + public void factory_parse_success(String text, Year expected) { + Year year = Year.parse(text); + assertEquals(year, expected); + } + + @DataProvider(name="badParseData") + Object[][] provider_badParseData() { + return new Object[][] { + {"", 0}, + {"-00", 1}, + {"--01-0", 1}, + {"A01", 0}, + {"200", 0}, + {"2009/12", 4}, + + {"-0000-10", 0}, + {"-12345678901-10", 11}, + {"+1-10", 1}, + {"+12-10", 1}, + {"+123-10", 1}, + {"+1234-10", 0}, + {"12345-10", 0}, + {"+12345678901-10", 11}, + }; + } + + @Test(dataProvider="badParseData", expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_fail(String text, int pos) { + try { + Year.parse(text); + fail(String.format("Parse should have failed for %s at position %d", text, pos)); + } catch (DateTimeParseException ex) { + assertEquals(ex.getParsedString(), text); + assertEquals(ex.getErrorIndex(), pos); + throw ex; + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_nullText() { + Year.parse(null); + } + + //----------------------------------------------------------------------- + // parse(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_parse_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("y"); + Year test = Year.parse("2010", f); + assertEquals(test, Year.of(2010)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullText() { + DateTimeFormatter f = DateTimeFormatters.pattern("y"); + Year.parse((String) null, f); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullFormatter() { + Year.parse("ANY", null); + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @Test + public void test_get_TemporalField() { + assertEquals(TEST_2008.get(ChronoField.YEAR), 2008); + assertEquals(TEST_2008.get(ChronoField.YEAR_OF_ERA), 2008); + assertEquals(TEST_2008.get(ChronoField.ERA), 1); + } + + @Test + public void test_getLong_TemporalField() { + assertEquals(TEST_2008.getLong(ChronoField.YEAR), 2008); + assertEquals(TEST_2008.getLong(ChronoField.YEAR_OF_ERA), 2008); + assertEquals(TEST_2008.getLong(ChronoField.ERA), 1); + } + + //----------------------------------------------------------------------- + // isLeap() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_isLeap() { + assertEquals(Year.of(1999).isLeap(), false); + assertEquals(Year.of(2000).isLeap(), true); + assertEquals(Year.of(2001).isLeap(), false); + + assertEquals(Year.of(2007).isLeap(), false); + assertEquals(Year.of(2008).isLeap(), true); + assertEquals(Year.of(2009).isLeap(), false); + assertEquals(Year.of(2010).isLeap(), false); + assertEquals(Year.of(2011).isLeap(), false); + assertEquals(Year.of(2012).isLeap(), true); + + assertEquals(Year.of(2095).isLeap(), false); + assertEquals(Year.of(2096).isLeap(), true); + assertEquals(Year.of(2097).isLeap(), false); + assertEquals(Year.of(2098).isLeap(), false); + assertEquals(Year.of(2099).isLeap(), false); + assertEquals(Year.of(2100).isLeap(), false); + assertEquals(Year.of(2101).isLeap(), false); + assertEquals(Year.of(2102).isLeap(), false); + assertEquals(Year.of(2103).isLeap(), false); + assertEquals(Year.of(2104).isLeap(), true); + assertEquals(Year.of(2105).isLeap(), false); + + assertEquals(Year.of(-500).isLeap(), false); + assertEquals(Year.of(-400).isLeap(), true); + assertEquals(Year.of(-300).isLeap(), false); + assertEquals(Year.of(-200).isLeap(), false); + assertEquals(Year.of(-100).isLeap(), false); + assertEquals(Year.of(0).isLeap(), true); + assertEquals(Year.of(100).isLeap(), false); + assertEquals(Year.of(200).isLeap(), false); + assertEquals(Year.of(300).isLeap(), false); + assertEquals(Year.of(400).isLeap(), true); + assertEquals(Year.of(500).isLeap(), false); + } + + //----------------------------------------------------------------------- + // plusYears() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusYears() { + assertEquals(Year.of(2007).plusYears(-1), Year.of(2006)); + assertEquals(Year.of(2007).plusYears(0), Year.of(2007)); + assertEquals(Year.of(2007).plusYears(1), Year.of(2008)); + assertEquals(Year.of(2007).plusYears(2), Year.of(2009)); + + assertEquals(Year.of(Year.MAX_VALUE - 1).plusYears(1), Year.of(Year.MAX_VALUE)); + assertEquals(Year.of(Year.MAX_VALUE).plusYears(0), Year.of(Year.MAX_VALUE)); + + assertEquals(Year.of(Year.MIN_VALUE + 1).plusYears(-1), Year.of(Year.MIN_VALUE)); + assertEquals(Year.of(Year.MIN_VALUE).plusYears(0), Year.of(Year.MIN_VALUE)); + } + + @Test(groups={"tck"}) + public void test_plusYear_zero_equals() { + Year base = Year.of(2007); + assertEquals(base.plusYears(0), base); + } + + @Test(groups={"tck"}) + public void test_plusYears_big() { + long years = 20L + Year.MAX_VALUE; + assertEquals(Year.of(-40).plusYears(years), Year.of((int) (-40L + years))); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_max() { + Year.of(Year.MAX_VALUE).plusYears(1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_maxLots() { + Year.of(Year.MAX_VALUE).plusYears(1000); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_min() { + Year.of(Year.MIN_VALUE).plusYears(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_minLots() { + Year.of(Year.MIN_VALUE).plusYears(-1000); + } + + //----------------------------------------------------------------------- + // minusYears() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusYears() { + assertEquals(Year.of(2007).minusYears(-1), Year.of(2008)); + assertEquals(Year.of(2007).minusYears(0), Year.of(2007)); + assertEquals(Year.of(2007).minusYears(1), Year.of(2006)); + assertEquals(Year.of(2007).minusYears(2), Year.of(2005)); + + assertEquals(Year.of(Year.MAX_VALUE - 1).minusYears(-1), Year.of(Year.MAX_VALUE)); + assertEquals(Year.of(Year.MAX_VALUE).minusYears(0), Year.of(Year.MAX_VALUE)); + + assertEquals(Year.of(Year.MIN_VALUE + 1).minusYears(1), Year.of(Year.MIN_VALUE)); + assertEquals(Year.of(Year.MIN_VALUE).minusYears(0), Year.of(Year.MIN_VALUE)); + } + + @Test(groups={"tck"}) + public void test_minusYear_zero_equals() { + Year base = Year.of(2007); + assertEquals(base.minusYears(0), base); + } + + @Test(groups={"tck"}) + public void test_minusYears_big() { + long years = 20L + Year.MAX_VALUE; + assertEquals(Year.of(40).minusYears(years), Year.of((int) (40L - years))); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_max() { + Year.of(Year.MAX_VALUE).minusYears(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_maxLots() { + Year.of(Year.MAX_VALUE).minusYears(-1000); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_min() { + Year.of(Year.MIN_VALUE).minusYears(1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_minLots() { + Year.of(Year.MIN_VALUE).minusYears(1000); + } + + //----------------------------------------------------------------------- + // adjustInto() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjustDate() { + LocalDate base = LocalDate.of(2007, 2, 12); + for (int i = -4; i <= 2104; i++) { + Temporal result = Year.of(i).adjustInto(base); + assertEquals(result, LocalDate.of(i, 2, 12)); + } + } + + @Test(groups={"tck"}) + public void test_adjustDate_resolve() { + Year test = Year.of(2011); + assertEquals(test.adjustInto(LocalDate.of(2012, 2, 29)), LocalDate.of(2011, 2, 28)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_adjustDate_nullLocalDate() { + Year test = Year.of(1); + test.adjustInto((LocalDate) null); + } + + //----------------------------------------------------------------------- + // length() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_length() { + assertEquals(Year.of(1999).length(), 365); + assertEquals(Year.of(2000).length(), 366); + assertEquals(Year.of(2001).length(), 365); + + assertEquals(Year.of(2007).length(), 365); + assertEquals(Year.of(2008).length(), 366); + assertEquals(Year.of(2009).length(), 365); + assertEquals(Year.of(2010).length(), 365); + assertEquals(Year.of(2011).length(), 365); + assertEquals(Year.of(2012).length(), 366); + + assertEquals(Year.of(2095).length(), 365); + assertEquals(Year.of(2096).length(), 366); + assertEquals(Year.of(2097).length(), 365); + assertEquals(Year.of(2098).length(), 365); + assertEquals(Year.of(2099).length(), 365); + assertEquals(Year.of(2100).length(), 365); + assertEquals(Year.of(2101).length(), 365); + assertEquals(Year.of(2102).length(), 365); + assertEquals(Year.of(2103).length(), 365); + assertEquals(Year.of(2104).length(), 366); + assertEquals(Year.of(2105).length(), 365); + + assertEquals(Year.of(-500).length(), 365); + assertEquals(Year.of(-400).length(), 366); + assertEquals(Year.of(-300).length(), 365); + assertEquals(Year.of(-200).length(), 365); + assertEquals(Year.of(-100).length(), 365); + assertEquals(Year.of(0).length(), 366); + assertEquals(Year.of(100).length(), 365); + assertEquals(Year.of(200).length(), 365); + assertEquals(Year.of(300).length(), 365); + assertEquals(Year.of(400).length(), 366); + assertEquals(Year.of(500).length(), 365); + } + + //----------------------------------------------------------------------- + // isValidMonthDay(Month) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_isValidMonthDay_june() { + Year test = Year.of(2007); + MonthDay monthDay = MonthDay.of(6, 30); + assertEquals(test.isValidMonthDay(monthDay), true); + } + + @Test(groups={"tck"}) + public void test_isValidMonthDay_febNonLeap() { + Year test = Year.of(2007); + MonthDay monthDay = MonthDay.of(2, 29); + assertEquals(test.isValidMonthDay(monthDay), false); + } + + @Test(groups={"tck"}) + public void test_isValidMonthDay_febLeap() { + Year test = Year.of(2008); + MonthDay monthDay = MonthDay.of(2, 29); + assertEquals(test.isValidMonthDay(monthDay), true); + } + + @Test(groups={"tck"}) + public void test_isValidMonthDay_null() { + Year test = Year.of(2008); + assertEquals(test.isValidMonthDay(null), false); + } + + //----------------------------------------------------------------------- + // atMonth(Month) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atMonth() { + Year test = Year.of(2008); + assertEquals(test.atMonth(Month.JUNE), YearMonth.of(2008, 6)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_atMonth_nullMonth() { + Year test = Year.of(2008); + test.atMonth((Month) null); + } + + //----------------------------------------------------------------------- + // atMonth(int) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atMonth_int() { + Year test = Year.of(2008); + assertEquals(test.atMonth(6), YearMonth.of(2008, 6)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atMonth_int_invalidMonth() { + Year test = Year.of(2008); + test.atMonth(13); + } + + //----------------------------------------------------------------------- + // atMonthDay(Month) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atMonthDay() { + Year test = Year.of(2008); + assertEquals(test.atMonthDay(MonthDay.of(6, 30)), LocalDate.of(2008, 6, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_atMonthDay_nullMonthDay() { + Year test = Year.of(2008); + test.atMonthDay((MonthDay) null); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atMonthDay_invalidMonthDay() { + Year test = Year.of(2008); + test.atMonthDay(MonthDay.of(6, 31)); + } + + //----------------------------------------------------------------------- + // atDay(int) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atDay_notLeapYear() { + Year test = Year.of(2007); + LocalDate expected = LocalDate.of(2007, 1, 1); + for (int i = 1; i <= 365; i++) { + assertEquals(test.atDay(i), expected); + expected = expected.plusDays(1); + } + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atDay_notLeapYear_day366() { + Year test = Year.of(2007); + test.atDay(366); + } + + @Test(groups={"tck"}) + public void test_atDay_leapYear() { + Year test = Year.of(2008); + LocalDate expected = LocalDate.of(2008, 1, 1); + for (int i = 1; i <= 366; i++) { + assertEquals(test.atDay(i), expected); + expected = expected.plusDays(1); + } + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atDay_day0() { + Year test = Year.of(2007); + test.atDay(0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atDay_day367() { + Year test = Year.of(2007); + test.atDay(367); + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_compareTo() { + for (int i = -4; i <= 2104; i++) { + Year a = Year.of(i); + for (int j = -4; j <= 2104; j++) { + Year b = Year.of(j); + if (i < j) { + assertEquals(a.compareTo(b) < 0, true); + assertEquals(b.compareTo(a) > 0, true); + assertEquals(a.isAfter(b), false); + assertEquals(a.isBefore(b), true); + assertEquals(b.isAfter(a), true); + assertEquals(b.isBefore(a), false); + } else if (i > j) { + assertEquals(a.compareTo(b) > 0, true); + assertEquals(b.compareTo(a) < 0, true); + assertEquals(a.isAfter(b), true); + assertEquals(a.isBefore(b), false); + assertEquals(b.isAfter(a), false); + assertEquals(b.isBefore(a), true); + } else { + assertEquals(a.compareTo(b), 0); + assertEquals(b.compareTo(a), 0); + assertEquals(a.isAfter(b), false); + assertEquals(a.isBefore(b), false); + assertEquals(b.isAfter(a), false); + assertEquals(b.isBefore(a), false); + } + } + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_compareTo_nullYear() { + Year doy = null; + Year test = Year.of(1); + test.compareTo(doy); + } + + //----------------------------------------------------------------------- + // equals() / hashCode() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_equals() { + for (int i = -4; i <= 2104; i++) { + Year a = Year.of(i); + for (int j = -4; j <= 2104; j++) { + Year b = Year.of(j); + assertEquals(a.equals(b), i == j); + assertEquals(a.hashCode() == b.hashCode(), i == j); + } + } + } + + @Test(groups={"tck"}) + public void test_equals_same() { + Year test = Year.of(2011); + assertEquals(test.equals(test), true); + } + + @Test(groups={"tck"}) + public void test_equals_nullYear() { + Year doy = null; + Year test = Year.of(1); + assertEquals(test.equals(doy), false); + } + + @Test(groups={"tck"}) + public void test_equals_incorrectType() { + Year test = Year.of(1); + assertEquals(test.equals("Incorrect type"), false); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString() { + for (int i = -4; i <= 2104; i++) { + Year a = Year.of(i); + assertEquals(a.toString(), "" + i); + } + } + + //----------------------------------------------------------------------- + // toString(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("y"); + String t = Year.of(2010).toString(f); + assertEquals(t, "2010"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_toString_formatter_null() { + Year.of(2010).toString(null); + } + +} diff --git a/test/java/time/tck/java/time/temporal/TCKYearMonth.java b/test/java/time/tck/java/time/temporal/TCKYearMonth.java new file mode 100644 index 0000000000000000000000000000000000000000..25d57f1a22b2fbfc3f197c2c09ece5d4b7015c9b --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TCKYearMonth.java @@ -0,0 +1,1046 @@ +/* + * 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. + * + * 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 tck.java.time.temporal; + +import static java.time.temporal.ChronoField.EPOCH_MONTH; +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import java.time.Clock; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatters; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.JulianFields; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; +import java.time.temporal.Year; +import java.time.temporal.YearMonth; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import tck.java.time.AbstractDateTimeTest; + +/** + * Test YearMonth. + */ +@Test +public class TCKYearMonth extends AbstractDateTimeTest { + + private YearMonth TEST_2008_06; + + @BeforeMethod(groups={"tck", "implementation"}) + public void setUp() { + TEST_2008_06 = YearMonth.of(2008, 6); + } + + //----------------------------------------------------------------------- + @Override + protected List samples() { + TemporalAccessor[] array = {TEST_2008_06, }; + return Arrays.asList(array); + } + + @Override + protected List validFields() { + TemporalField[] array = { + MONTH_OF_YEAR, + EPOCH_MONTH, + YEAR_OF_ERA, + YEAR, + ERA, + }; + return Arrays.asList(array); + } + + @Override + protected List invalidFields() { + List list = new ArrayList<>(Arrays.asList(ChronoField.values())); + list.removeAll(validFields()); + list.add(JulianFields.JULIAN_DAY); + list.add(JulianFields.MODIFIED_JULIAN_DAY); + list.add(JulianFields.RATA_DIE); + return list; + } + + //----------------------------------------------------------------------- + @Test + public void test_serialization() throws IOException, ClassNotFoundException { + assertSerializable(TEST_2008_06); + } + + @Test + public void test_serialization_format() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream dos = new DataOutputStream(baos) ) { + dos.writeByte(5); + dos.writeInt(2012); + dos.writeByte(9); + } + byte[] bytes = baos.toByteArray(); + assertSerializedBySer(YearMonth.of(2012, 9), bytes); + } + + //----------------------------------------------------------------------- + void check(YearMonth test, int y, int m) { + assertEquals(test.getYear(), y); + assertEquals(test.getMonth().getValue(), m); + } + + //----------------------------------------------------------------------- + // now() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now() { + YearMonth expected = YearMonth.now(Clock.systemDefaultZone()); + YearMonth test = YearMonth.now(); + for (int i = 0; i < 100; i++) { + if (expected.equals(test)) { + return; + } + expected = YearMonth.now(Clock.systemDefaultZone()); + test = YearMonth.now(); + } + assertEquals(test, expected); + } + + //----------------------------------------------------------------------- + // now(ZoneId) + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_ZoneId_nullZoneId() { + YearMonth.now((ZoneId) null); + } + + @Test(groups={"tck"}) + public void now_ZoneId() { + ZoneId zone = ZoneId.of("UTC+01:02:03"); + YearMonth expected = YearMonth.now(Clock.system(zone)); + YearMonth test = YearMonth.now(zone); + for (int i = 0; i < 100; i++) { + if (expected.equals(test)) { + return; + } + expected = YearMonth.now(Clock.system(zone)); + test = YearMonth.now(zone); + } + assertEquals(test, expected); + } + + //----------------------------------------------------------------------- + // now(Clock) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void now_Clock() { + Instant instant = LocalDateTime.of(2010, 12, 31, 0, 0).toInstant(ZoneOffset.UTC); + Clock clock = Clock.fixed(instant, ZoneOffset.UTC); + YearMonth test = YearMonth.now(clock); + assertEquals(test.getYear(), 2010); + assertEquals(test.getMonth(), Month.DECEMBER); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void now_Clock_nullClock() { + YearMonth.now((Clock) null); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_intsMonth() { + YearMonth test = YearMonth.of(2008, Month.FEBRUARY); + check(test, 2008, 2); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_intsMonth_yearTooLow() { + YearMonth.of(Year.MIN_VALUE - 1, Month.JANUARY); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_intsMonth_dayTooHigh() { + YearMonth.of(Year.MAX_VALUE + 1, Month.JANUARY); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_intsMonth_nullMonth() { + YearMonth.of(2008, null); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_ints() { + YearMonth test = YearMonth.of(2008, 2); + check(test, 2008, 2); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_ints_yearTooLow() { + YearMonth.of(Year.MIN_VALUE - 1, 2); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_ints_dayTooHigh() { + YearMonth.of(Year.MAX_VALUE + 1, 2); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_ints_monthTooLow() { + YearMonth.of(2008, 0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_ints_monthTooHigh() { + YearMonth.of(2008, 13); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_CalendricalObject() { + assertEquals(YearMonth.from(LocalDate.of(2007, 7, 15)), YearMonth.of(2007, 7)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_CalendricalObject_invalid_noDerive() { + YearMonth.from(LocalTime.of(12, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_CalendricalObject_null() { + YearMonth.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // parse() + //----------------------------------------------------------------------- + @DataProvider(name="goodParseData") + Object[][] provider_goodParseData() { + return new Object[][] { + {"0000-01", YearMonth.of(0, 1)}, + {"0000-12", YearMonth.of(0, 12)}, + {"9999-12", YearMonth.of(9999, 12)}, + {"2000-01", YearMonth.of(2000, 1)}, + {"2000-02", YearMonth.of(2000, 2)}, + {"2000-03", YearMonth.of(2000, 3)}, + {"2000-04", YearMonth.of(2000, 4)}, + {"2000-05", YearMonth.of(2000, 5)}, + {"2000-06", YearMonth.of(2000, 6)}, + {"2000-07", YearMonth.of(2000, 7)}, + {"2000-08", YearMonth.of(2000, 8)}, + {"2000-09", YearMonth.of(2000, 9)}, + {"2000-10", YearMonth.of(2000, 10)}, + {"2000-11", YearMonth.of(2000, 11)}, + {"2000-12", YearMonth.of(2000, 12)}, + + {"+12345678-03", YearMonth.of(12345678, 3)}, + {"+123456-03", YearMonth.of(123456, 3)}, + {"0000-03", YearMonth.of(0, 3)}, + {"-1234-03", YearMonth.of(-1234, 3)}, + {"-12345678-03", YearMonth.of(-12345678, 3)}, + + {"+" + Year.MAX_VALUE + "-03", YearMonth.of(Year.MAX_VALUE, 3)}, + {Year.MIN_VALUE + "-03", YearMonth.of(Year.MIN_VALUE, 3)}, + }; + } + + @Test(dataProvider="goodParseData", groups={"tck"}) + public void factory_parse_success(String text, YearMonth expected) { + YearMonth yearMonth = YearMonth.parse(text); + assertEquals(yearMonth, expected); + } + + //----------------------------------------------------------------------- + @DataProvider(name="badParseData") + Object[][] provider_badParseData() { + return new Object[][] { + {"", 0}, + {"-00", 1}, + {"--01-0", 1}, + {"A01-3", 0}, + {"200-01", 0}, + {"2009/12", 4}, + + {"-0000-10", 0}, + {"-12345678901-10", 11}, + {"+1-10", 1}, + {"+12-10", 1}, + {"+123-10", 1}, + {"+1234-10", 0}, + {"12345-10", 0}, + {"+12345678901-10", 11}, + }; + } + + @Test(dataProvider="badParseData", expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_fail(String text, int pos) { + try { + YearMonth.parse(text); + fail(String.format("Parse should have failed for %s at position %d", text, pos)); + } catch (DateTimeParseException ex) { + assertEquals(ex.getParsedString(), text); + assertEquals(ex.getErrorIndex(), pos); + throw ex; + } + } + + //----------------------------------------------------------------------- + @Test(expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_illegalValue_Month() { + YearMonth.parse("2008-13"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_nullText() { + YearMonth.parse(null); + } + + //----------------------------------------------------------------------- + // parse(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_parse_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M"); + YearMonth test = YearMonth.parse("2010 12", f); + assertEquals(test, YearMonth.of(2010, 12)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullText() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M"); + YearMonth.parse((String) null, f); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_formatter_nullFormatter() { + YearMonth.parse("ANY", null); + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @Test + public void test_get_TemporalField() { + assertEquals(TEST_2008_06.get(ChronoField.YEAR), 2008); + assertEquals(TEST_2008_06.get(ChronoField.MONTH_OF_YEAR), 6); + assertEquals(TEST_2008_06.get(ChronoField.YEAR_OF_ERA), 2008); + assertEquals(TEST_2008_06.get(ChronoField.ERA), 1); + } + + @Test + public void test_getLong_TemporalField() { + assertEquals(TEST_2008_06.getLong(ChronoField.YEAR), 2008); + assertEquals(TEST_2008_06.getLong(ChronoField.MONTH_OF_YEAR), 6); + assertEquals(TEST_2008_06.getLong(ChronoField.YEAR_OF_ERA), 2008); + assertEquals(TEST_2008_06.getLong(ChronoField.ERA), 1); + assertEquals(TEST_2008_06.getLong(ChronoField.EPOCH_MONTH), (2008 - 1970) * 12 + 6 - 1); + } + + //----------------------------------------------------------------------- + // get*() + //----------------------------------------------------------------------- + @DataProvider(name="sampleDates") + Object[][] provider_sampleDates() { + return new Object[][] { + {2008, 1}, + {2008, 2}, + {-1, 3}, + {0, 12}, + }; + } + + //----------------------------------------------------------------------- + // with(Year) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_with_Year() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.with(Year.of(2000)), YearMonth.of(2000, 6)); + } + + @Test(groups={"tck"}) + public void test_with_Year_noChange_equal() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.with(Year.of(2008)), test); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_with_Year_null() { + YearMonth test = YearMonth.of(2008, 6); + test.with((Year) null); + } + + //----------------------------------------------------------------------- + // with(Month) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_with_Month() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.with(Month.JANUARY), YearMonth.of(2008, 1)); + } + + @Test(groups={"tck"}) + public void test_with_Month_noChange_equal() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.with(Month.JUNE), test); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_with_Month_null() { + YearMonth test = YearMonth.of(2008, 6); + test.with((Month) null); + } + + //----------------------------------------------------------------------- + // withYear() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withYear() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.withYear(1999), YearMonth.of(1999, 6)); + } + + @Test(groups={"tck"}) + public void test_withYear_int_noChange_equal() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.withYear(2008), test); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withYear_tooLow() { + YearMonth test = YearMonth.of(2008, 6); + test.withYear(Year.MIN_VALUE - 1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withYear_tooHigh() { + YearMonth test = YearMonth.of(2008, 6); + test.withYear(Year.MAX_VALUE + 1); + } + + //----------------------------------------------------------------------- + // withMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_withMonth() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.withMonth(1), YearMonth.of(2008, 1)); + } + + @Test(groups={"tck"}) + public void test_withMonth_int_noChange_equal() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.withMonth(6), test); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withMonth_tooLow() { + YearMonth test = YearMonth.of(2008, 6); + test.withMonth(0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_withMonth_tooHigh() { + YearMonth test = YearMonth.of(2008, 6); + test.withMonth(13); + } + + //----------------------------------------------------------------------- + // plusYears() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusYears_long() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.plusYears(1), YearMonth.of(2009, 6)); + } + + @Test(groups={"tck"}) + public void test_plusYears_long_noChange_equal() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.plusYears(0), test); + } + + @Test(groups={"tck"}) + public void test_plusYears_long_negative() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.plusYears(-1), YearMonth.of(2007, 6)); + } + + @Test(groups={"tck"}) + public void test_plusYears_long_big() { + YearMonth test = YearMonth.of(-40, 6); + assertEquals(test.plusYears(20L + Year.MAX_VALUE), YearMonth.of((int) (-40L + 20L + Year.MAX_VALUE), 6)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_long_invalidTooLarge() { + YearMonth test = YearMonth.of(Year.MAX_VALUE, 6); + test.plusYears(1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_long_invalidTooLargeMaxAddMax() { + YearMonth test = YearMonth.of(Year.MAX_VALUE, 12); + test.plusYears(Long.MAX_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_long_invalidTooLargeMaxAddMin() { + YearMonth test = YearMonth.of(Year.MAX_VALUE, 12); + test.plusYears(Long.MIN_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusYears_long_invalidTooSmall() { + YearMonth test = YearMonth.of(Year.MIN_VALUE, 6); + test.plusYears(-1); + } + + //----------------------------------------------------------------------- + // plusMonths() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_plusMonths_long() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.plusMonths(1), YearMonth.of(2008, 7)); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_noChange_equal() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.plusMonths(0), test); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_overYears() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.plusMonths(7), YearMonth.of(2009, 1)); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_negative() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.plusMonths(-1), YearMonth.of(2008, 5)); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_negativeOverYear() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.plusMonths(-6), YearMonth.of(2007, 12)); + } + + @Test(groups={"tck"}) + public void test_plusMonths_long_big() { + YearMonth test = YearMonth.of(-40, 6); + long months = 20L + Integer.MAX_VALUE; + assertEquals(test.plusMonths(months), YearMonth.of((int) (-40L + months / 12), 6 + (int) (months % 12))); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_plusMonths_long_invalidTooLarge() { + YearMonth test = YearMonth.of(Year.MAX_VALUE, 12); + test.plusMonths(1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusMonths_long_invalidTooLargeMaxAddMax() { + YearMonth test = YearMonth.of(Year.MAX_VALUE, 12); + test.plusMonths(Long.MAX_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_plusMonths_long_invalidTooLargeMaxAddMin() { + YearMonth test = YearMonth.of(Year.MAX_VALUE, 12); + test.plusMonths(Long.MIN_VALUE); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_plusMonths_long_invalidTooSmall() { + YearMonth test = YearMonth.of(Year.MIN_VALUE, 1); + test.plusMonths(-1); + } + + //----------------------------------------------------------------------- + // minusYears() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusYears_long() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.minusYears(1), YearMonth.of(2007, 6)); + } + + @Test(groups={"tck"}) + public void test_minusYears_long_noChange_equal() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.minusYears(0), test); + } + + @Test(groups={"tck"}) + public void test_minusYears_long_negative() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.minusYears(-1), YearMonth.of(2009, 6)); + } + + @Test(groups={"tck"}) + public void test_minusYears_long_big() { + YearMonth test = YearMonth.of(40, 6); + assertEquals(test.minusYears(20L + Year.MAX_VALUE), YearMonth.of((int) (40L - 20L - Year.MAX_VALUE), 6)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_long_invalidTooLarge() { + YearMonth test = YearMonth.of(Year.MAX_VALUE, 6); + test.minusYears(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_long_invalidTooLargeMaxSubtractMax() { + YearMonth test = YearMonth.of(Year.MIN_VALUE, 12); + test.minusYears(Long.MAX_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_long_invalidTooLargeMaxSubtractMin() { + YearMonth test = YearMonth.of(Year.MIN_VALUE, 12); + test.minusYears(Long.MIN_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusYears_long_invalidTooSmall() { + YearMonth test = YearMonth.of(Year.MIN_VALUE, 6); + test.minusYears(1); + } + + //----------------------------------------------------------------------- + // minusMonths() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_minusMonths_long() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.minusMonths(1), YearMonth.of(2008, 5)); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_noChange_equal() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.minusMonths(0), test); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_overYears() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.minusMonths(6), YearMonth.of(2007, 12)); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_negative() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.minusMonths(-1), YearMonth.of(2008, 7)); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_negativeOverYear() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.minusMonths(-7), YearMonth.of(2009, 1)); + } + + @Test(groups={"tck"}) + public void test_minusMonths_long_big() { + YearMonth test = YearMonth.of(40, 6); + long months = 20L + Integer.MAX_VALUE; + assertEquals(test.minusMonths(months), YearMonth.of((int) (40L - months / 12), 6 - (int) (months % 12))); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_minusMonths_long_invalidTooLarge() { + YearMonth test = YearMonth.of(Year.MAX_VALUE, 12); + test.minusMonths(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusMonths_long_invalidTooLargeMaxSubtractMax() { + YearMonth test = YearMonth.of(Year.MAX_VALUE, 12); + test.minusMonths(Long.MAX_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_minusMonths_long_invalidTooLargeMaxSubtractMin() { + YearMonth test = YearMonth.of(Year.MAX_VALUE, 12); + test.minusMonths(Long.MIN_VALUE); + } + + @Test(expectedExceptions={DateTimeException.class}, groups={"tck"}) + public void test_minusMonths_long_invalidTooSmall() { + YearMonth test = YearMonth.of(Year.MIN_VALUE, 1); + test.minusMonths(1); + } + + //----------------------------------------------------------------------- + // adjustInto() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjustDate() { + YearMonth test = YearMonth.of(2008, 6); + LocalDate date = LocalDate.of(2007, 1, 1); + assertEquals(test.adjustInto(date), LocalDate.of(2008, 6, 1)); + } + + @Test(groups={"tck"}) + public void test_adjustDate_preserveDoM() { + YearMonth test = YearMonth.of(2011, 3); + LocalDate date = LocalDate.of(2008, 2, 29); + assertEquals(test.adjustInto(date), LocalDate.of(2011, 3, 29)); + } + + @Test(groups={"tck"}) + public void test_adjustDate_resolve() { + YearMonth test = YearMonth.of(2007, 2); + LocalDate date = LocalDate.of(2008, 3, 31); + assertEquals(test.adjustInto(date), LocalDate.of(2007, 2, 28)); + } + + @Test(groups={"tck"}) + public void test_adjustDate_equal() { + YearMonth test = YearMonth.of(2008, 6); + LocalDate date = LocalDate.of(2008, 6, 30); + assertEquals(test.adjustInto(date), date); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_adjustDate_null() { + TEST_2008_06.adjustInto((LocalDate) null); + } + + //----------------------------------------------------------------------- + // isLeapYear() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_isLeapYear() { + assertEquals(YearMonth.of(2007, 6).isLeapYear(), false); + assertEquals(YearMonth.of(2008, 6).isLeapYear(), true); + } + + //----------------------------------------------------------------------- + // lengthOfMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_lengthOfMonth_june() { + YearMonth test = YearMonth.of(2007, 6); + assertEquals(test.lengthOfMonth(), 30); + } + + @Test(groups={"tck"}) + public void test_lengthOfMonth_febNonLeap() { + YearMonth test = YearMonth.of(2007, 2); + assertEquals(test.lengthOfMonth(), 28); + } + + @Test(groups={"tck"}) + public void test_lengthOfMonth_febLeap() { + YearMonth test = YearMonth.of(2008, 2); + assertEquals(test.lengthOfMonth(), 29); + } + + //----------------------------------------------------------------------- + // lengthOfYear() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_lengthOfYear() { + assertEquals(YearMonth.of(2007, 6).lengthOfYear(), 365); + assertEquals(YearMonth.of(2008, 6).lengthOfYear(), 366); + } + + //----------------------------------------------------------------------- + // isValidDay(int) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_isValidDay_int_june() { + YearMonth test = YearMonth.of(2007, 6); + assertEquals(test.isValidDay(1), true); + assertEquals(test.isValidDay(30), true); + + assertEquals(test.isValidDay(-1), false); + assertEquals(test.isValidDay(0), false); + assertEquals(test.isValidDay(31), false); + assertEquals(test.isValidDay(32), false); + } + + @Test(groups={"tck"}) + public void test_isValidDay_int_febNonLeap() { + YearMonth test = YearMonth.of(2007, 2); + assertEquals(test.isValidDay(1), true); + assertEquals(test.isValidDay(28), true); + + assertEquals(test.isValidDay(-1), false); + assertEquals(test.isValidDay(0), false); + assertEquals(test.isValidDay(29), false); + assertEquals(test.isValidDay(32), false); + } + + @Test(groups={"tck"}) + public void test_isValidDay_int_febLeap() { + YearMonth test = YearMonth.of(2008, 2); + assertEquals(test.isValidDay(1), true); + assertEquals(test.isValidDay(29), true); + + assertEquals(test.isValidDay(-1), false); + assertEquals(test.isValidDay(0), false); + assertEquals(test.isValidDay(30), false); + assertEquals(test.isValidDay(32), false); + } + + //----------------------------------------------------------------------- + // atDay(int) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_atDay_int() { + YearMonth test = YearMonth.of(2008, 6); + assertEquals(test.atDay(30), LocalDate.of(2008, 6, 30)); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_atDay_int_invalidDay() { + YearMonth test = YearMonth.of(2008, 6); + test.atDay(31); + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_comparisons() { + doTest_comparisons_YearMonth( + YearMonth.of(-1, 1), + YearMonth.of(0, 1), + YearMonth.of(0, 12), + YearMonth.of(1, 1), + YearMonth.of(1, 2), + YearMonth.of(1, 12), + YearMonth.of(2008, 1), + YearMonth.of(2008, 6), + YearMonth.of(2008, 12) + ); + } + + void doTest_comparisons_YearMonth(YearMonth... localDates) { + for (int i = 0; i < localDates.length; i++) { + YearMonth a = localDates[i]; + for (int j = 0; j < localDates.length; j++) { + YearMonth b = localDates[j]; + if (i < j) { + assertTrue(a.compareTo(b) < 0, a + " <=> " + b); + assertEquals(a.isBefore(b), true, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else if (i > j) { + assertTrue(a.compareTo(b) > 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), true, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else { + assertEquals(a.compareTo(b), 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), true, a + " <=> " + b); + } + } + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_compareTo_ObjectNull() { + TEST_2008_06.compareTo(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isBefore_ObjectNull() { + TEST_2008_06.isBefore(null); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_isAfter_ObjectNull() { + TEST_2008_06.isAfter(null); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_equals() { + YearMonth a = YearMonth.of(2008, 6); + YearMonth b = YearMonth.of(2008, 6); + YearMonth c = YearMonth.of(2007, 6); + YearMonth d = YearMonth.of(2008, 5); + + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), true); + assertEquals(a.equals(c), false); + assertEquals(a.equals(d), false); + + assertEquals(b.equals(a), true); + assertEquals(b.equals(b), true); + assertEquals(b.equals(c), false); + assertEquals(b.equals(d), false); + + assertEquals(c.equals(a), false); + assertEquals(c.equals(b), false); + assertEquals(c.equals(c), true); + assertEquals(c.equals(d), false); + + assertEquals(d.equals(a), false); + assertEquals(d.equals(b), false); + assertEquals(d.equals(c), false); + assertEquals(d.equals(d), true); + } + + @Test(groups={"tck"}) + public void test_equals_itself_true() { + assertEquals(TEST_2008_06.equals(TEST_2008_06), true); + } + + @Test(groups={"tck"}) + public void test_equals_string_false() { + assertEquals(TEST_2008_06.equals("2007-07-15"), false); + } + + @Test(groups={"tck"}) + public void test_equals_null_false() { + assertEquals(TEST_2008_06.equals(null), false); + } + + //----------------------------------------------------------------------- + // hashCode() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleDates", groups={"tck"}) + public void test_hashCode(int y, int m) { + YearMonth a = YearMonth.of(y, m); + assertEquals(a.hashCode(), a.hashCode()); + YearMonth b = YearMonth.of(y, m); + assertEquals(a.hashCode(), b.hashCode()); + } + + @Test(groups={"tck"}) + public void test_hashCode_unique() { + Set uniques = new HashSet(201 * 12); + for (int i = 1900; i <= 2100; i++) { + for (int j = 1; j <= 12; j++) { + assertTrue(uniques.add(YearMonth.of(i, j).hashCode())); + } + } + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="sampleToString") + Object[][] provider_sampleToString() { + return new Object[][] { + {2008, 1, "2008-01"}, + {2008, 12, "2008-12"}, + {7, 5, "0007-05"}, + {0, 5, "0000-05"}, + {-1, 1, "-0001-01"}, + }; + } + + @Test(dataProvider="sampleToString", groups={"tck"}) + public void test_toString(int y, int m, String expected) { + YearMonth test = YearMonth.of(y, m); + String str = test.toString(); + assertEquals(str, expected); + } + + //----------------------------------------------------------------------- + // toString(DateTimeFormatter) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString_formatter() { + DateTimeFormatter f = DateTimeFormatters.pattern("y M"); + String t = YearMonth.of(2010, 12).toString(f); + assertEquals(t, "2010 12"); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_toString_formatter_null() { + YearMonth.of(2010, 12).toString(null); + } + +} diff --git a/test/java/time/tck/java/time/temporal/TestChrono.java b/test/java/time/tck/java/time/temporal/TestChrono.java new file mode 100644 index 0000000000000000000000000000000000000000..cd30519dd047f6b0a2dbf17f868ef9a8d9388c84 --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TestChrono.java @@ -0,0 +1,206 @@ +/* + * 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. + * + * 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) 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 tck.java.time.temporal; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Locale; +import java.util.Set; + +import java.time.temporal.Chrono; +import java.time.temporal.ChronoField; +import java.time.calendar.HijrahChrono; +import java.time.calendar.JapaneseChrono; +import java.time.calendar.MinguoChrono; +import java.time.calendar.ThaiBuddhistChrono; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.ISOChrono; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test Chrono class. + */ +@Test +public class TestChrono { + + @BeforeMethod(groups="tck") + public void setUp() { + // Ensure each of the classes are initialized (until initialization is fixed) + Chrono c; + c = HijrahChrono.INSTANCE; + c = ISOChrono.INSTANCE; + c = JapaneseChrono.INSTANCE; + c = MinguoChrono.INSTANCE; + c = ThaiBuddhistChrono.INSTANCE; + c.toString(); // avoids variable being marked as unused + } + + //----------------------------------------------------------------------- + // regular data factory for names and descriptions of available calendars + //----------------------------------------------------------------------- + @DataProvider(name = "calendars") + Object[][] data_of_calendars() { + return new Object[][] { + {"Hijrah", "islamicc", "Hijrah calendar"}, + {"ISO", "iso8601", "ISO calendar"}, + {"Japanese", "japanese", "Japanese calendar"}, + {"Minguo", "roc", "Minguo Calendar"}, + {"ThaiBuddhist", "buddhist", "ThaiBuddhist calendar"}, + }; + } + + @Test(dataProvider = "calendars") + public void test_getters(String chronoId, String calendarSystemType, String description) { + Chrono chrono = Chrono.of(chronoId); + assertNotNull(chrono, "Required calendar not found by ID: " + chronoId); + assertEquals(chrono.getId(), chronoId); + assertEquals(chrono.getCalendarType(), calendarSystemType); + } + + @Test(dataProvider = "calendars") + public void test_required_calendars(String chronoId, String calendarSystemType, String description) { + Chrono chrono = Chrono.of(chronoId); + assertNotNull(chrono, "Required calendar not found by ID: " + chronoId); + chrono = Chrono.of(calendarSystemType); + assertNotNull(chrono, "Required calendar not found by type: " + chronoId); + Set> cals = Chrono.getAvailableChronologies(); + assertTrue(cals.contains(chrono), "Required calendar not found in set of available calendars"); + } + + @Test(groups="tck") + public void test_calendar_list() { + Set> chronos = Chrono.getAvailableChronologies(); + assertNotNull(chronos, "Required list of calendars must be non-null"); + for (Chrono chrono : chronos) { + Chrono lookup = Chrono.of(chrono.getId()); + assertNotNull(lookup, "Required calendar not found: " + chrono); + } + assertEquals(chronos.size() >= data_of_calendars().length, true, "Chrono.getAvailableChronologies().size = " + chronos.size() + + ", expected >= " + data_of_calendars().length); + } + + /** + * Compute the number of days from the Epoch and compute the date from the number of days. + */ + @Test(dataProvider = "calendars", groups="tck") + public void test_epoch(String name, String alias, String description) { + Chrono chrono = Chrono.of(name); // a chronology. In practice this is rarely hardcoded + ChronoLocalDate date1 = chrono.dateNow(); + long epoch1 = date1.getLong(ChronoField.EPOCH_DAY); + ChronoLocalDate date2 = date1.with(ChronoField.EPOCH_DAY, epoch1); + assertEquals(date1, date2, "Date from epoch day is not same date: " + date1 + " != " + date2); + long epoch2 = date1.getLong(ChronoField.EPOCH_DAY); + assertEquals(epoch1, epoch2, "Epoch day not the same: " + epoch1 + " != " + epoch2); + } + + //----------------------------------------------------------------------- + // locale based lookup + //----------------------------------------------------------------------- + @DataProvider(name = "calendarsystemtype") + Object[][] data_CalendarType() { + return new Object[][] { + {HijrahChrono.INSTANCE, "islamicc"}, + {ISOChrono.INSTANCE, "iso8601"}, + {JapaneseChrono.INSTANCE, "japanese"}, + {MinguoChrono.INSTANCE, "roc"}, + {ThaiBuddhistChrono.INSTANCE, "buddhist"}, + }; + } + + @Test(dataProvider = "calendarsystemtype", groups="tck") + public void test_getCalendarType(Chrono chrono, String calendarType) { + assertEquals(chrono.getCalendarType(), calendarType); + } + + @Test(dataProvider = "calendarsystemtype", groups="tck") + public void test_lookupLocale(Chrono chrono, String calendarType) { + Locale locale = new Locale.Builder().setLanguage("en").setRegion("CA").setUnicodeLocaleKeyword("ca", calendarType).build(); + assertEquals(Chrono.ofLocale(locale), chrono); + } + + + //----------------------------------------------------------------------- + // serialization; serialize and check each calendar system + //----------------------------------------------------------------------- + @Test(groups={"implementation"}, dataProvider = "calendarsystemtype") + public > void test_chronoSerializationSingleton(C chrono, String calendarType) throws Exception { + C orginal = chrono; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(orginal); + out.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + @SuppressWarnings("unchecked") + C ser = (C) in.readObject(); + assertSame(ser, chrono, "Deserialized Chrono is not the singleton serialized"); + } + +} diff --git a/test/java/time/tck/java/time/temporal/TestChronoLocalDate.java b/test/java/time/tck/java/time/temporal/TestChronoLocalDate.java new file mode 100644 index 0000000000000000000000000000000000000000..95caf573a68d21178d10fdaac3b14d069e9c7a8f --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TestChronoLocalDate.java @@ -0,0 +1,503 @@ +/* + * 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) 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 tck.java.time.temporal; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.ChronoUnit; +import java.time.temporal.SimplePeriod; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.format.DateTimeBuilder; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.ValueRange; +import java.time.temporal.TemporalUnit; +import java.time.temporal.ISOChrono; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test assertions that must be true for the built-in ISO chronology. + */ +@Test +public class TestChronoLocalDate { + + //----------------------------------------------------------------------- + // regular data factory for names and descriptions of ISO calendar + //----------------------------------------------------------------------- + @DataProvider(name = "calendars") + Chrono[][] data_of_calendars() { + return new Chrono[][]{ + {ISOChrono.INSTANCE}, + }; + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badWithAdjusterChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDate date = chrono.date(refDate); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDate date2 = chrono2.date(refDate); + TemporalAdjuster adjuster = new FixedAdjuster(date2); + if (chrono != chrono2) { + try { + date.with(adjuster); + Assert.fail("WithAdjuster should have thrown a ClassCastException"); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDate result = date.with(adjuster); + assertEquals(result, date2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badPlusAdjusterChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDate date = chrono.date(refDate); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDate date2 = chrono2.date(refDate); + TemporalAdder adjuster = new FixedAdjuster(date2); + if (chrono != chrono2) { + try { + date.plus(adjuster); + Assert.fail("WithAdjuster should have thrown a ClassCastException"); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDate result = date.plus(adjuster); + assertEquals(result, date2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badMinusAdjusterChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDate date = chrono.date(refDate); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDate date2 = chrono2.date(refDate); + TemporalSubtractor adjuster = new FixedAdjuster(date2); + if (chrono != chrono2) { + try { + date.minus(adjuster); + Assert.fail("WithAdjuster should have thrown a ClassCastException"); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDate result = date.minus(adjuster); + assertEquals(result, date2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badPlusTemporalUnitChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDate date = chrono.date(refDate); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDate date2 = chrono2.date(refDate); + TemporalUnit adjuster = new FixedTemporalUnit(date2); + if (chrono != chrono2) { + try { + date.plus(1, adjuster); + Assert.fail("TemporalUnit.doPlus plus should have thrown a ClassCastException" + date.getClass() + + ", can not be cast to " + date2.getClass()); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDate result = date.plus(1, adjuster); + assertEquals(result, date2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badMinusTemporalUnitChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDate date = chrono.date(refDate); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDate date2 = chrono2.date(refDate); + TemporalUnit adjuster = new FixedTemporalUnit(date2); + if (chrono != chrono2) { + try { + date.minus(1, adjuster); + Assert.fail("TemporalUnit.doPlus minus should have thrown a ClassCastException" + date.getClass() + + ", can not be cast to " + date2.getClass()); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDate result = date.minus(1, adjuster); + assertEquals(result, date2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badTemporalFieldChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDate date = chrono.date(refDate); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDate date2 = chrono2.date(refDate); + TemporalField adjuster = new FixedTemporalField(date2); + if (chrono != chrono2) { + try { + date.with(adjuster, 1); + Assert.fail("TemporalField doWith() should have thrown a ClassCastException" + date.getClass() + + ", can not be cast to " + date2.getClass()); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDate result = date.with(adjuster, 1); + assertEquals(result, date2, "TemporalField doWith() failed to replace date"); + } + } + } + + //----------------------------------------------------------------------- + // isBefore, isAfter, isEqual, DATE_COMPARATOR + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="calendars") + public void test_date_comparisons(Chrono chrono) { + List> dates = new ArrayList<>(); + + ChronoLocalDate date = chrono.date(LocalDate.of(1900, 1, 1)); + + // Insert dates in order, no duplicates + dates.add(date.minus(1000, ChronoUnit.YEARS)); + dates.add(date.minus(100, ChronoUnit.YEARS)); + dates.add(date.minus(10, ChronoUnit.YEARS)); + dates.add(date.minus(1, ChronoUnit.YEARS)); + dates.add(date.minus(1, ChronoUnit.MONTHS)); + dates.add(date.minus(1, ChronoUnit.WEEKS)); + dates.add(date.minus(1, ChronoUnit.DAYS)); + dates.add(date); + dates.add(date.plus(1, ChronoUnit.DAYS)); + dates.add(date.plus(1, ChronoUnit.WEEKS)); + dates.add(date.plus(1, ChronoUnit.MONTHS)); + dates.add(date.plus(1, ChronoUnit.YEARS)); + dates.add(date.plus(10, ChronoUnit.YEARS)); + dates.add(date.plus(100, ChronoUnit.YEARS)); + dates.add(date.plus(1000, ChronoUnit.YEARS)); + + // Check these dates against the corresponding dates for every calendar + for (Chrono[] clist : data_of_calendars()) { + List> otherDates = new ArrayList<>(); + Chrono chrono2 = clist[0]; + for (ChronoLocalDate d : dates) { + otherDates.add(chrono2.date(d)); + } + + // Now compare the sequence of original dates with the sequence of converted dates + for (int i = 0; i < dates.size(); i++) { + ChronoLocalDate a = dates.get(i); + for (int j = 0; j < otherDates.size(); j++) { + ChronoLocalDate b = otherDates.get(j); + int cmp = ChronoLocalDate.DATE_COMPARATOR.compare(a, b); + if (i < j) { + assertTrue(cmp < 0, a + " compare " + b); + assertEquals(a.isBefore(b), true, a + " isBefore " + b); + assertEquals(a.isAfter(b), false, a + " isAfter " + b); + assertEquals(a.isEqual(b), false, a + " isEqual " + b); + } else if (i > j) { + assertTrue(cmp > 0, a + " compare " + b); + assertEquals(a.isBefore(b), false, a + " isBefore " + b); + assertEquals(a.isAfter(b), true, a + " isAfter " + b); + assertEquals(a.isEqual(b), false, a + " isEqual " + b); + } else { + assertTrue(cmp == 0, a + " compare " + b); + assertEquals(a.isBefore(b), false, a + " isBefore " + b); + assertEquals(a.isAfter(b), false, a + " isAfter " + b); + assertEquals(a.isEqual(b), true, a + " isEqual " + b); + } + } + } + } + } + + public void test_date_comparator_checkGenerics_ISO() { + List> dates = new ArrayList<>(); + ChronoLocalDate date = LocalDate.of(1900, 1, 1); + + // Insert dates in order, no duplicates + dates.add(date.minus(10, ChronoUnit.YEARS)); + dates.add(date.minus(1, ChronoUnit.YEARS)); + dates.add(date.minus(1, ChronoUnit.MONTHS)); + dates.add(date.minus(1, ChronoUnit.WEEKS)); + dates.add(date.minus(1, ChronoUnit.DAYS)); + dates.add(date); + dates.add(date.plus(1, ChronoUnit.DAYS)); + dates.add(date.plus(1, ChronoUnit.WEEKS)); + dates.add(date.plus(1, ChronoUnit.MONTHS)); + dates.add(date.plus(1, ChronoUnit.YEARS)); + dates.add(date.plus(10, ChronoUnit.YEARS)); + + List> copy = new ArrayList<>(dates); + Collections.shuffle(copy); + Collections.sort(copy, ChronoLocalDate.DATE_COMPARATOR); + assertEquals(copy, dates); + assertTrue(ChronoLocalDate.DATE_COMPARATOR.compare(copy.get(0), copy.get(1)) < 0); + } + + public void test_date_comparator_checkGenerics_LocalDate() { + List dates = new ArrayList<>(); + LocalDate date = LocalDate.of(1900, 1, 1); + + // Insert dates in order, no duplicates + dates.add(date.minus(10, ChronoUnit.YEARS)); + dates.add(date.minus(1, ChronoUnit.YEARS)); + dates.add(date.minus(1, ChronoUnit.MONTHS)); + dates.add(date.minus(1, ChronoUnit.WEEKS)); + dates.add(date.minus(1, ChronoUnit.DAYS)); + dates.add(date); + dates.add(date.plus(1, ChronoUnit.DAYS)); + dates.add(date.plus(1, ChronoUnit.WEEKS)); + dates.add(date.plus(1, ChronoUnit.MONTHS)); + dates.add(date.plus(1, ChronoUnit.YEARS)); + dates.add(date.plus(10, ChronoUnit.YEARS)); + + List copy = new ArrayList<>(dates); + Collections.shuffle(copy); + Collections.sort(copy, ChronoLocalDate.DATE_COMPARATOR); + assertEquals(copy, dates); + assertTrue(ChronoLocalDate.DATE_COMPARATOR.compare(copy.get(0), copy.get(1)) < 0); + } + + //----------------------------------------------------------------------- + // Test Serialization of ISO via chrono API + //----------------------------------------------------------------------- + @Test( groups={"tck"}, dataProvider="calendars") + public > void test_ChronoSerialization(C chrono) throws Exception { + LocalDate ref = LocalDate.of(2000, 1, 5); + ChronoLocalDate orginal = chrono.date(ref); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(orginal); + out.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + @SuppressWarnings("unchecked") + ChronoLocalDate ser = (ChronoLocalDate) in.readObject(); + assertEquals(ser, orginal, "deserialized date is wrong"); + } + + /** + * FixedAdjusted returns a fixed Temporal in all adjustments. + * Construct an adjuster with the Temporal that should be returned from adjust. + */ + static class FixedAdjuster implements TemporalAdjuster, TemporalAdder, TemporalSubtractor { + private Temporal datetime; + + FixedAdjuster(Temporal datetime) { + this.datetime = datetime; + } + + @Override + public Temporal adjustInto(Temporal ignore) { + return datetime; + } + + @Override + public Temporal addTo(Temporal ignore) { + return datetime; + } + + @Override + public Temporal subtractFrom(Temporal ignore) { + return datetime; + } + + } + + /** + * FixedTemporalUnit returns a fixed Temporal in all adjustments. + * Construct an FixedTemporalUnit with the Temporal that should be returned from doPlus. + */ + static class FixedTemporalUnit implements TemporalUnit { + private Temporal temporal; + + FixedTemporalUnit(Temporal temporal) { + this.temporal = temporal; + } + + @Override + public String getName() { + return "FixedTemporalUnit"; + } + + @Override + public Duration getDuration() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isDurationEstimated() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isSupported(Temporal temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @SuppressWarnings("unchecked") + @Override + public R doPlus(R dateTime, long periodToAdd) { + return (R) this.temporal; + } + + @Override + public SimplePeriod between(R dateTime1, R dateTime2) { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + /** + * FixedTemporalField returns a fixed Temporal in all adjustments. + * Construct an FixedTemporalField with the Temporal that should be returned from doWith. + */ + static class FixedTemporalField implements TemporalField { + private Temporal temporal; + FixedTemporalField(Temporal temporal) { + this.temporal = temporal; + } + + @Override + public String getName() { + return "FixedTemporalField"; + } + + @Override + public TemporalUnit getBaseUnit() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public TemporalUnit getRangeUnit() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ValueRange range() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean doIsSupported(TemporalAccessor temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ValueRange doRange(TemporalAccessor temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public long doGet(TemporalAccessor temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @SuppressWarnings("unchecked") + @Override + public R doWith(R temporal, long newValue) { + return (R) this.temporal; + } + + @Override + public boolean resolve(DateTimeBuilder builder, long value) { + throw new UnsupportedOperationException("Not supported yet."); + } + + } +} diff --git a/test/java/time/tck/java/time/temporal/TestChronoLocalDateTime.java b/test/java/time/tck/java/time/temporal/TestChronoLocalDateTime.java new file mode 100644 index 0000000000000000000000000000000000000000..c51be6c69b8080aca0a0c4d7b2212bb64978e77c --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TestChronoLocalDateTime.java @@ -0,0 +1,470 @@ +/* + * 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) 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 tck.java.time.temporal; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.List; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoLocalDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.SimplePeriod; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.format.DateTimeBuilder; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.ValueRange; +import java.time.temporal.TemporalUnit; +import java.time.temporal.ISOChrono; +import java.time.calendar.HijrahChrono; +import java.time.calendar.JapaneseChrono; +import java.time.calendar.MinguoChrono; +import java.time.calendar.ThaiBuddhistChrono; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test assertions that must be true for all built-in chronologies. + */ +@Test +public class TestChronoLocalDateTime { + + //----------------------------------------------------------------------- + // regular data factory for names and descriptions of available calendars + //----------------------------------------------------------------------- + @DataProvider(name = "calendars") + Chrono[][] data_of_calendars() { + return new Chrono[][]{ + {HijrahChrono.INSTANCE}, + {ISOChrono.INSTANCE}, + {JapaneseChrono.INSTANCE}, + {MinguoChrono.INSTANCE}, + {ThaiBuddhistChrono.INSTANCE}}; + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badWithAdjusterChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDateTime cdt = chrono.date(refDate).atTime(LocalTime.NOON); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDateTime cdt2 = chrono2.date(refDate).atTime(LocalTime.NOON); + TemporalAdjuster adjuster = new FixedAdjuster(cdt2); + if (chrono != chrono2) { + try { + cdt.with(adjuster); + Assert.fail("WithAdjuster should have thrown a ClassCastException, " + + "required: " + cdt + ", supplied: " + cdt2); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDateTime result = cdt.with(adjuster); + assertEquals(result, cdt2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badPlusAdjusterChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDateTime cdt = chrono.date(refDate).atTime(LocalTime.NOON); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDateTime cdt2 = chrono2.date(refDate).atTime(LocalTime.NOON); + TemporalAdder adjuster = new FixedAdjuster(cdt2); + if (chrono != chrono2) { + try { + cdt.plus(adjuster); + Assert.fail("WithAdjuster should have thrown a ClassCastException, " + + "required: " + cdt + ", supplied: " + cdt2); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDateTime result = cdt.plus(adjuster); + assertEquals(result, cdt2, "WithAdjuster failed to replace date time"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badMinusAdjusterChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDateTime cdt = chrono.date(refDate).atTime(LocalTime.NOON); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDateTime cdt2 = chrono2.date(refDate).atTime(LocalTime.NOON); + TemporalSubtractor adjuster = new FixedAdjuster(cdt2); + if (chrono != chrono2) { + try { + cdt.minus(adjuster); + Assert.fail("WithAdjuster should have thrown a ClassCastException, " + + "required: " + cdt + ", supplied: " + cdt2); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDateTime result = cdt.minus(adjuster); + assertEquals(result, cdt2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badPlusTemporalUnitChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDateTime cdt = chrono.date(refDate).atTime(LocalTime.NOON); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDateTime cdt2 = chrono2.date(refDate).atTime(LocalTime.NOON); + TemporalUnit adjuster = new FixedTemporalUnit(cdt2); + if (chrono != chrono2) { + try { + cdt.plus(1, adjuster); + Assert.fail("TemporalUnit.doPlus plus should have thrown a ClassCastException" + cdt + + ", can not be cast to " + cdt2); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDateTime result = cdt.plus(1, adjuster); + assertEquals(result, cdt2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badMinusTemporalUnitChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDateTime cdt = chrono.date(refDate).atTime(LocalTime.NOON); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDateTime cdt2 = chrono2.date(refDate).atTime(LocalTime.NOON); + TemporalUnit adjuster = new FixedTemporalUnit(cdt2); + if (chrono != chrono2) { + try { + cdt.minus(1, adjuster); + Assert.fail("TemporalUnit.doPlus minus should have thrown a ClassCastException" + cdt.getClass() + + ", can not be cast to " + cdt2.getClass()); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDateTime result = cdt.minus(1, adjuster); + assertEquals(result, cdt2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badTemporalFieldChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoLocalDateTime cdt = chrono.date(refDate).atTime(LocalTime.NOON); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoLocalDateTime cdt2 = chrono2.date(refDate).atTime(LocalTime.NOON); + TemporalField adjuster = new FixedTemporalField(cdt2); + if (chrono != chrono2) { + try { + cdt.with(adjuster, 1); + Assert.fail("TemporalField doWith() should have thrown a ClassCastException" + cdt.getClass() + + ", can not be cast to " + cdt2.getClass()); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoLocalDateTime result = cdt.with(adjuster, 1); + assertEquals(result, cdt2, "TemporalField doWith() failed to replace date"); + } + } + } + + //----------------------------------------------------------------------- + // isBefore, isAfter, isEqual + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="calendars") + public void test_datetime_comparisons(Chrono chrono) { + List> dates = new ArrayList<>(); + + ChronoLocalDateTime date = chrono.date(LocalDate.of(1900, 1, 1)).atTime(LocalTime.MIN); + + // Insert dates in order, no duplicates + dates.add(date.minus(100, ChronoUnit.YEARS)); + dates.add(date.minus(1, ChronoUnit.YEARS)); + dates.add(date.minus(1, ChronoUnit.MONTHS)); + dates.add(date.minus(1, ChronoUnit.WEEKS)); + dates.add(date.minus(1, ChronoUnit.DAYS)); + dates.add(date.minus(1, ChronoUnit.HOURS)); + dates.add(date.minus(1, ChronoUnit.MINUTES)); + dates.add(date.minus(1, ChronoUnit.SECONDS)); + dates.add(date.minus(1, ChronoUnit.NANOS)); + dates.add(date); + dates.add(date.plus(1, ChronoUnit.NANOS)); + dates.add(date.plus(1, ChronoUnit.SECONDS)); + dates.add(date.plus(1, ChronoUnit.MINUTES)); + dates.add(date.plus(1, ChronoUnit.HOURS)); + dates.add(date.plus(1, ChronoUnit.DAYS)); + dates.add(date.plus(1, ChronoUnit.WEEKS)); + dates.add(date.plus(1, ChronoUnit.MONTHS)); + dates.add(date.plus(1, ChronoUnit.YEARS)); + dates.add(date.plus(100, ChronoUnit.YEARS)); + + // Check these dates against the corresponding dates for every calendar + for (Chrono[] clist : data_of_calendars()) { + List> otherDates = new ArrayList<>(); + Chrono chrono2 = clist[0]; + for (ChronoLocalDateTime d : dates) { + otherDates.add(chrono2.date(d).atTime(d.getTime())); + } + + // Now compare the sequence of original dates with the sequence of converted dates + for (int i = 0; i < dates.size(); i++) { + ChronoLocalDateTime a = dates.get(i); + for (int j = 0; j < otherDates.size(); j++) { + ChronoLocalDateTime b = otherDates.get(j); + int cmp = ChronoLocalDateTime.DATE_TIME_COMPARATOR.compare(a, b); + if (i < j) { + assertTrue(cmp < 0, a + " compare " + b); + assertEquals(a.isBefore(b), true, a + " isBefore " + b); + assertEquals(a.isAfter(b), false, a + " isAfter " + b); + assertEquals(a.isEqual(b), false, a + " isEqual " + b); + } else if (i > j) { + assertTrue(cmp > 0, a + " compare " + b); + assertEquals(a.isBefore(b), false, a + " isBefore " + b); + assertEquals(a.isAfter(b), true, a + " isAfter " + b); + assertEquals(a.isEqual(b), false, a + " isEqual " + b); + } else { + assertTrue(cmp == 0, a + " compare " + b); + assertEquals(a.isBefore(b), false, a + " isBefore " + b); + assertEquals(a.isAfter(b), false, a + " isAfter " + b); + assertEquals(a.isEqual(b), true, a + " isEqual " + b); + } + } + } + } + } + + //----------------------------------------------------------------------- + // Test Serialization of ISO via chrono API + //----------------------------------------------------------------------- + @Test( groups={"tck"}, dataProvider="calendars") + public > void test_ChronoLocalDateTimeSerialization(C chrono) throws Exception { + LocalDateTime ref = LocalDate.of(2000, 1, 5).atTime(12, 1, 2, 3); + ChronoLocalDateTime orginal = chrono.date(ref).atTime(ref.getTime()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(orginal); + out.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + @SuppressWarnings("unchecked") + ChronoLocalDateTime ser = (ChronoLocalDateTime) in.readObject(); + assertEquals(ser, orginal, "deserialized date is wrong"); + } + + /** + * FixedAdjusted returns a fixed Temporal in all adjustments. + * Construct an adjuster with the Temporal that should be returned from adjust. + */ + static class FixedAdjuster implements TemporalAdjuster, TemporalAdder, TemporalSubtractor { + private Temporal datetime; + + FixedAdjuster(Temporal datetime) { + this.datetime = datetime; + } + + @Override + public Temporal adjustInto(Temporal ignore) { + return datetime; + } + + @Override + public Temporal addTo(Temporal ignore) { + return datetime; + } + + @Override + public Temporal subtractFrom(Temporal ignore) { + return datetime; + } + + } + + /** + * FixedTemporalUnit returns a fixed Temporal in all adjustments. + * Construct an FixedTemporalUnit with the Temporal that should be returned from doPlus. + */ + static class FixedTemporalUnit implements TemporalUnit { + private Temporal temporal; + + FixedTemporalUnit(Temporal temporal) { + this.temporal = temporal; + } + + @Override + public String getName() { + return "FixedTemporalUnit"; + } + + @Override + public Duration getDuration() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isDurationEstimated() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isSupported(Temporal temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @SuppressWarnings("unchecked") + @Override + public R doPlus(R dateTime, long periodToAdd) { + return (R) this.temporal; + } + + @Override + public SimplePeriod between(R dateTime1, R dateTime2) { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + /** + * FixedTemporalField returns a fixed Temporal in all adjustments. + * Construct an FixedTemporalField with the Temporal that should be returned from doWith. + */ + static class FixedTemporalField implements TemporalField { + private Temporal temporal; + FixedTemporalField(Temporal temporal) { + this.temporal = temporal; + } + + @Override + public String getName() { + return "FixedTemporalField"; + } + + @Override + public TemporalUnit getBaseUnit() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public TemporalUnit getRangeUnit() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ValueRange range() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean doIsSupported(TemporalAccessor temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ValueRange doRange(TemporalAccessor temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public long doGet(TemporalAccessor temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @SuppressWarnings("unchecked") + @Override + public R doWith(R temporal, long newValue) { + return (R) this.temporal; + } + + @Override + public boolean resolve(DateTimeBuilder builder, long value) { + throw new UnsupportedOperationException("Not supported yet."); + } + + } +} diff --git a/test/java/time/tck/java/time/temporal/TestChronoZonedDateTime.java b/test/java/time/tck/java/time/temporal/TestChronoZonedDateTime.java new file mode 100644 index 0000000000000000000000000000000000000000..e89725bb3d3aa3a4ef42eb74a33921ee10526ee7 --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TestChronoZonedDateTime.java @@ -0,0 +1,475 @@ +/* + * 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) 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 tck.java.time.temporal; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.List; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoUnit; +import java.time.temporal.ChronoZonedDateTime; +import java.time.temporal.SimplePeriod; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.format.DateTimeBuilder; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.ValueRange; +import java.time.temporal.ISOChrono; +import java.time.temporal.TemporalUnit; +import java.time.calendar.HijrahChrono; +import java.time.calendar.JapaneseChrono; +import java.time.calendar.MinguoChrono; +import java.time.calendar.ThaiBuddhistChrono; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test assertions that must be true for all built-in chronologies. + */ +@Test +public class TestChronoZonedDateTime { + + //----------------------------------------------------------------------- + // regular data factory for names and descriptions of available calendars + //----------------------------------------------------------------------- + @DataProvider(name = "calendars") + Chrono[][] data_of_calendars() { + return new Chrono[][]{ + {HijrahChrono.INSTANCE}, + {ISOChrono.INSTANCE}, + {JapaneseChrono.INSTANCE}, + {MinguoChrono.INSTANCE}, + {ThaiBuddhistChrono.INSTANCE}, + }; + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badWithAdjusterChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoZonedDateTime czdt = chrono.date(refDate).atTime(LocalTime.NOON).atZone(ZoneOffset.UTC); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoZonedDateTime czdt2 = chrono2.date(refDate).atTime(LocalTime.NOON).atZone(ZoneOffset.UTC); + TemporalAdjuster adjuster = new FixedAdjuster(czdt2); + if (chrono != chrono2) { + try { + czdt.with(adjuster); + Assert.fail("WithAdjuster should have thrown a ClassCastException, " + + "required: " + czdt + ", supplied: " + czdt2); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + ChronoZonedDateTime result = czdt.with(adjuster); + assertEquals(result, czdt2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badPlusAdjusterChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoZonedDateTime czdt = chrono.date(refDate).atTime(LocalTime.NOON).atZone(ZoneOffset.UTC); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoZonedDateTime czdt2 = chrono2.date(refDate).atTime(LocalTime.NOON).atZone(ZoneOffset.UTC); + TemporalAdder adjuster = new FixedAdjuster(czdt2); + if (chrono != chrono2) { + try { + czdt.plus(adjuster); + Assert.fail("WithAdjuster should have thrown a ClassCastException, " + + "required: " + czdt + ", supplied: " + czdt2); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoZonedDateTime result = czdt.plus(adjuster); + assertEquals(result, czdt2, "WithAdjuster failed to replace date time"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badMinusAdjusterChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoZonedDateTime czdt = chrono.date(refDate).atTime(LocalTime.NOON).atZone(ZoneOffset.UTC); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoZonedDateTime czdt2 = chrono2.date(refDate).atTime(LocalTime.NOON).atZone(ZoneOffset.UTC); + TemporalSubtractor adjuster = new FixedAdjuster(czdt2); + if (chrono != chrono2) { + try { + czdt.minus(adjuster); + Assert.fail("WithAdjuster should have thrown a ClassCastException, " + + "required: " + czdt + ", supplied: " + czdt2); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoZonedDateTime result = czdt.minus(adjuster); + assertEquals(result, czdt2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badPlusTemporalUnitChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoZonedDateTime czdt = chrono.date(refDate).atTime(LocalTime.NOON).atZone(ZoneOffset.UTC); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoZonedDateTime czdt2 = chrono2.date(refDate).atTime(LocalTime.NOON).atZone(ZoneOffset.UTC); + TemporalUnit adjuster = new FixedTemporalUnit(czdt2); + if (chrono != chrono2) { + try { + czdt.plus(1, adjuster); + Assert.fail("TemporalUnit.doPlus plus should have thrown a ClassCastException, " + czdt + + " can not be cast to " + czdt2); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoZonedDateTime result = czdt.plus(1, adjuster); + assertEquals(result, czdt2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badMinusTemporalUnitChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoZonedDateTime czdt = chrono.date(refDate).atTime(LocalTime.NOON).atZone(ZoneOffset.UTC); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoZonedDateTime czdt2 = chrono2.date(refDate).atTime(LocalTime.NOON).atZone(ZoneOffset.UTC); + TemporalUnit adjuster = new FixedTemporalUnit(czdt2); + if (chrono != chrono2) { + try { + czdt.minus(1, adjuster); + Assert.fail("TemporalUnit.doPlus minus should have thrown a ClassCastException, " + czdt.getClass() + + " can not be cast to " + czdt2.getClass()); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoZonedDateTime result = czdt.minus(1, adjuster); + assertEquals(result, czdt2, "WithAdjuster failed to replace date"); + } + } + } + + @Test(groups={"tck"}, dataProvider="calendars") + public void test_badTemporalFieldChrono(Chrono chrono) { + LocalDate refDate = LocalDate.of(1900, 1, 1); + ChronoZonedDateTime czdt = chrono.date(refDate).atTime(LocalTime.NOON).atZone(ZoneOffset.UTC); + for (Chrono[] clist : data_of_calendars()) { + Chrono chrono2 = clist[0]; + ChronoZonedDateTime czdt2 = chrono2.date(refDate).atTime(LocalTime.NOON).atZone(ZoneOffset.UTC); + TemporalField adjuster = new FixedTemporalField(czdt2); + if (chrono != chrono2) { + try { + czdt.with(adjuster, 1); + Assert.fail("TemporalField doWith() should have thrown a ClassCastException, " + czdt.getClass() + + " can not be cast to " + czdt2.getClass()); + } catch (ClassCastException cce) { + // Expected exception; not an error + } + } else { + // Same chronology, + ChronoZonedDateTime result = czdt.with(adjuster, 1); + assertEquals(result, czdt2, "TemporalField doWith() failed to replace date"); + } + } + } + + //----------------------------------------------------------------------- + // isBefore, isAfter, isEqual, INSTANT_COMPARATOR test a Chrono against the other Chronos + //----------------------------------------------------------------------- + @Test(groups={"tck"}, dataProvider="calendars") + public void test_zonedDateTime_comparisons(Chrono chrono) { + List> dates = new ArrayList<>(); + + ChronoZonedDateTime date = chrono.date(LocalDate.of(1900, 1, 1)) + .atTime(LocalTime.MIN) + .atZone(ZoneOffset.UTC); + + // Insert dates in order, no duplicates + dates.add(date.minus(100, ChronoUnit.YEARS)); + dates.add(date.minus(1, ChronoUnit.YEARS)); + dates.add(date.minus(1, ChronoUnit.MONTHS)); + dates.add(date.minus(1, ChronoUnit.WEEKS)); + dates.add(date.minus(1, ChronoUnit.DAYS)); + dates.add(date.minus(1, ChronoUnit.HOURS)); + dates.add(date.minus(1, ChronoUnit.MINUTES)); + dates.add(date.minus(1, ChronoUnit.SECONDS)); + dates.add(date.minus(1, ChronoUnit.NANOS)); + dates.add(date); + dates.add(date.plus(1, ChronoUnit.NANOS)); + dates.add(date.plus(1, ChronoUnit.SECONDS)); + dates.add(date.plus(1, ChronoUnit.MINUTES)); + dates.add(date.plus(1, ChronoUnit.HOURS)); + dates.add(date.plus(1, ChronoUnit.DAYS)); + dates.add(date.plus(1, ChronoUnit.WEEKS)); + dates.add(date.plus(1, ChronoUnit.MONTHS)); + dates.add(date.plus(1, ChronoUnit.YEARS)); + dates.add(date.plus(100, ChronoUnit.YEARS)); + + // Check these dates against the corresponding dates for every calendar + for (Chrono[] clist : data_of_calendars()) { + List> otherDates = new ArrayList<>(); + Chrono chrono2 = ISOChrono.INSTANCE; //clist[0]; + for (ChronoZonedDateTime d : dates) { + otherDates.add(chrono2.date(d).atTime(d.getTime()).atZone(d.getZone())); + } + + // Now compare the sequence of original dates with the sequence of converted dates + for (int i = 0; i < dates.size(); i++) { + ChronoZonedDateTime a = dates.get(i); + for (int j = 0; j < otherDates.size(); j++) { + ChronoZonedDateTime b = otherDates.get(j); + int cmp = ChronoZonedDateTime.INSTANT_COMPARATOR.compare(a, b); + if (i < j) { + assertTrue(cmp < 0, a + " compare " + b); + assertEquals(a.isBefore(b), true, a + " isBefore " + b); + assertEquals(a.isAfter(b), false, a + " ifAfter " + b); + assertEquals(a.isEqual(b), false, a + " isEqual " + b); + } else if (i > j) { + assertTrue(cmp > 0, a + " compare " + b); + assertEquals(a.isBefore(b), false, a + " isBefore " + b); + assertEquals(a.isAfter(b), true, a + " ifAfter " + b); + assertEquals(a.isEqual(b), false, a + " isEqual " + b); + } else { + assertTrue(cmp == 0, a + " compare " + b); + assertEquals(a.isBefore(b), false, a + " isBefore " + b); + assertEquals(a.isAfter(b), false, a + " ifAfter " + b); + assertEquals(a.isEqual(b), true, a + " isEqual " + b); + } + } + } + } + } + + //----------------------------------------------------------------------- + // Test Serialization of ISO via chrono API + //----------------------------------------------------------------------- + @Test( groups={"tck"}, dataProvider="calendars") + public > void test_ChronoZonedDateTimeSerialization(C chrono) throws Exception { + ZonedDateTime ref = LocalDate.of(2000, 1, 5).atTime(12, 1, 2, 3).atZone(ZoneId.of("GMT+01:23")); + ChronoZonedDateTime orginal = chrono.date(ref).atTime(ref.getTime()).atZone(ref.getZone()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(orginal); + out.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + @SuppressWarnings("unchecked") + ChronoZonedDateTime ser = (ChronoZonedDateTime) in.readObject(); + assertEquals(ser, orginal, "deserialized date is wrong"); + } + + + /** + * FixedAdjusted returns a fixed Temporal in all adjustments. + * Construct an adjuster with the Temporal that should be returned from adjust. + */ + static class FixedAdjuster implements TemporalAdjuster, TemporalAdder, TemporalSubtractor { + private Temporal datetime; + + FixedAdjuster(Temporal datetime) { + this.datetime = datetime; + } + + @Override + public Temporal adjustInto(Temporal ignore) { + return datetime; + } + + @Override + public Temporal addTo(Temporal ignore) { + return datetime; + } + + @Override + public Temporal subtractFrom(Temporal ignore) { + return datetime; + } + + } + + /** + * FixedTemporalUnit returns a fixed Temporal in all adjustments. + * Construct an FixedTemporalUnit with the Temporal that should be returned from doPlus. + */ + static class FixedTemporalUnit implements TemporalUnit { + private Temporal temporal; + + FixedTemporalUnit(Temporal temporal) { + this.temporal = temporal; + } + + @Override + public String getName() { + return "FixedTemporalUnit"; + } + + @Override + public Duration getDuration() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isDurationEstimated() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isSupported(Temporal temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @SuppressWarnings("unchecked") + @Override + public R doPlus(R dateTime, long periodToAdd) { + return (R) this.temporal; + } + + @Override + public SimplePeriod between(R dateTime1, R dateTime2) { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + /** + * FixedTemporalField returns a fixed Temporal in all adjustments. + * Construct an FixedTemporalField with the Temporal that should be returned from doWith. + */ + static class FixedTemporalField implements TemporalField { + private Temporal temporal; + FixedTemporalField(Temporal temporal) { + this.temporal = temporal; + } + + @Override + public String getName() { + return "FixedTemporalField"; + } + + @Override + public TemporalUnit getBaseUnit() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public TemporalUnit getRangeUnit() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ValueRange range() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean doIsSupported(TemporalAccessor temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ValueRange doRange(TemporalAccessor temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public long doGet(TemporalAccessor temporal) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @SuppressWarnings("unchecked") + @Override + public R doWith(R temporal, long newValue) { + return (R) this.temporal; + } + + @Override + public boolean resolve(DateTimeBuilder builder, long value) { + throw new UnsupportedOperationException("Not supported yet."); + } + + } +} diff --git a/test/java/time/tck/java/time/temporal/TestISOChrono.java b/test/java/time/tck/java/time/temporal/TestISOChrono.java new file mode 100644 index 0000000000000000000000000000000000000000..634fab301e565ca248c657e45bf66322c322aa59 --- /dev/null +++ b/test/java/time/tck/java/time/temporal/TestISOChrono.java @@ -0,0 +1,314 @@ +/* + * 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. + * + * 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 tck.java.time.temporal; + +import static java.time.temporal.ChronoField.ERA; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; +import static java.time.temporal.ISOChrono.ERA_BCE; +import static java.time.temporal.ISOChrono.ERA_CE; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Month; +import java.time.temporal.Chrono; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.Adjusters; +import java.time.calendar.HijrahChrono; +import java.time.temporal.Era; +import java.time.temporal.ISOChrono; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test. + */ +@Test +public class TestISOChrono { + + //----------------------------------------------------------------------- + // Chrono.ofName("ISO") Lookup by name + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_chrono_byName() { + Chrono c = ISOChrono.INSTANCE; + Chrono test = Chrono.of("ISO"); + Assert.assertNotNull(test, "The ISO calendar could not be found byName"); + Assert.assertEquals(test.getId(), "ISO", "ID mismatch"); + Assert.assertEquals(test.getCalendarType(), "iso8601", "Type mismatch"); + Assert.assertEquals(test, c); + } + + //----------------------------------------------------------------------- + // Lookup by Singleton + //----------------------------------------------------------------------- + @Test(groups="tck") + public void instanceNotNull() { + assertNotNull(ISOChrono.INSTANCE); + } + + //----------------------------------------------------------------------- + // Era creation + //----------------------------------------------------------------------- + @Test(groups="tck") + public void test_eraOf() { + assertEquals(ISOChrono.INSTANCE.eraOf(0), ERA_BCE); + assertEquals(ISOChrono.INSTANCE.eraOf(1), ERA_CE); + } + + //----------------------------------------------------------------------- + // creation, toLocalDate() + //----------------------------------------------------------------------- + @DataProvider(name="samples") + Object[][] data_samples() { + return new Object[][] { + {ISOChrono.INSTANCE.date(1, 7, 8), LocalDate.of(1, 7, 8)}, + {ISOChrono.INSTANCE.date(1, 7, 20), LocalDate.of(1, 7, 20)}, + {ISOChrono.INSTANCE.date(1, 7, 21), LocalDate.of(1, 7, 21)}, + + {ISOChrono.INSTANCE.date(2, 7, 8), LocalDate.of(2, 7, 8)}, + {ISOChrono.INSTANCE.date(3, 6, 27), LocalDate.of(3, 6, 27)}, + {ISOChrono.INSTANCE.date(3, 5, 23), LocalDate.of(3, 5, 23)}, + {ISOChrono.INSTANCE.date(4, 6, 16), LocalDate.of(4, 6, 16)}, + {ISOChrono.INSTANCE.date(4, 7, 3), LocalDate.of(4, 7, 3)}, + {ISOChrono.INSTANCE.date(4, 7, 4), LocalDate.of(4, 7, 4)}, + {ISOChrono.INSTANCE.date(5, 1, 1), LocalDate.of(5, 1, 1)}, + {ISOChrono.INSTANCE.date(1727, 3, 3), LocalDate.of(1727, 3, 3)}, + {ISOChrono.INSTANCE.date(1728, 10, 28), LocalDate.of(1728, 10, 28)}, + {ISOChrono.INSTANCE.date(2012, 10, 29), LocalDate.of(2012, 10, 29)}, + }; + } + + @Test(dataProvider="samples", groups={"tck"}) + public void test_toLocalDate(ChronoLocalDate isoDate, LocalDate iso) { + assertEquals(LocalDate.from(isoDate), iso); + } + + @Test(dataProvider="samples", groups={"tck"}) + public void test_fromCalendrical(ChronoLocalDate isoDate, LocalDate iso) { + assertEquals(ISOChrono.INSTANCE.date(iso), isoDate); + } + + @DataProvider(name="badDates") + Object[][] data_badDates() { + return new Object[][] { + {2012, 0, 0}, + + {2012, -1, 1}, + {2012, 0, 1}, + {2012, 14, 1}, + {2012, 15, 1}, + + {2012, 1, -1}, + {2012, 1, 0}, + {2012, 1, 32}, + + {2012, 12, -1}, + {2012, 12, 0}, + {2012, 12, 32}, + }; + } + + @Test(dataProvider="badDates", groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_badDates(int year, int month, int dom) { + ISOChrono.INSTANCE.date(year, month, dom); + } + + @Test(groups="tck") + public void test_date_withEra() { + int year = 5; + int month = 5; + int dayOfMonth = 5; + ChronoLocalDate test = ISOChrono.INSTANCE.date(ERA_BCE, year, month, dayOfMonth); + assertEquals(test.getEra(), ERA_BCE); + assertEquals(test.get(ChronoField.YEAR_OF_ERA), year); + assertEquals(test.get(ChronoField.MONTH_OF_YEAR), month); + assertEquals(test.get(ChronoField.DAY_OF_MONTH), dayOfMonth); + + assertEquals(test.get(YEAR), 1 + (-1 * year)); + assertEquals(test.get(ERA), 0); + assertEquals(test.get(YEAR_OF_ERA), year); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test(expectedExceptions=DateTimeException.class, groups="tck") + public void test_date_withEra_withWrongEra() { + ISOChrono.INSTANCE.date((Era) HijrahChrono.ERA_AH, 1, 1, 1); + } + + //----------------------------------------------------------------------- + // with(DateTimeAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjust1() { + ChronoLocalDate base = ISOChrono.INSTANCE.date(1728, 10, 28); + ChronoLocalDate test = base.with(Adjusters.lastDayOfMonth()); + assertEquals(test, ISOChrono.INSTANCE.date(1728, 10, 31)); + } + + @Test(groups={"tck"}) + public void test_adjust2() { + ChronoLocalDate base = ISOChrono.INSTANCE.date(1728, 12, 2); + ChronoLocalDate test = base.with(Adjusters.lastDayOfMonth()); + assertEquals(test, ISOChrono.INSTANCE.date(1728, 12, 31)); + } + + //----------------------------------------------------------------------- + // ISODate.with(Local*) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjust_toLocalDate() { + ChronoLocalDate isoDate = ISOChrono.INSTANCE.date(1726, 1, 4); + ChronoLocalDate test = isoDate.with(LocalDate.of(2012, 7, 6)); + assertEquals(test, ISOChrono.INSTANCE.date(2012, 7, 6)); + } + + @Test(groups={"tck"}) + public void test_adjust_toMonth() { + ChronoLocalDate isoDate = ISOChrono.INSTANCE.date(1726, 1, 4); + assertEquals(ISOChrono.INSTANCE.date(1726, 4, 4), isoDate.with(Month.APRIL)); + } + + //----------------------------------------------------------------------- + // LocalDate.with(ISODate) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_LocalDate_adjustToISODate() { + ChronoLocalDate isoDate = ISOChrono.INSTANCE.date(1728, 10, 29); + LocalDate test = LocalDate.MIN.with(isoDate); + assertEquals(test, LocalDate.of(1728, 10, 29)); + } + + @Test(groups={"tck"}) + public void test_LocalDateTime_adjustToISODate() { + ChronoLocalDate isoDate = ISOChrono.INSTANCE.date(1728, 10, 29); + LocalDateTime test = LocalDateTime.MIN.with(isoDate); + assertEquals(test, LocalDateTime.of(1728, 10, 29, 0, 0)); + } + + //----------------------------------------------------------------------- + // isLeapYear() + //----------------------------------------------------------------------- + @DataProvider(name="leapYears") + Object[][] leapYearInformation() { + return new Object[][] { + {2000, true}, + {1996, true}, + {1600, true}, + + {1900, false}, + {2100, false}, + }; + } + + @Test(dataProvider="leapYears", groups="tck") + public void test_isLeapYear(int year, boolean isLeapYear) { + assertEquals(ISOChrono.INSTANCE.isLeapYear(year), isLeapYear); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @Test(groups="tck") + public void test_now() { + assertEquals(LocalDate.from(ISOChrono.INSTANCE.dateNow()), LocalDate.now()); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="toString") + Object[][] data_toString() { + return new Object[][] { + {ISOChrono.INSTANCE.date(1, 1, 1), "0001-01-01"}, + {ISOChrono.INSTANCE.date(1728, 10, 28), "1728-10-28"}, + {ISOChrono.INSTANCE.date(1728, 10, 29), "1728-10-29"}, + {ISOChrono.INSTANCE.date(1727, 12, 5), "1727-12-05"}, + {ISOChrono.INSTANCE.date(1727, 12, 6), "1727-12-06"}, + }; + } + + @Test(dataProvider="toString", groups={"tck"}) + public void test_toString(ChronoLocalDate isoDate, String expected) { + assertEquals(isoDate.toString(), expected); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @Test(groups="tck") + public void test_equals_true() { + assertTrue(ISOChrono.INSTANCE.equals(ISOChrono.INSTANCE)); + } + + @Test(groups="tck") + public void test_equals_false() { + assertFalse(ISOChrono.INSTANCE.equals(HijrahChrono.INSTANCE)); + } + +} diff --git a/test/java/time/tck/java/time/zone/TCKFixedZoneRules.java b/test/java/time/tck/java/time/zone/TCKFixedZoneRules.java new file mode 100644 index 0000000000000000000000000000000000000000..08617bae23b295055dfe44bed03f088207e6bc5d --- /dev/null +++ b/test/java/time/tck/java/time/zone/TCKFixedZoneRules.java @@ -0,0 +1,241 @@ +/* + * 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. + * + * 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) 2010-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 tck.java.time.zone; + +import java.time.zone.*; +import test.java.time.zone.*; + +import static org.testng.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneOffset; +import java.time.zone.ZoneOffsetTransitionRule.TimeDefinition; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test ZoneRules for fixed offset time-zones. + */ +@Test +public class TCKFixedZoneRules { + + private static final ZoneOffset OFFSET_PONE = ZoneOffset.ofHours(1); + private static final ZoneOffset OFFSET_PTWO = ZoneOffset.ofHours(2); + private static final ZoneOffset OFFSET_M18 = ZoneOffset.ofHours(-18); + private static final LocalDateTime LDT = LocalDateTime.of(2010, 12, 3, 11, 30); + private static final Instant INSTANT = LDT.toInstant(OFFSET_PONE); + + private ZoneRules make(ZoneOffset offset) { + return offset.getRules(); + } + + @DataProvider(name="rules") + Object[][] data_rules() { + return new Object[][] { + {make(OFFSET_PONE), OFFSET_PONE}, + {make(OFFSET_PTWO), OFFSET_PTWO}, + {make(OFFSET_M18), OFFSET_M18}, + }; + } + + //----------------------------------------------------------------------- + // Basics + //----------------------------------------------------------------------- + @Test(groups="tck", dataProvider="rules") + public void test_serialization(ZoneRules test, ZoneOffset expectedOffset) throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(test); + baos.close(); + byte[] bytes = baos.toByteArray(); + + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + ObjectInputStream in = new ObjectInputStream(bais); + ZoneRules result = (ZoneRules) in.readObject(); + + assertEquals(result, test); + assertEquals(result.getClass(), test.getClass()); + } + + //----------------------------------------------------------------------- + // basics + //----------------------------------------------------------------------- + @Test(groups="tck", dataProvider="rules") + public void test_getOffset_Instant(ZoneRules test, ZoneOffset expectedOffset) { + assertEquals(test.getOffset(INSTANT), expectedOffset); + assertEquals(test.getOffset((Instant) null), expectedOffset); + } + + @Test(groups="tck", dataProvider="rules") + public void test_getOffset_LocalDateTime(ZoneRules test, ZoneOffset expectedOffset) { + assertEquals(test.getOffset(LDT), expectedOffset); + assertEquals(test.getOffset((LocalDateTime) null), expectedOffset); + } + + @Test(groups="tck", dataProvider="rules") + public void test_getValidOffsets_LDT(ZoneRules test, ZoneOffset expectedOffset) { + assertEquals(test.getValidOffsets(LDT).size(), 1); + assertEquals(test.getValidOffsets(LDT).get(0), expectedOffset); + assertEquals(test.getValidOffsets(null).size(), 1); + assertEquals(test.getValidOffsets(null).get(0), expectedOffset); + } + + @Test(groups="tck", dataProvider="rules") + public void test_getTransition_LDT(ZoneRules test, ZoneOffset expectedOffset) { + assertEquals(test.getTransition(LDT), null); + assertEquals(test.getTransition(null), null); + } + + @Test(groups="tck", dataProvider="rules") + public void test_isValidOffset_LDT_ZO(ZoneRules test, ZoneOffset expectedOffset) { + assertEquals(test.isValidOffset(LDT, expectedOffset), true); + assertEquals(test.isValidOffset(LDT, ZoneOffset.UTC), false); + assertEquals(test.isValidOffset(LDT, null), false); + + assertEquals(test.isValidOffset(null, expectedOffset), true); + assertEquals(test.isValidOffset(null, ZoneOffset.UTC), false); + assertEquals(test.isValidOffset(null, null), false); + } + + @Test(groups="tck", dataProvider="rules") + public void test_getStandardOffset_Instant(ZoneRules test, ZoneOffset expectedOffset) { + assertEquals(test.getStandardOffset(INSTANT), expectedOffset); + assertEquals(test.getStandardOffset(null), expectedOffset); + } + + @Test(groups="tck", dataProvider="rules") + public void test_getDaylightSavings_Instant(ZoneRules test, ZoneOffset expectedOffset) { + assertEquals(test.getDaylightSavings(INSTANT), Duration.ZERO); + assertEquals(test.getDaylightSavings(null), Duration.ZERO); + } + + @Test(groups="tck", dataProvider="rules") + public void test_isDaylightSavings_Instant(ZoneRules test, ZoneOffset expectedOffset) { + assertEquals(test.isDaylightSavings(INSTANT), false); + assertEquals(test.isDaylightSavings(null), false); + } + + //------------------------------------------------------------------------- + @Test(groups="tck", dataProvider="rules") + public void test_nextTransition_Instant(ZoneRules test, ZoneOffset expectedOffset) { + assertEquals(test.nextTransition(INSTANT), null); + assertEquals(test.nextTransition(null), null); + } + + @Test(groups="tck", dataProvider="rules") + public void test_previousTransition_Instant(ZoneRules test, ZoneOffset expectedOffset) { + assertEquals(test.previousTransition(INSTANT), null); + assertEquals(test.previousTransition(null), null); + } + + //------------------------------------------------------------------------- + @Test(groups="tck", dataProvider="rules") + public void test_getTransitions(ZoneRules test, ZoneOffset expectedOffset) { + assertEquals(test.getTransitions().size(), 0); + } + + @Test(expectedExceptions=UnsupportedOperationException.class, groups="tck") + public void test_getTransitions_immutable() { + ZoneRules test = make(OFFSET_PTWO); + test.getTransitions().add(ZoneOffsetTransition.of(LDT, OFFSET_PONE, OFFSET_PTWO)); + } + + @Test(groups="tck", dataProvider="rules") + public void test_getTransitionRules(ZoneRules test, ZoneOffset expectedOffset) { + assertEquals(test.getTransitionRules().size(), 0); + } + + @Test(expectedExceptions=UnsupportedOperationException.class, groups="tck") + public void test_getTransitionRules_immutable() { + ZoneRules test = make(OFFSET_PTWO); + test.getTransitionRules().add(ZoneOffsetTransitionRule.of(Month.JULY, 2, null, LocalTime.of(12, 30), false, TimeDefinition.STANDARD, OFFSET_PONE, OFFSET_PTWO, OFFSET_PONE)); + } + + //----------------------------------------------------------------------- + // equals() / hashCode() + //----------------------------------------------------------------------- + @Test(groups="tck") + public void test_equalsHashCode() { + ZoneRules a = make(OFFSET_PONE); + ZoneRules b = make(OFFSET_PTWO); + + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), false); + assertEquals(b.equals(a), false); + assertEquals(b.equals(b), true); + + assertEquals(a.equals("Rubbish"), false); + assertEquals(a.equals(null), false); + + assertEquals(a.hashCode() == a.hashCode(), true); + assertEquals(b.hashCode() == b.hashCode(), true); + } + +} diff --git a/test/java/time/tck/java/time/zone/TCKZoneOffsetTransition.java b/test/java/time/tck/java/time/zone/TCKZoneOffsetTransition.java new file mode 100644 index 0000000000000000000000000000000000000000..36dc4ba0800ee2adfa746f270a0e1de35091ea3a --- /dev/null +++ b/test/java/time/tck/java/time/zone/TCKZoneOffsetTransition.java @@ -0,0 +1,297 @@ +/* + * 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. + * + * 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) 2010-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 tck.java.time.zone; + +import java.time.temporal.Year; +import java.time.zone.*; + +import static java.time.temporal.ChronoUnit.HOURS; +import static org.testng.Assert.assertEquals; + +import java.io.IOException; + +import tck.java.time.AbstractTCKTest; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +import org.testng.annotations.Test; + +/** + * Test ZoneOffsetTransition. + */ +@Test +public class TCKZoneOffsetTransition extends AbstractTCKTest { + + private static final ZoneOffset OFFSET_0100 = ZoneOffset.ofHours(1); + private static final ZoneOffset OFFSET_0200 = ZoneOffset.ofHours(2); + private static final ZoneOffset OFFSET_0230 = ZoneOffset.ofHoursMinutes(2, 30); + private static final ZoneOffset OFFSET_0300 = ZoneOffset.ofHours(3); + private static final ZoneOffset OFFSET_0400 = ZoneOffset.ofHours(4); + + //----------------------------------------------------------------------- + // factory + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_nullTransition() { + ZoneOffsetTransition.of(null, OFFSET_0100, OFFSET_0200); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_nullOffsetBefore() { + ZoneOffsetTransition.of(LocalDateTime.of(2010, 12, 3, 11, 30), null, OFFSET_0200); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_nullOffsetAfter() { + ZoneOffsetTransition.of(LocalDateTime.of(2010, 12, 3, 11, 30), OFFSET_0200, null); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_factory_sameOffset() { + ZoneOffsetTransition.of(LocalDateTime.of(2010, 12, 3, 11, 30), OFFSET_0200, OFFSET_0200); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_factory_noNanos() { + ZoneOffsetTransition.of(LocalDateTime.of(2010, 12, 3, 11, 30, 0, 500), OFFSET_0200, OFFSET_0300); + } + + //----------------------------------------------------------------------- + // getters + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_getters_gap() throws Exception { + LocalDateTime before = LocalDateTime.of(2010, 3, 31, 1, 0); + LocalDateTime after = LocalDateTime.of(2010, 3, 31, 2, 0); + ZoneOffsetTransition test = ZoneOffsetTransition.of(before, OFFSET_0200, OFFSET_0300); + assertEquals(test.isGap(), true); + assertEquals(test.isOverlap(), false); + assertEquals(test.getDateTimeBefore(), before); + assertEquals(test.getDateTimeAfter(), after); + assertEquals(test.getInstant(), before.toInstant(OFFSET_0200)); + assertEquals(test.getOffsetBefore(), OFFSET_0200); + assertEquals(test.getOffsetAfter(), OFFSET_0300); + assertEquals(test.getDuration(), Duration.of(1, HOURS)); + assertSerializable(test); + } + + @Test(groups={"tck"}) + public void test_getters_overlap() throws Exception { + LocalDateTime before = LocalDateTime.of(2010, 10, 31, 1, 0); + LocalDateTime after = LocalDateTime.of(2010, 10, 31, 0, 0); + ZoneOffsetTransition test = ZoneOffsetTransition.of(before, OFFSET_0300, OFFSET_0200); + assertEquals(test.isGap(), false); + assertEquals(test.isOverlap(), true); + assertEquals(test.getDateTimeBefore(), before); + assertEquals(test.getDateTimeAfter(), after); + assertEquals(test.getInstant(), before.toInstant(OFFSET_0300)); + assertEquals(test.getOffsetBefore(), OFFSET_0300); + assertEquals(test.getOffsetAfter(), OFFSET_0200); + assertEquals(test.getDuration(), Duration.of(-1, HOURS)); + assertSerializable(test); + } + + //----------------------------------------------------------------------- + @Test + public void test_serialization_unusual1() throws Exception { + LocalDateTime ldt = LocalDateTime.of(Year.MAX_VALUE, 12, 31, 1, 31, 53); + ZoneOffsetTransition test = ZoneOffsetTransition.of(ldt, ZoneOffset.of("+02:04:56"), ZoneOffset.of("-10:02:34")); + assertSerializable(test); + } + + @Test + public void test_serialization_unusual2() throws Exception { + LocalDateTime ldt = LocalDateTime.of(Year.MIN_VALUE, 1, 1, 12, 1, 3); + ZoneOffsetTransition test = ZoneOffsetTransition.of(ldt, ZoneOffset.of("+02:04:56"), ZoneOffset.of("+10:02:34")); + assertSerializable(test); + } + + //----------------------------------------------------------------------- + // isValidOffset() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_isValidOffset_gap() { + LocalDateTime ldt = LocalDateTime.of(2010, 3, 31, 1, 0); + ZoneOffsetTransition test = ZoneOffsetTransition.of(ldt, OFFSET_0200, OFFSET_0300); + assertEquals(test.isValidOffset(OFFSET_0100), false); + assertEquals(test.isValidOffset(OFFSET_0200), false); + assertEquals(test.isValidOffset(OFFSET_0230), false); + assertEquals(test.isValidOffset(OFFSET_0300), false); + assertEquals(test.isValidOffset(OFFSET_0400), false); + } + + @Test(groups={"tck"}) + public void test_isValidOffset_overlap() { + LocalDateTime ldt = LocalDateTime.of(2010, 10, 31, 1, 0); + ZoneOffsetTransition test = ZoneOffsetTransition.of(ldt, OFFSET_0300, OFFSET_0200); + assertEquals(test.isValidOffset(OFFSET_0100), false); + assertEquals(test.isValidOffset(OFFSET_0200), true); + assertEquals(test.isValidOffset(OFFSET_0230), false); + assertEquals(test.isValidOffset(OFFSET_0300), true); + assertEquals(test.isValidOffset(OFFSET_0400), false); + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_compareTo() { + ZoneOffsetTransition a = ZoneOffsetTransition.of( + LocalDateTime.ofEpochSecond(23875287L - 1, 0, OFFSET_0200), OFFSET_0200, OFFSET_0300); + ZoneOffsetTransition b = ZoneOffsetTransition.of( + LocalDateTime.ofEpochSecond(23875287L, 0, OFFSET_0300), OFFSET_0300, OFFSET_0200); + ZoneOffsetTransition c = ZoneOffsetTransition.of( + LocalDateTime.ofEpochSecond(23875287L + 1, 0, OFFSET_0100), OFFSET_0100,OFFSET_0400); + + assertEquals(a.compareTo(a) == 0, true); + assertEquals(a.compareTo(b) < 0, true); + assertEquals(a.compareTo(c) < 0, true); + + assertEquals(b.compareTo(a) > 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(b.compareTo(c) < 0, true); + + assertEquals(c.compareTo(a) > 0, true); + assertEquals(c.compareTo(b) > 0, true); + assertEquals(c.compareTo(c) == 0, true); + } + + @Test(groups={"tck"}) + public void test_compareTo_sameInstant() { + ZoneOffsetTransition a = ZoneOffsetTransition.of( + LocalDateTime.ofEpochSecond(23875287L, 0, OFFSET_0200), OFFSET_0200, OFFSET_0300); + ZoneOffsetTransition b = ZoneOffsetTransition.of( + LocalDateTime.ofEpochSecond(23875287L, 0, OFFSET_0300), OFFSET_0300, OFFSET_0200); + ZoneOffsetTransition c = ZoneOffsetTransition.of( + LocalDateTime.ofEpochSecond(23875287L, 0, OFFSET_0100), OFFSET_0100, OFFSET_0400); + + assertEquals(a.compareTo(a) == 0, true); + assertEquals(a.compareTo(b) == 0, true); + assertEquals(a.compareTo(c) == 0, true); + + assertEquals(b.compareTo(a) == 0, true); + assertEquals(b.compareTo(b) == 0, true); + assertEquals(b.compareTo(c) == 0, true); + + assertEquals(c.compareTo(a) == 0, true); + assertEquals(c.compareTo(b) == 0, true); + assertEquals(c.compareTo(c) == 0, true); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_equals() { + LocalDateTime ldtA = LocalDateTime.of(2010, 3, 31, 1, 0); + ZoneOffsetTransition a1 = ZoneOffsetTransition.of(ldtA, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransition a2 = ZoneOffsetTransition.of(ldtA, OFFSET_0200, OFFSET_0300); + LocalDateTime ldtB = LocalDateTime.of(2010, 10, 31, 1, 0); + ZoneOffsetTransition b = ZoneOffsetTransition.of(ldtB, OFFSET_0300, OFFSET_0200); + + assertEquals(a1.equals(a1), true); + assertEquals(a1.equals(a2), true); + assertEquals(a1.equals(b), false); + assertEquals(a2.equals(a1), true); + assertEquals(a2.equals(a2), true); + assertEquals(a2.equals(b), false); + assertEquals(b.equals(a1), false); + assertEquals(b.equals(a2), false); + assertEquals(b.equals(b), true); + + assertEquals(a1.equals(""), false); + assertEquals(a1.equals(null), false); + } + + //----------------------------------------------------------------------- + // hashCode() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_hashCode_floatingWeek_gap_notEndOfDay() { + LocalDateTime ldtA = LocalDateTime.of(2010, 3, 31, 1, 0); + ZoneOffsetTransition a1 = ZoneOffsetTransition.of(ldtA, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransition a2 = ZoneOffsetTransition.of(ldtA, OFFSET_0200, OFFSET_0300); + LocalDateTime ldtB = LocalDateTime.of(2010, 10, 31, 1, 0); + ZoneOffsetTransition b = ZoneOffsetTransition.of(ldtB, OFFSET_0300, OFFSET_0200); + + assertEquals(a1.hashCode(), a1.hashCode()); + assertEquals(a1.hashCode(), a2.hashCode()); + assertEquals(b.hashCode(), b.hashCode()); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString_gap() { + LocalDateTime ldt = LocalDateTime.of(2010, 3, 31, 1, 0); + ZoneOffsetTransition test = ZoneOffsetTransition.of(ldt, OFFSET_0200, OFFSET_0300); + assertEquals(test.toString(), "Transition[Gap at 2010-03-31T01:00+02:00 to +03:00]"); + } + + @Test(groups={"tck"}) + public void test_toString_overlap() { + LocalDateTime ldt = LocalDateTime.of(2010, 10, 31, 1, 0); + ZoneOffsetTransition test = ZoneOffsetTransition.of(ldt, OFFSET_0300, OFFSET_0200); + assertEquals(test.toString(), "Transition[Overlap at 2010-10-31T01:00+03:00 to +02:00]"); + } + +} diff --git a/test/java/time/tck/java/time/zone/TCKZoneOffsetTransitionRule.java b/test/java/time/tck/java/time/zone/TCKZoneOffsetTransitionRule.java new file mode 100644 index 0000000000000000000000000000000000000000..f98d7141b16fcfbc1a137deac6a6389b92a091bd --- /dev/null +++ b/test/java/time/tck/java/time/zone/TCKZoneOffsetTransitionRule.java @@ -0,0 +1,557 @@ +/* + * 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. + * + * 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) 2010-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 tck.java.time.zone; + +import java.time.ZoneId; +import java.time.zone.*; +import test.java.time.zone.*; + +import static org.testng.Assert.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import tck.java.time.AbstractTCKTest; +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneOffset; +import java.time.zone.ZoneOffsetTransitionRule.TimeDefinition; + +import org.testng.annotations.Test; + +/** + * Test ZoneOffsetTransitionRule. + */ +@Test +public class TCKZoneOffsetTransitionRule extends AbstractTCKTest { + + private static final LocalTime TIME_0100 = LocalTime.of(1, 0); + private static final ZoneOffset OFFSET_0200 = ZoneOffset.ofHours(2); + private static final ZoneOffset OFFSET_0300 = ZoneOffset.ofHours(3); + + //----------------------------------------------------------------------- + // factory + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_nullMonth() { + ZoneOffsetTransitionRule.of( + null, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_nullTime() { + ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, null, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_nullTimeDefinition() { + ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, null, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_nullStandardOffset() { + ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + null, OFFSET_0200, OFFSET_0300); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_nullOffsetBefore() { + ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, null, OFFSET_0300); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_nullOffsetAfter() { + ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, null); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_factory_invalidDayOfMonthIndicator_tooSmall() { + ZoneOffsetTransitionRule.of( + Month.MARCH, -29, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_factory_invalidDayOfMonthIndicator_zero() { + ZoneOffsetTransitionRule.of( + Month.MARCH, 0, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_factory_invalidDayOfMonthIndicator_tooLarge() { + ZoneOffsetTransitionRule.of( + Month.MARCH, 32, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void test_factory_invalidMidnightFlag() { + ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, true, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + } + + //----------------------------------------------------------------------- + // getters + //----------------------------------------------------------------------- + @Test + public void test_getters_floatingWeek() throws Exception { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(test.getMonth(), Month.MARCH); + assertEquals(test.getDayOfMonthIndicator(), 20); + assertEquals(test.getDayOfWeek(), DayOfWeek.SUNDAY); + assertEquals(test.getLocalTime(), TIME_0100); + assertEquals(test.isMidnightEndOfDay(), false); + assertEquals(test.getTimeDefinition(), TimeDefinition.WALL); + assertEquals(test.getStandardOffset(), OFFSET_0200); + assertEquals(test.getOffsetBefore(), OFFSET_0200); + assertEquals(test.getOffsetAfter(), OFFSET_0300); + assertSerializable(test); + } + + @Test + public void test_getters_floatingWeekBackwards() throws Exception { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, -1, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(test.getMonth(), Month.MARCH); + assertEquals(test.getDayOfMonthIndicator(), -1); + assertEquals(test.getDayOfWeek(), DayOfWeek.SUNDAY); + assertEquals(test.getLocalTime(), TIME_0100); + assertEquals(test.isMidnightEndOfDay(), false); + assertEquals(test.getTimeDefinition(), TimeDefinition.WALL); + assertEquals(test.getStandardOffset(), OFFSET_0200); + assertEquals(test.getOffsetBefore(), OFFSET_0200); + assertEquals(test.getOffsetAfter(), OFFSET_0300); + assertSerializable(test); + } + + @Test + public void test_getters_fixedDate() throws Exception { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, null, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(test.getMonth(), Month.MARCH); + assertEquals(test.getDayOfMonthIndicator(), 20); + assertEquals(test.getDayOfWeek(), null); + assertEquals(test.getLocalTime(), TIME_0100); + assertEquals(test.isMidnightEndOfDay(), false); + assertEquals(test.getTimeDefinition(), TimeDefinition.WALL); + assertEquals(test.getStandardOffset(), OFFSET_0200); + assertEquals(test.getOffsetBefore(), OFFSET_0200); + assertEquals(test.getOffsetAfter(), OFFSET_0300); + assertSerializable(test); + } + + @Test + public void test_serialization_unusualOffsets() throws Exception { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, null, TIME_0100, false, TimeDefinition.STANDARD, + ZoneOffset.ofHoursMinutesSeconds(-12, -20, -50), + ZoneOffset.ofHoursMinutesSeconds(-4, -10, -34), + ZoneOffset.ofHours(-18)); + assertSerializable(test); + } + + @Test + public void test_serialization_endOfDay() throws Exception { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.FRIDAY, LocalTime.MIDNIGHT, true, TimeDefinition.UTC, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertSerializable(test); + } + + @Test + public void test_serialization_unusualTime() throws Exception { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.WEDNESDAY, LocalTime.of(13, 34, 56), false, TimeDefinition.STANDARD, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertSerializable(test); + } + + //----------------------------------------------------------------------- + // createTransition() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_createTransition_floatingWeek_gap_notEndOfDay() { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransition trans = ZoneOffsetTransition.of( + LocalDateTime.of(2000, Month.MARCH, 26, 1, 0), OFFSET_0200, OFFSET_0300); + assertEquals(test.createTransition(2000), trans); + } + + @Test(groups={"tck"}) + public void test_createTransition_floatingWeek_overlap_endOfDay() { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, LocalTime.MIDNIGHT, true, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0300, OFFSET_0200); + ZoneOffsetTransition trans = ZoneOffsetTransition.of( + LocalDateTime.of(2000, Month.MARCH, 27, 0, 0), OFFSET_0300, OFFSET_0200); + assertEquals(test.createTransition(2000), trans); + } + + @Test(groups={"tck"}) + public void test_createTransition_floatingWeekBackwards_last() { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, -1, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransition trans = ZoneOffsetTransition.of( + LocalDateTime.of(2000, Month.MARCH, 26, 1, 0), OFFSET_0200, OFFSET_0300); + assertEquals(test.createTransition(2000), trans); + } + + @Test(groups={"tck"}) + public void test_createTransition_floatingWeekBackwards_seventhLast() { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, -7, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransition trans = ZoneOffsetTransition.of( + LocalDateTime.of(2000, Month.MARCH, 19, 1, 0), OFFSET_0200, OFFSET_0300); + assertEquals(test.createTransition(2000), trans); + } + + @Test(groups={"tck"}) + public void test_createTransition_floatingWeekBackwards_secondLast() { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, -2, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransition trans = ZoneOffsetTransition.of( + LocalDateTime.of(2000, Month.MARCH, 26, 1, 0), OFFSET_0200, OFFSET_0300); + assertEquals(test.createTransition(2000), trans); + } + + @Test(groups={"tck"}) + public void test_createTransition_fixedDate() { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, null, TIME_0100, false, TimeDefinition.STANDARD, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransition trans = ZoneOffsetTransition.of( + LocalDateTime.of(2000, Month.MARCH, 20, 1, 0), OFFSET_0200, OFFSET_0300); + assertEquals(test.createTransition(2000), trans); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_equals_monthDifferent() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransitionRule b = ZoneOffsetTransitionRule.of( + Month.APRIL, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), false); + assertEquals(b.equals(a), false); + assertEquals(b.equals(b), true); + } + + @Test(groups={"tck"}) + public void test_equals_dayOfMonthDifferent() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransitionRule b = ZoneOffsetTransitionRule.of( + Month.MARCH, 21, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), false); + assertEquals(b.equals(a), false); + assertEquals(b.equals(b), true); + } + + @Test(groups={"tck"}) + public void test_equals_dayOfWeekDifferent() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransitionRule b = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SATURDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), false); + assertEquals(b.equals(a), false); + assertEquals(b.equals(b), true); + } + + @Test(groups={"tck"}) + public void test_equals_dayOfWeekDifferentNull() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransitionRule b = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, null, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), false); + assertEquals(b.equals(a), false); + assertEquals(b.equals(b), true); + } + + @Test(groups={"tck"}) + public void test_equals_localTimeDifferent() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransitionRule b = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, LocalTime.MIDNIGHT, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), false); + assertEquals(b.equals(a), false); + assertEquals(b.equals(b), true); + } + + @Test(groups={"tck"}) + public void test_equals_endOfDayDifferent() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, LocalTime.MIDNIGHT, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransitionRule b = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, LocalTime.MIDNIGHT, true, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), false); + assertEquals(b.equals(a), false); + assertEquals(b.equals(b), true); + } + + @Test(groups={"tck"}) + public void test_equals_timeDefinitionDifferent() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransitionRule b = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.STANDARD, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), false); + assertEquals(b.equals(a), false); + assertEquals(b.equals(b), true); + } + + @Test(groups={"tck"}) + public void test_equals_standardOffsetDifferent() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransitionRule b = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0300, OFFSET_0200, OFFSET_0300); + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), false); + assertEquals(b.equals(a), false); + assertEquals(b.equals(b), true); + } + + @Test(groups={"tck"}) + public void test_equals_offsetBeforeDifferent() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransitionRule b = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0300, OFFSET_0300); + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), false); + assertEquals(b.equals(a), false); + assertEquals(b.equals(b), true); + } + + @Test(groups={"tck"}) + public void test_equals_offsetAfterDifferent() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransitionRule b = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0200); + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), false); + assertEquals(b.equals(a), false); + assertEquals(b.equals(b), true); + } + + @Test(groups={"tck"}) + public void test_equals_string_false() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(a.equals("TZDB"), false); + } + + @Test(groups={"tck"}) + public void test_equals_null_false() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(a.equals(null), false); + } + + //----------------------------------------------------------------------- + // hashCode() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_hashCode_floatingWeek_gap_notEndOfDay() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransitionRule b = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(a.hashCode(), b.hashCode()); + } + + @Test(groups={"tck"}) + public void test_hashCode_floatingWeek_overlap_endOfDay_nullDayOfWeek() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.OCTOBER, 20, null, LocalTime.MIDNIGHT, true, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0300, OFFSET_0200); + ZoneOffsetTransitionRule b = ZoneOffsetTransitionRule.of( + Month.OCTOBER, 20, null, LocalTime.MIDNIGHT, true, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0300, OFFSET_0200); + assertEquals(a.hashCode(), b.hashCode()); + } + + @Test(groups={"tck"}) + public void test_hashCode_floatingWeekBackwards() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.MARCH, -1, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransitionRule b = ZoneOffsetTransitionRule.of( + Month.MARCH, -1, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(a.hashCode(), b.hashCode()); + } + + @Test(groups={"tck"}) + public void test_hashCode_fixedDate() { + ZoneOffsetTransitionRule a = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, null, TIME_0100, false, TimeDefinition.STANDARD, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + ZoneOffsetTransitionRule b = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, null, TIME_0100, false, TimeDefinition.STANDARD, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(a.hashCode(), b.hashCode()); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString_floatingWeek_gap_notEndOfDay() { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(test.toString(), "TransitionRule[Gap +02:00 to +03:00, SUNDAY on or after MARCH 20 at 01:00 WALL, standard offset +02:00]"); + } + + @Test(groups={"tck"}) + public void test_toString_floatingWeek_overlap_endOfDay() { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.OCTOBER, 20, DayOfWeek.SUNDAY, LocalTime.MIDNIGHT, true, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0300, OFFSET_0200); + assertEquals(test.toString(), "TransitionRule[Overlap +03:00 to +02:00, SUNDAY on or after OCTOBER 20 at 24:00 WALL, standard offset +02:00]"); + } + + @Test(groups={"tck"}) + public void test_toString_floatingWeekBackwards_last() { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, -1, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(test.toString(), "TransitionRule[Gap +02:00 to +03:00, SUNDAY on or before last day of MARCH at 01:00 WALL, standard offset +02:00]"); + } + + @Test(groups={"tck"}) + public void test_toString_floatingWeekBackwards_secondLast() { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, -2, DayOfWeek.SUNDAY, TIME_0100, false, TimeDefinition.WALL, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(test.toString(), "TransitionRule[Gap +02:00 to +03:00, SUNDAY on or before last day minus 1 of MARCH at 01:00 WALL, standard offset +02:00]"); + } + + @Test(groups={"tck"}) + public void test_toString_fixedDate() { + ZoneOffsetTransitionRule test = ZoneOffsetTransitionRule.of( + Month.MARCH, 20, null, TIME_0100, false, TimeDefinition.STANDARD, + OFFSET_0200, OFFSET_0200, OFFSET_0300); + assertEquals(test.toString(), "TransitionRule[Gap +02:00 to +03:00, MARCH 20 at 01:00 STANDARD, standard offset +02:00]"); + } + +} diff --git a/test/java/time/tck/java/time/zone/TCKZoneRules.java b/test/java/time/tck/java/time/zone/TCKZoneRules.java new file mode 100644 index 0000000000000000000000000000000000000000..294e2e07cb2654fd4e929f783136c89c53cf953a --- /dev/null +++ b/test/java/time/tck/java/time/zone/TCKZoneRules.java @@ -0,0 +1,1005 @@ +/* + * 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. + * + * 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 tck.java.time.zone; + +import java.time.temporal.Year; +import java.time.zone.*; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Iterator; +import java.util.List; + +import java.time.DayOfWeek; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.zone.ZoneOffsetTransitionRule.TimeDefinition; + +import org.testng.annotations.Test; + +/** + * Test ZoneRules. + */ +@Test +public class TCKZoneRules { + + private static final ZoneOffset OFFSET_ZERO = ZoneOffset.ofHours(0); + private static final ZoneOffset OFFSET_PONE = ZoneOffset.ofHours(1); + private static final ZoneOffset OFFSET_PTWO = ZoneOffset.ofHours(2); + public static final String LATEST_TZDB = "2009b"; + private static final int OVERLAP = 2; + private static final int GAP = 0; + + //----------------------------------------------------------------------- + // Basics + //----------------------------------------------------------------------- + public void test_serialization_loaded() throws Exception { + assertSerialization(europeLondon()); + assertSerialization(europeParis()); + assertSerialization(americaNewYork()); + } + + private void assertSerialization(ZoneRules test) throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(test); + baos.close(); + byte[] bytes = baos.toByteArray(); + + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + ObjectInputStream in = new ObjectInputStream(bais); + ZoneRules result = (ZoneRules) in.readObject(); + + assertEquals(result, test); + } + + //----------------------------------------------------------------------- + // Europe/London + //----------------------------------------------------------------------- + private ZoneRules europeLondon() { + return ZoneId.of("Europe/London").getRules(); + } + + public void test_London() { + ZoneRules test = europeLondon(); + assertEquals(test.isFixedOffset(), false); + } + + public void test_London_preTimeZones() { + ZoneRules test = europeLondon(); + ZonedDateTime old = createZDT(1800, 1, 1, ZoneOffset.UTC); + Instant instant = old.toInstant(); + ZoneOffset offset = ZoneOffset.ofHoursMinutesSeconds(0, -1, -15); + assertEquals(test.getOffset(instant), offset); + checkOffset(test, old.getDateTime(), offset, 1); + assertEquals(test.getStandardOffset(instant), offset); + assertEquals(test.getDaylightSavings(instant), Duration.ZERO); + assertEquals(test.isDaylightSavings(instant), false); + } + + public void test_London_getOffset() { + ZoneRules test = europeLondon(); + assertEquals(test.getOffset(createInstant(2008, 1, 1, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 2, 1, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 3, 1, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 4, 1, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 5, 1, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 6, 1, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 7, 1, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 8, 1, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 9, 1, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 10, 1, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 11, 1, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 12, 1, ZoneOffset.UTC)), OFFSET_ZERO); + } + + public void test_London_getOffset_toDST() { + ZoneRules test = europeLondon(); + assertEquals(test.getOffset(createInstant(2008, 3, 24, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 3, 25, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 3, 26, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 3, 27, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 3, 28, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 3, 29, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 3, 30, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 3, 31, ZoneOffset.UTC)), OFFSET_PONE); + // cutover at 01:00Z + assertEquals(test.getOffset(createInstant(2008, 3, 30, 0, 59, 59, 999999999, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 3, 30, 1, 0, 0, 0, ZoneOffset.UTC)), OFFSET_PONE); + } + + public void test_London_getOffset_fromDST() { + ZoneRules test = europeLondon(); + assertEquals(test.getOffset(createInstant(2008, 10, 24, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 10, 25, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 10, 26, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 10, 27, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 10, 28, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 10, 29, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 10, 30, ZoneOffset.UTC)), OFFSET_ZERO); + assertEquals(test.getOffset(createInstant(2008, 10, 31, ZoneOffset.UTC)), OFFSET_ZERO); + // cutover at 01:00Z + assertEquals(test.getOffset(createInstant(2008, 10, 26, 0, 59, 59, 999999999, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 10, 26, 1, 0, 0, 0, ZoneOffset.UTC)), OFFSET_ZERO); + } + + public void test_London_getOffsetInfo() { + ZoneRules test = europeLondon(); + checkOffset(test, createLDT(2008, 1, 1), OFFSET_ZERO, 1); + checkOffset(test, createLDT(2008, 2, 1), OFFSET_ZERO, 1); + checkOffset(test, createLDT(2008, 3, 1), OFFSET_ZERO, 1); + checkOffset(test, createLDT(2008, 4, 1), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 5, 1), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 6, 1), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 7, 1), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 8, 1), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 9, 1), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 10, 1), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 11, 1), OFFSET_ZERO, 1); + checkOffset(test, createLDT(2008, 12, 1), OFFSET_ZERO, 1); + } + + public void test_London_getOffsetInfo_toDST() { + ZoneRules test = europeLondon(); + checkOffset(test, createLDT(2008, 3, 24), OFFSET_ZERO, 1); + checkOffset(test, createLDT(2008, 3, 25), OFFSET_ZERO, 1); + checkOffset(test, createLDT(2008, 3, 26), OFFSET_ZERO, 1); + checkOffset(test, createLDT(2008, 3, 27), OFFSET_ZERO, 1); + checkOffset(test, createLDT(2008, 3, 28), OFFSET_ZERO, 1); + checkOffset(test, createLDT(2008, 3, 29), OFFSET_ZERO, 1); + checkOffset(test, createLDT(2008, 3, 30), OFFSET_ZERO, 1); + checkOffset(test, createLDT(2008, 3, 31), OFFSET_PONE, 1); + // cutover at 01:00Z + checkOffset(test, LocalDateTime.of(2008, 3, 30, 0, 59, 59, 999999999), OFFSET_ZERO, 1); + checkOffset(test, LocalDateTime.of(2008, 3, 30, 2, 0, 0, 0), OFFSET_PONE, 1); + } + + public void test_London_getOffsetInfo_fromDST() { + ZoneRules test = europeLondon(); + checkOffset(test, createLDT(2008, 10, 24), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 10, 25), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 10, 26), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 10, 27), OFFSET_ZERO, 1); + checkOffset(test, createLDT(2008, 10, 28), OFFSET_ZERO, 1); + checkOffset(test, createLDT(2008, 10, 29), OFFSET_ZERO, 1); + checkOffset(test, createLDT(2008, 10, 30), OFFSET_ZERO, 1); + checkOffset(test, createLDT(2008, 10, 31), OFFSET_ZERO, 1); + // cutover at 01:00Z + checkOffset(test, LocalDateTime.of(2008, 10, 26, 0, 59, 59, 999999999), OFFSET_PONE, 1); + checkOffset(test, LocalDateTime.of(2008, 10, 26, 2, 0, 0, 0), OFFSET_ZERO, 1); + } + + public void test_London_getOffsetInfo_gap() { + ZoneRules test = europeLondon(); + final LocalDateTime dateTime = LocalDateTime.of(2008, 3, 30, 1, 0, 0, 0); + ZoneOffsetTransition trans = checkOffset(test, dateTime, OFFSET_ZERO, GAP); + assertEquals(trans.isGap(), true); + assertEquals(trans.isOverlap(), false); + assertEquals(trans.getOffsetBefore(), OFFSET_ZERO); + assertEquals(trans.getOffsetAfter(), OFFSET_PONE); + assertEquals(trans.getInstant(), createInstant(2008, 3, 30, 1, 0, ZoneOffset.UTC)); + assertEquals(trans.getDateTimeBefore(), LocalDateTime.of(2008, 3, 30, 1, 0)); + assertEquals(trans.getDateTimeAfter(), LocalDateTime.of(2008, 3, 30, 2, 0)); + assertEquals(trans.isValidOffset(OFFSET_ZERO), false); + assertEquals(trans.isValidOffset(OFFSET_PONE), false); + assertEquals(trans.isValidOffset(OFFSET_PTWO), false); + assertEquals(trans.toString(), "Transition[Gap at 2008-03-30T01:00Z to +01:00]"); + + assertFalse(trans.equals(null)); + assertFalse(trans.equals(OFFSET_ZERO)); + assertTrue(trans.equals(trans)); + + final ZoneOffsetTransition otherTrans = test.getTransition(dateTime); + assertTrue(trans.equals(otherTrans)); + assertEquals(trans.hashCode(), otherTrans.hashCode()); + } + + public void test_London_getOffsetInfo_overlap() { + ZoneRules test = europeLondon(); + final LocalDateTime dateTime = LocalDateTime.of(2008, 10, 26, 1, 0, 0, 0); + ZoneOffsetTransition trans = checkOffset(test, dateTime, OFFSET_PONE, OVERLAP); + assertEquals(trans.isGap(), false); + assertEquals(trans.isOverlap(), true); + assertEquals(trans.getOffsetBefore(), OFFSET_PONE); + assertEquals(trans.getOffsetAfter(), OFFSET_ZERO); + assertEquals(trans.getInstant(), createInstant(2008, 10, 26, 1, 0, ZoneOffset.UTC)); + assertEquals(trans.getDateTimeBefore(), LocalDateTime.of(2008, 10, 26, 2, 0)); + assertEquals(trans.getDateTimeAfter(), LocalDateTime.of(2008, 10, 26, 1, 0)); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(-1)), false); + assertEquals(trans.isValidOffset(OFFSET_ZERO), true); + assertEquals(trans.isValidOffset(OFFSET_PONE), true); + assertEquals(trans.isValidOffset(OFFSET_PTWO), false); + assertEquals(trans.toString(), "Transition[Overlap at 2008-10-26T02:00+01:00 to Z]"); + + assertFalse(trans.equals(null)); + assertFalse(trans.equals(OFFSET_PONE)); + assertTrue(trans.equals(trans)); + + final ZoneOffsetTransition otherTrans = test.getTransition(dateTime); + assertTrue(trans.equals(otherTrans)); + assertEquals(trans.hashCode(), otherTrans.hashCode()); + } + + public void test_London_getStandardOffset() { + ZoneRules test = europeLondon(); + ZonedDateTime zdt = createZDT(1840, 1, 1, ZoneOffset.UTC); + while (zdt.getYear() < 2010) { + Instant instant = zdt.toInstant(); + if (zdt.getYear() < 1848) { + assertEquals(test.getStandardOffset(instant), ZoneOffset.ofHoursMinutesSeconds(0, -1, -15)); + } else if (zdt.getYear() >= 1969 && zdt.getYear() < 1972) { + assertEquals(test.getStandardOffset(instant), OFFSET_PONE); + } else { + assertEquals(test.getStandardOffset(instant), OFFSET_ZERO); + } + zdt = zdt.plusMonths(6); + } + } + + public void test_London_getTransitions() { + ZoneRules test = europeLondon(); + List trans = test.getTransitions(); + + ZoneOffsetTransition first = trans.get(0); + assertEquals(first.getDateTimeBefore(), LocalDateTime.of(1847, 12, 1, 0, 0)); + assertEquals(first.getOffsetBefore(), ZoneOffset.ofHoursMinutesSeconds(0, -1, -15)); + assertEquals(first.getOffsetAfter(), OFFSET_ZERO); + + ZoneOffsetTransition spring1916 = trans.get(1); + assertEquals(spring1916.getDateTimeBefore(), LocalDateTime.of(1916, 5, 21, 2, 0)); + assertEquals(spring1916.getOffsetBefore(), OFFSET_ZERO); + assertEquals(spring1916.getOffsetAfter(), OFFSET_PONE); + + ZoneOffsetTransition autumn1916 = trans.get(2); + assertEquals(autumn1916.getDateTimeBefore(), LocalDateTime.of(1916, 10, 1, 3, 0)); + assertEquals(autumn1916.getOffsetBefore(), OFFSET_PONE); + assertEquals(autumn1916.getOffsetAfter(), OFFSET_ZERO); + + ZoneOffsetTransition zot = null; + Iterator it = trans.iterator(); + while (it.hasNext()) { + zot = it.next(); + if (zot.getDateTimeBefore().getYear() == 1990) { + break; + } + } + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1990, 3, 25, 1, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_ZERO); + zot = it.next(); + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1990, 10, 28, 2, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_PONE); + zot = it.next(); + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1991, 3, 31, 1, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_ZERO); + zot = it.next(); + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1991, 10, 27, 2, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_PONE); + zot = it.next(); + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1992, 3, 29, 1, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_ZERO); + zot = it.next(); + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1992, 10, 25, 2, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_PONE); + zot = it.next(); + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1993, 3, 28, 1, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_ZERO); + zot = it.next(); + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1993, 10, 24, 2, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_PONE); + zot = it.next(); + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1994, 3, 27, 1, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_ZERO); + zot = it.next(); + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1994, 10, 23, 2, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_PONE); + zot = it.next(); + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1995, 3, 26, 1, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_ZERO); + zot = it.next(); + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1995, 10, 22, 2, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_PONE); + zot = it.next(); + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1996, 3, 31, 1, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_ZERO); + zot = it.next(); + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1996, 10, 27, 2, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_PONE); + zot = it.next(); + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1997, 3, 30, 1, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_ZERO); + zot = it.next(); + assertEquals(zot.getDateTimeBefore(), LocalDateTime.of(1997, 10, 26, 2, 0)); + assertEquals(zot.getOffsetBefore(), OFFSET_PONE); + assertEquals(it.hasNext(), false); + } + + public void test_London_getTransitionRules() { + ZoneRules test = europeLondon(); + List rules = test.getTransitionRules(); + assertEquals(rules.size(), 2); + + ZoneOffsetTransitionRule in = rules.get(0); + assertEquals(in.getMonth(), Month.MARCH); + assertEquals(in.getDayOfMonthIndicator(), 25); // optimized from -1 + assertEquals(in.getDayOfWeek(), DayOfWeek.SUNDAY); + assertEquals(in.getLocalTime(), LocalTime.of(1, 0)); + assertEquals(in.getTimeDefinition(), TimeDefinition.UTC); + assertEquals(in.getStandardOffset(), OFFSET_ZERO); + assertEquals(in.getOffsetBefore(), OFFSET_ZERO); + assertEquals(in.getOffsetAfter(), OFFSET_PONE); + + ZoneOffsetTransitionRule out = rules.get(1); + assertEquals(out.getMonth(), Month.OCTOBER); + assertEquals(out.getDayOfMonthIndicator(), 25); // optimized from -1 + assertEquals(out.getDayOfWeek(), DayOfWeek.SUNDAY); + assertEquals(out.getLocalTime(), LocalTime.of(1, 0)); + assertEquals(out.getTimeDefinition(), TimeDefinition.UTC); + assertEquals(out.getStandardOffset(), OFFSET_ZERO); + assertEquals(out.getOffsetBefore(), OFFSET_PONE); + assertEquals(out.getOffsetAfter(), OFFSET_ZERO); + } + + //----------------------------------------------------------------------- + public void test_London_nextTransition_historic() { + ZoneRules test = europeLondon(); + List trans = test.getTransitions(); + + ZoneOffsetTransition first = trans.get(0); + assertEquals(test.nextTransition(first.getInstant().minusNanos(1)), first); + + for (int i = 0; i < trans.size() - 1; i++) { + ZoneOffsetTransition cur = trans.get(i); + ZoneOffsetTransition next = trans.get(i + 1); + + assertEquals(test.nextTransition(cur.getInstant()), next); + assertEquals(test.nextTransition(next.getInstant().minusNanos(1)), next); + } + } + + public void test_London_nextTransition_rulesBased() { + ZoneRules test = europeLondon(); + List rules = test.getTransitionRules(); + List trans = test.getTransitions(); + + ZoneOffsetTransition last = trans.get(trans.size() - 1); + assertEquals(test.nextTransition(last.getInstant()), rules.get(0).createTransition(1998)); + + for (int year = 1998; year < 2010; year++) { + ZoneOffsetTransition a = rules.get(0).createTransition(year); + ZoneOffsetTransition b = rules.get(1).createTransition(year); + ZoneOffsetTransition c = rules.get(0).createTransition(year + 1); + + assertEquals(test.nextTransition(a.getInstant()), b); + assertEquals(test.nextTransition(b.getInstant().minusNanos(1)), b); + + assertEquals(test.nextTransition(b.getInstant()), c); + assertEquals(test.nextTransition(c.getInstant().minusNanos(1)), c); + } + } + + public void test_London_nextTransition_lastYear() { + ZoneRules test = europeLondon(); + List rules = test.getTransitionRules(); + ZoneOffsetTransition zot = rules.get(1).createTransition(Year.MAX_VALUE); + assertEquals(test.nextTransition(zot.getInstant()), null); + } + + //----------------------------------------------------------------------- + public void test_London_previousTransition_historic() { + ZoneRules test = europeLondon(); + List trans = test.getTransitions(); + + ZoneOffsetTransition first = trans.get(0); + assertEquals(test.previousTransition(first.getInstant()), null); + assertEquals(test.previousTransition(first.getInstant().minusNanos(1)), null); + + for (int i = 0; i < trans.size() - 1; i++) { + ZoneOffsetTransition prev = trans.get(i); + ZoneOffsetTransition cur = trans.get(i + 1); + + assertEquals(test.previousTransition(cur.getInstant()), prev); + assertEquals(test.previousTransition(prev.getInstant().plusSeconds(1)), prev); + assertEquals(test.previousTransition(prev.getInstant().plusNanos(1)), prev); + } + } + + public void test_London_previousTransition_rulesBased() { + ZoneRules test = europeLondon(); + List rules = test.getTransitionRules(); + List trans = test.getTransitions(); + + ZoneOffsetTransition last = trans.get(trans.size() - 1); + assertEquals(test.previousTransition(last.getInstant().plusSeconds(1)), last); + assertEquals(test.previousTransition(last.getInstant().plusNanos(1)), last); + + // Jan 1st of year between transitions and rules + ZonedDateTime odt = ZonedDateTime.ofInstant(last.getInstant(), last.getOffsetAfter()); + odt = odt.withDayOfYear(1).plusYears(1).with(LocalTime.MIDNIGHT); + assertEquals(test.previousTransition(odt.toInstant()), last); + + // later years + for (int year = 1998; year < 2010; year++) { + ZoneOffsetTransition a = rules.get(0).createTransition(year); + ZoneOffsetTransition b = rules.get(1).createTransition(year); + ZoneOffsetTransition c = rules.get(0).createTransition(year + 1); + + assertEquals(test.previousTransition(c.getInstant()), b); + assertEquals(test.previousTransition(b.getInstant().plusSeconds(1)), b); + assertEquals(test.previousTransition(b.getInstant().plusNanos(1)), b); + + assertEquals(test.previousTransition(b.getInstant()), a); + assertEquals(test.previousTransition(a.getInstant().plusSeconds(1)), a); + assertEquals(test.previousTransition(a.getInstant().plusNanos(1)), a); + } + } + + //----------------------------------------------------------------------- + // Europe/Paris + //----------------------------------------------------------------------- + private ZoneRules europeParis() { + return ZoneId.of("Europe/Paris").getRules(); + } + + public void test_Paris() { + ZoneRules test = europeParis(); + assertEquals(test.isFixedOffset(), false); + } + + public void test_Paris_preTimeZones() { + ZoneRules test = europeParis(); + ZonedDateTime old = createZDT(1800, 1, 1, ZoneOffset.UTC); + Instant instant = old.toInstant(); + ZoneOffset offset = ZoneOffset.ofHoursMinutesSeconds(0, 9, 21); + assertEquals(test.getOffset(instant), offset); + checkOffset(test, old.getDateTime(), offset, 1); + assertEquals(test.getStandardOffset(instant), offset); + assertEquals(test.getDaylightSavings(instant), Duration.ZERO); + assertEquals(test.isDaylightSavings(instant), false); + } + + public void test_Paris_getOffset() { + ZoneRules test = europeParis(); + assertEquals(test.getOffset(createInstant(2008, 1, 1, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 2, 1, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 3, 1, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 4, 1, ZoneOffset.UTC)), OFFSET_PTWO); + assertEquals(test.getOffset(createInstant(2008, 5, 1, ZoneOffset.UTC)), OFFSET_PTWO); + assertEquals(test.getOffset(createInstant(2008, 6, 1, ZoneOffset.UTC)), OFFSET_PTWO); + assertEquals(test.getOffset(createInstant(2008, 7, 1, ZoneOffset.UTC)), OFFSET_PTWO); + assertEquals(test.getOffset(createInstant(2008, 8, 1, ZoneOffset.UTC)), OFFSET_PTWO); + assertEquals(test.getOffset(createInstant(2008, 9, 1, ZoneOffset.UTC)), OFFSET_PTWO); + assertEquals(test.getOffset(createInstant(2008, 10, 1, ZoneOffset.UTC)), OFFSET_PTWO); + assertEquals(test.getOffset(createInstant(2008, 11, 1, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 12, 1, ZoneOffset.UTC)), OFFSET_PONE); + } + + public void test_Paris_getOffset_toDST() { + ZoneRules test = europeParis(); + assertEquals(test.getOffset(createInstant(2008, 3, 24, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 3, 25, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 3, 26, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 3, 27, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 3, 28, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 3, 29, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 3, 30, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 3, 31, ZoneOffset.UTC)), OFFSET_PTWO); + // cutover at 01:00Z + assertEquals(test.getOffset(createInstant(2008, 3, 30, 0, 59, 59, 999999999, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 3, 30, 1, 0, 0, 0, ZoneOffset.UTC)), OFFSET_PTWO); + } + + public void test_Paris_getOffset_fromDST() { + ZoneRules test = europeParis(); + assertEquals(test.getOffset(createInstant(2008, 10, 24, ZoneOffset.UTC)), OFFSET_PTWO); + assertEquals(test.getOffset(createInstant(2008, 10, 25, ZoneOffset.UTC)), OFFSET_PTWO); + assertEquals(test.getOffset(createInstant(2008, 10, 26, ZoneOffset.UTC)), OFFSET_PTWO); + assertEquals(test.getOffset(createInstant(2008, 10, 27, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 10, 28, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 10, 29, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 10, 30, ZoneOffset.UTC)), OFFSET_PONE); + assertEquals(test.getOffset(createInstant(2008, 10, 31, ZoneOffset.UTC)), OFFSET_PONE); + // cutover at 01:00Z + assertEquals(test.getOffset(createInstant(2008, 10, 26, 0, 59, 59, 999999999, ZoneOffset.UTC)), OFFSET_PTWO); + assertEquals(test.getOffset(createInstant(2008, 10, 26, 1, 0, 0, 0, ZoneOffset.UTC)), OFFSET_PONE); + } + + public void test_Paris_getOffsetInfo() { + ZoneRules test = europeParis(); + checkOffset(test, createLDT(2008, 1, 1), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 2, 1), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 3, 1), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 4, 1), OFFSET_PTWO, 1); + checkOffset(test, createLDT(2008, 5, 1), OFFSET_PTWO, 1); + checkOffset(test, createLDT(2008, 6, 1), OFFSET_PTWO, 1); + checkOffset(test, createLDT(2008, 7, 1), OFFSET_PTWO, 1); + checkOffset(test, createLDT(2008, 8, 1), OFFSET_PTWO, 1); + checkOffset(test, createLDT(2008, 9, 1), OFFSET_PTWO, 1); + checkOffset(test, createLDT(2008, 10, 1), OFFSET_PTWO, 1); + checkOffset(test, createLDT(2008, 11, 1), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 12, 1), OFFSET_PONE, 1); + } + + public void test_Paris_getOffsetInfo_toDST() { + ZoneRules test = europeParis(); + checkOffset(test, createLDT(2008, 3, 24), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 3, 25), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 3, 26), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 3, 27), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 3, 28), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 3, 29), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 3, 30), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 3, 31), OFFSET_PTWO, 1); + // cutover at 01:00Z which is 02:00+01:00(local Paris time) + checkOffset(test, LocalDateTime.of(2008, 3, 30, 1, 59, 59, 999999999), OFFSET_PONE, 1); + checkOffset(test, LocalDateTime.of(2008, 3, 30, 3, 0, 0, 0), OFFSET_PTWO, 1); + } + + public void test_Paris_getOffsetInfo_fromDST() { + ZoneRules test = europeParis(); + checkOffset(test, createLDT(2008, 10, 24), OFFSET_PTWO, 1); + checkOffset(test, createLDT(2008, 10, 25), OFFSET_PTWO, 1); + checkOffset(test, createLDT(2008, 10, 26), OFFSET_PTWO, 1); + checkOffset(test, createLDT(2008, 10, 27), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 10, 28), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 10, 29), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 10, 30), OFFSET_PONE, 1); + checkOffset(test, createLDT(2008, 10, 31), OFFSET_PONE, 1); + // cutover at 01:00Z which is 02:00+01:00(local Paris time) + checkOffset(test, LocalDateTime.of(2008, 10, 26, 1, 59, 59, 999999999), OFFSET_PTWO, 1); + checkOffset(test, LocalDateTime.of(2008, 10, 26, 3, 0, 0, 0), OFFSET_PONE, 1); + } + + public void test_Paris_getOffsetInfo_gap() { + ZoneRules test = europeParis(); + final LocalDateTime dateTime = LocalDateTime.of(2008, 3, 30, 2, 0, 0, 0); + ZoneOffsetTransition trans = checkOffset(test, dateTime, OFFSET_PONE, GAP); + assertEquals(trans.isGap(), true); + assertEquals(trans.isOverlap(), false); + assertEquals(trans.getOffsetBefore(), OFFSET_PONE); + assertEquals(trans.getOffsetAfter(), OFFSET_PTWO); + assertEquals(trans.getInstant(), createInstant(2008, 3, 30, 1, 0, ZoneOffset.UTC)); + assertEquals(trans.isValidOffset(OFFSET_ZERO), false); + assertEquals(trans.isValidOffset(OFFSET_PONE), false); + assertEquals(trans.isValidOffset(OFFSET_PTWO), false); + assertEquals(trans.toString(), "Transition[Gap at 2008-03-30T02:00+01:00 to +02:00]"); + + assertFalse(trans.equals(null)); + assertFalse(trans.equals(OFFSET_PONE)); + assertTrue(trans.equals(trans)); + + final ZoneOffsetTransition otherTrans = test.getTransition(dateTime); + assertTrue(trans.equals(otherTrans)); + assertEquals(trans.hashCode(), otherTrans.hashCode()); + } + + public void test_Paris_getOffsetInfo_overlap() { + ZoneRules test = europeParis(); + final LocalDateTime dateTime = LocalDateTime.of(2008, 10, 26, 2, 0, 0, 0); + ZoneOffsetTransition trans = checkOffset(test, dateTime, OFFSET_PTWO, OVERLAP); + assertEquals(trans.isGap(), false); + assertEquals(trans.isOverlap(), true); + assertEquals(trans.getOffsetBefore(), OFFSET_PTWO); + assertEquals(trans.getOffsetAfter(), OFFSET_PONE); + assertEquals(trans.getInstant(), createInstant(2008, 10, 26, 1, 0, ZoneOffset.UTC)); + assertEquals(trans.isValidOffset(OFFSET_ZERO), false); + assertEquals(trans.isValidOffset(OFFSET_PONE), true); + assertEquals(trans.isValidOffset(OFFSET_PTWO), true); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(3)), false); + assertEquals(trans.toString(), "Transition[Overlap at 2008-10-26T03:00+02:00 to +01:00]"); + + assertFalse(trans.equals(null)); + assertFalse(trans.equals(OFFSET_PTWO)); + assertTrue(trans.equals(trans)); + + final ZoneOffsetTransition otherTrans = test.getTransition(dateTime); + assertTrue(trans.equals(otherTrans)); + assertEquals(trans.hashCode(), otherTrans.hashCode()); + } + + public void test_Paris_getStandardOffset() { + ZoneRules test = europeParis(); + ZonedDateTime zdt = createZDT(1840, 1, 1, ZoneOffset.UTC); + while (zdt.getYear() < 2010) { + Instant instant = zdt.toInstant(); + if (zdt.getDate().isBefore(LocalDate.of(1911, 3, 11))) { + assertEquals(test.getStandardOffset(instant), ZoneOffset.ofHoursMinutesSeconds(0, 9, 21)); + } else if (zdt.getDate().isBefore(LocalDate.of(1940, 6, 14))) { + assertEquals(test.getStandardOffset(instant), OFFSET_ZERO); + } else if (zdt.getDate().isBefore(LocalDate.of(1944, 8, 25))) { + assertEquals(test.getStandardOffset(instant), OFFSET_PONE); + } else if (zdt.getDate().isBefore(LocalDate.of(1945, 9, 16))) { + assertEquals(test.getStandardOffset(instant), OFFSET_ZERO); + } else { + assertEquals(test.getStandardOffset(instant), OFFSET_PONE); + } + zdt = zdt.plusMonths(6); + } + } + + //----------------------------------------------------------------------- + // America/New_York + //----------------------------------------------------------------------- + private ZoneRules americaNewYork() { + return ZoneId.of("America/New_York").getRules(); + } + + public void test_NewYork() { + ZoneRules test = americaNewYork(); + assertEquals(test.isFixedOffset(), false); + } + + public void test_NewYork_preTimeZones() { + ZoneRules test = americaNewYork(); + ZonedDateTime old = createZDT(1800, 1, 1, ZoneOffset.UTC); + Instant instant = old.toInstant(); + ZoneOffset offset = ZoneOffset.of("-04:56:02"); + assertEquals(test.getOffset(instant), offset); + checkOffset(test, old.getDateTime(), offset, 1); + assertEquals(test.getStandardOffset(instant), offset); + assertEquals(test.getDaylightSavings(instant), Duration.ZERO); + assertEquals(test.isDaylightSavings(instant), false); + } + + public void test_NewYork_getOffset() { + ZoneRules test = americaNewYork(); + ZoneOffset offset = ZoneOffset.ofHours(-5); + assertEquals(test.getOffset(createInstant(2008, 1, 1, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getOffset(createInstant(2008, 2, 1, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getOffset(createInstant(2008, 3, 1, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getOffset(createInstant(2008, 4, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 5, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 6, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 7, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 8, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 9, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 10, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 11, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 12, 1, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getOffset(createInstant(2008, 1, 28, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getOffset(createInstant(2008, 2, 28, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getOffset(createInstant(2008, 3, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 4, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 5, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 6, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 7, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 8, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 9, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 10, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 11, 28, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getOffset(createInstant(2008, 12, 28, offset)), ZoneOffset.ofHours(-5)); + } + + public void test_NewYork_getOffset_toDST() { + ZoneRules test = americaNewYork(); + ZoneOffset offset = ZoneOffset.ofHours(-5); + assertEquals(test.getOffset(createInstant(2008, 3, 8, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getOffset(createInstant(2008, 3, 9, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getOffset(createInstant(2008, 3, 10, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 3, 11, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 3, 12, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 3, 13, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 3, 14, offset)), ZoneOffset.ofHours(-4)); + // cutover at 02:00 local + assertEquals(test.getOffset(createInstant(2008, 3, 9, 1, 59, 59, 999999999, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getOffset(createInstant(2008, 3, 9, 2, 0, 0, 0, offset)), ZoneOffset.ofHours(-4)); + } + + public void test_NewYork_getOffset_fromDST() { + ZoneRules test = americaNewYork(); + ZoneOffset offset = ZoneOffset.ofHours(-4); + assertEquals(test.getOffset(createInstant(2008, 11, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 11, 2, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 11, 3, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getOffset(createInstant(2008, 11, 4, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getOffset(createInstant(2008, 11, 5, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getOffset(createInstant(2008, 11, 6, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getOffset(createInstant(2008, 11, 7, offset)), ZoneOffset.ofHours(-5)); + // cutover at 02:00 local + assertEquals(test.getOffset(createInstant(2008, 11, 2, 1, 59, 59, 999999999, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getOffset(createInstant(2008, 11, 2, 2, 0, 0, 0, offset)), ZoneOffset.ofHours(-5)); + } + + public void test_NewYork_getOffsetInfo() { + ZoneRules test = americaNewYork(); + checkOffset(test, createLDT(2008, 1, 1), ZoneOffset.ofHours(-5), 1); + checkOffset(test, createLDT(2008, 2, 1), ZoneOffset.ofHours(-5), 1); + checkOffset(test, createLDT(2008, 3, 1), ZoneOffset.ofHours(-5), 1); + checkOffset(test, createLDT(2008, 4, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 5, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 6, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 7, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 8, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 9, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 10, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 11, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 12, 1), ZoneOffset.ofHours(-5), 1); + checkOffset(test, createLDT(2008, 1, 28), ZoneOffset.ofHours(-5), 1); + checkOffset(test, createLDT(2008, 2, 28), ZoneOffset.ofHours(-5), 1); + checkOffset(test, createLDT(2008, 3, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 4, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 5, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 6, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 7, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 8, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 9, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 10, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 11, 28), ZoneOffset.ofHours(-5), 1); + checkOffset(test, createLDT(2008, 12, 28), ZoneOffset.ofHours(-5), 1); + } + + public void test_NewYork_getOffsetInfo_toDST() { + ZoneRules test = americaNewYork(); + checkOffset(test, createLDT(2008, 3, 8), ZoneOffset.ofHours(-5), 1); + checkOffset(test, createLDT(2008, 3, 9), ZoneOffset.ofHours(-5), 1); + checkOffset(test, createLDT(2008, 3, 10), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 3, 11), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 3, 12), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 3, 13), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 3, 14), ZoneOffset.ofHours(-4), 1); + // cutover at 02:00 local + checkOffset(test, LocalDateTime.of(2008, 3, 9, 1, 59, 59, 999999999), ZoneOffset.ofHours(-5), 1); + checkOffset(test, LocalDateTime.of(2008, 3, 9, 3, 0, 0, 0), ZoneOffset.ofHours(-4), 1); + } + + public void test_NewYork_getOffsetInfo_fromDST() { + ZoneRules test = americaNewYork(); + checkOffset(test, createLDT(2008, 11, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 11, 2), ZoneOffset.ofHours(-4), 1); + checkOffset(test, createLDT(2008, 11, 3), ZoneOffset.ofHours(-5), 1); + checkOffset(test, createLDT(2008, 11, 4), ZoneOffset.ofHours(-5), 1); + checkOffset(test, createLDT(2008, 11, 5), ZoneOffset.ofHours(-5), 1); + checkOffset(test, createLDT(2008, 11, 6), ZoneOffset.ofHours(-5), 1); + checkOffset(test, createLDT(2008, 11, 7), ZoneOffset.ofHours(-5), 1); + // cutover at 02:00 local + checkOffset(test, LocalDateTime.of(2008, 11, 2, 0, 59, 59, 999999999), ZoneOffset.ofHours(-4), 1); + checkOffset(test, LocalDateTime.of(2008, 11, 2, 2, 0, 0, 0), ZoneOffset.ofHours(-5), 1); + } + + public void test_NewYork_getOffsetInfo_gap() { + ZoneRules test = americaNewYork(); + final LocalDateTime dateTime = LocalDateTime.of(2008, 3, 9, 2, 0, 0, 0); + ZoneOffsetTransition trans = checkOffset(test, dateTime, ZoneOffset.ofHours(-5), GAP); + assertEquals(trans.isGap(), true); + assertEquals(trans.isOverlap(), false); + assertEquals(trans.getOffsetBefore(), ZoneOffset.ofHours(-5)); + assertEquals(trans.getOffsetAfter(), ZoneOffset.ofHours(-4)); + assertEquals(trans.getInstant(), createInstant(2008, 3, 9, 2, 0, ZoneOffset.ofHours(-5))); + assertEquals(trans.isValidOffset(OFFSET_PTWO), false); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(-5)), false); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(-4)), false); + assertEquals(trans.toString(), "Transition[Gap at 2008-03-09T02:00-05:00 to -04:00]"); + + assertFalse(trans.equals(null)); + assertFalse(trans.equals(ZoneOffset.ofHours(-5))); + assertTrue(trans.equals(trans)); + + final ZoneOffsetTransition otherTrans = test.getTransition(dateTime); + assertTrue(trans.equals(otherTrans)); + assertEquals(trans.hashCode(), otherTrans.hashCode()); + } + + public void test_NewYork_getOffsetInfo_overlap() { + ZoneRules test = americaNewYork(); + final LocalDateTime dateTime = LocalDateTime.of(2008, 11, 2, 1, 0, 0, 0); + ZoneOffsetTransition trans = checkOffset(test, dateTime, ZoneOffset.ofHours(-4), OVERLAP); + assertEquals(trans.isGap(), false); + assertEquals(trans.isOverlap(), true); + assertEquals(trans.getOffsetBefore(), ZoneOffset.ofHours(-4)); + assertEquals(trans.getOffsetAfter(), ZoneOffset.ofHours(-5)); + assertEquals(trans.getInstant(), createInstant(2008, 11, 2, 2, 0, ZoneOffset.ofHours(-4))); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(-1)), false); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(-5)), true); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(-4)), true); + assertEquals(trans.isValidOffset(OFFSET_PTWO), false); + assertEquals(trans.toString(), "Transition[Overlap at 2008-11-02T02:00-04:00 to -05:00]"); + + assertFalse(trans.equals(null)); + assertFalse(trans.equals(ZoneOffset.ofHours(-4))); + assertTrue(trans.equals(trans)); + + final ZoneOffsetTransition otherTrans = test.getTransition(dateTime); + assertTrue(trans.equals(otherTrans)); + assertEquals(trans.hashCode(), otherTrans.hashCode()); + } + + public void test_NewYork_getStandardOffset() { + ZoneRules test = americaNewYork(); + ZonedDateTime dateTime = createZDT(1860, 1, 1, ZoneOffset.UTC); + while (dateTime.getYear() < 2010) { + Instant instant = dateTime.toInstant(); + if (dateTime.getDate().isBefore(LocalDate.of(1883, 11, 18))) { + assertEquals(test.getStandardOffset(instant), ZoneOffset.of("-04:56:02")); + } else { + assertEquals(test.getStandardOffset(instant), ZoneOffset.ofHours(-5)); + } + dateTime = dateTime.plusMonths(6); + } + } + + //----------------------------------------------------------------------- + // Kathmandu + //----------------------------------------------------------------------- + private ZoneRules asiaKathmandu() { + return ZoneId.of("Asia/Kathmandu").getRules(); + } + + public void test_Kathmandu_nextTransition_historic() { + ZoneRules test = asiaKathmandu(); + List trans = test.getTransitions(); + + ZoneOffsetTransition first = trans.get(0); + assertEquals(test.nextTransition(first.getInstant().minusNanos(1)), first); + + for (int i = 0; i < trans.size() - 1; i++) { + ZoneOffsetTransition cur = trans.get(i); + ZoneOffsetTransition next = trans.get(i + 1); + + assertEquals(test.nextTransition(cur.getInstant()), next); + assertEquals(test.nextTransition(next.getInstant().minusNanos(1)), next); + } + } + + public void test_Kathmandu_nextTransition_noRules() { + ZoneRules test = asiaKathmandu(); + List trans = test.getTransitions(); + + ZoneOffsetTransition last = trans.get(trans.size() - 1); + assertEquals(test.nextTransition(last.getInstant()), null); + } + + //------------------------------------------------------------------------- + @Test(expectedExceptions=UnsupportedOperationException.class) + public void test_getTransitions_immutable() { + ZoneRules test = europeParis(); + test.getTransitions().clear(); + } + + @Test(expectedExceptions=UnsupportedOperationException.class) + public void test_getTransitionRules_immutable() { + ZoneRules test = europeParis(); + test.getTransitionRules().clear(); + } + + //----------------------------------------------------------------------- + // equals() / hashCode() + //----------------------------------------------------------------------- + public void test_equals() { + ZoneRules test1 = europeLondon(); + ZoneRules test2 = europeParis(); + ZoneRules test2b = europeParis(); + assertEquals(test1.equals(test2), false); + assertEquals(test2.equals(test1), false); + + assertEquals(test1.equals(test1), true); + assertEquals(test2.equals(test2), true); + assertEquals(test2.equals(test2b), true); + + assertEquals(test1.hashCode() == test1.hashCode(), true); + assertEquals(test2.hashCode() == test2.hashCode(), true); + assertEquals(test2.hashCode() == test2b.hashCode(), true); + } + + public void test_equals_null() { + assertEquals(europeLondon().equals(null), false); + } + + public void test_equals_notZoneRules() { + assertEquals(europeLondon().equals("Europe/London"), false); + } + + public void test_toString() { + assertEquals(europeLondon().toString().contains("ZoneRules"), true); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + private Instant createInstant(int year, int month, int day, ZoneOffset offset) { + return LocalDateTime.of(year, month, day, 0, 0).toInstant(offset); + } + + private Instant createInstant(int year, int month, int day, int hour, int min, ZoneOffset offset) { + return LocalDateTime.of(year, month, day, hour, min).toInstant(offset); + } + + private Instant createInstant(int year, int month, int day, int hour, int min, int sec, int nano, ZoneOffset offset) { + return LocalDateTime.of(year, month, day, hour, min, sec, nano).toInstant(offset); + } + + private ZonedDateTime createZDT(int year, int month, int day, ZoneId zone) { + return LocalDateTime.of(year, month, day, 0, 0).atZone(zone); + } + + private LocalDateTime createLDT(int year, int month, int day) { + return LocalDateTime.of(year, month, day, 0, 0); + } + + private ZoneOffsetTransition checkOffset(ZoneRules rules, LocalDateTime dateTime, ZoneOffset offset, int type) { + List validOffsets = rules.getValidOffsets(dateTime); + assertEquals(validOffsets.size(), type); + assertEquals(rules.getOffset(dateTime), offset); + if (type == 1) { + assertEquals(validOffsets.get(0), offset); + return null; + } else { + ZoneOffsetTransition zot = rules.getTransition(dateTime); + assertNotNull(zot); + assertEquals(zot.isOverlap(), type == 2); + assertEquals(zot.isGap(), type == 0); + assertEquals(zot.isValidOffset(offset), type == 2); + return zot; + } + } + +} diff --git a/test/java/time/tck/java/time/zone/TCKZoneRulesProvider.java b/test/java/time/tck/java/time/zone/TCKZoneRulesProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..2d245f66115194c93607a584d09b8e2f35432356 --- /dev/null +++ b/test/java/time/tck/java/time/zone/TCKZoneRulesProvider.java @@ -0,0 +1,196 @@ +/* + * 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. + * + * 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 tck.java.time.zone; + +import java.time.zone.*; +import test.java.time.zone.*; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.util.Collections; +import java.util.HashSet; +import java.util.NavigableMap; +import java.util.Set; +import java.util.TreeMap; + +import java.time.ZoneOffset; + +import org.testng.annotations.Test; + +/** + * Test ZoneRulesProvider. + */ +@Test +public class TCKZoneRulesProvider { + + private static String TZDB_VERSION = "2012i"; + + //----------------------------------------------------------------------- + // getAvailableZoneIds() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_getAvailableGroupIds() { + Set zoneIds = ZoneRulesProvider.getAvailableZoneIds(); + assertEquals(zoneIds.contains("Europe/London"), true); + zoneIds.clear(); + assertEquals(zoneIds.size(), 0); + Set zoneIds2 = ZoneRulesProvider.getAvailableZoneIds(); + assertEquals(zoneIds2.contains("Europe/London"), true); + } + + //----------------------------------------------------------------------- + // getRules(String) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_getRules_String() { + ZoneRules rules = ZoneRulesProvider.getRules("Europe/London"); + assertNotNull(rules); + ZoneRules rules2 = ZoneRulesProvider.getRules("Europe/London"); + assertEquals(rules2, rules); + } + + @Test(groups={"tck"}, expectedExceptions=ZoneRulesException.class) + public void test_getRules_String_unknownId() { + ZoneRulesProvider.getRules("Europe/Lon"); + } + + @Test(groups={"tck"}, expectedExceptions=NullPointerException.class) + public void test_getRules_String_null() { + ZoneRulesProvider.getRules(null); + } + + //----------------------------------------------------------------------- + // getVersions(String) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_getVersions_String() { + NavigableMap versions = ZoneRulesProvider.getVersions("Europe/London"); + assertTrue(versions.size() >= 1); + ZoneRules rules = ZoneRulesProvider.getRules("Europe/London"); + assertEquals(versions.lastEntry().getValue(), rules); + + NavigableMap copy = new TreeMap<>(versions); + versions.clear(); + assertEquals(versions.size(), 0); + NavigableMap versions2 = ZoneRulesProvider.getVersions("Europe/London"); + assertEquals(versions2, copy); + } + + @Test(groups={"tck"}, expectedExceptions=ZoneRulesException.class) + public void test_getVersions_String_unknownId() { + ZoneRulesProvider.getVersions("Europe/Lon"); + } + + @Test(groups={"tck"}, expectedExceptions=NullPointerException.class) + public void test_getVersions_String_null() { + ZoneRulesProvider.getVersions(null); + } + + //----------------------------------------------------------------------- + // refresh() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_refresh() { + assertEquals(ZoneRulesProvider.refresh(), false); + } + + //----------------------------------------------------------------------- + // registerProvider() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_registerProvider() { + Set pre = ZoneRulesProvider.getAvailableZoneIds(); + assertEquals(pre.contains("FooLocation"), false); + ZoneRulesProvider.registerProvider(new MockTempProvider()); + assertEquals(pre.contains("FooLocation"), false); + Set post = ZoneRulesProvider.getAvailableZoneIds(); + assertEquals(post.contains("FooLocation"), true); + + assertEquals(ZoneRulesProvider.getRules("FooLocation"), ZoneOffset.of("+01:45").getRules()); + } + + static class MockTempProvider extends ZoneRulesProvider { + final ZoneRules rules = ZoneOffset.of("+01:45").getRules(); + @Override + public Set provideZoneIds() { + return new HashSet(Collections.singleton("FooLocation")); + } + @Override + protected ZoneRulesProvider provideBind(String zoneId) { + return this; + } + @Override + protected NavigableMap provideVersions(String zoneId) { + NavigableMap result = new TreeMap<>(); + result.put("BarVersion", rules); + return result; + } + @Override + protected ZoneRules provideRules(String zoneId) { + if (zoneId.equals("FooLocation")) { + return rules; + } + throw new ZoneRulesException("Invalid"); + } + } + +} diff --git a/test/java/time/test/java/time/AbstractTest.java b/test/java/time/test/java/time/AbstractTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6d4d1340964a03e6882da57061e476629a633bf5 --- /dev/null +++ b/test/java/time/test/java/time/AbstractTest.java @@ -0,0 +1,131 @@ +/* + * 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. + * + * 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) 2011-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 test.java.time; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +/** + * Base test class. + */ +public abstract class AbstractTest { + + protected static boolean isIsoLeap(long year) { + if (year % 4 != 0) { + return false; + } + if (year % 100 == 0 && year % 400 != 0) { + return false; + } + return true; + } + + protected static void assertSerializable(Object o) throws IOException, ClassNotFoundException { + Object deserialisedObject = writeThenRead(o); + assertEquals(deserialisedObject, o); + } + + protected static void assertSerializableAndSame(Object o) throws IOException, ClassNotFoundException { + Object deserialisedObject = writeThenRead(o); + assertSame(deserialisedObject, o); + } + + private static Object writeThenRead(Object o) throws IOException, ClassNotFoundException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (ObjectOutputStream oos = new ObjectOutputStream(baos) ) { + oos.writeObject(o); + } + + try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()))) { + return ois.readObject(); + } + } + + protected static void assertImmutable(Class cls) { + assertTrue(Modifier.isPublic(cls.getModifiers())); + assertTrue(Modifier.isFinal(cls.getModifiers())); + Field[] fields = cls.getDeclaredFields(); + for (Field field : fields) { + if (field.getName().contains("$") == false) { + if (Modifier.isStatic(field.getModifiers())) { + assertTrue(Modifier.isFinal(field.getModifiers()), "Field:" + field.getName()); + } else { + assertTrue(Modifier.isPrivate(field.getModifiers()), "Field:" + field.getName()); + assertTrue(Modifier.isFinal(field.getModifiers()), "Field:" + field.getName()); + } + } + } + Constructor[] cons = cls.getDeclaredConstructors(); + for (Constructor con : cons) { + assertTrue(Modifier.isPrivate(con.getModifiers())); + } + } + +} diff --git a/test/java/time/test/java/time/MockSimplePeriod.java b/test/java/time/test/java/time/MockSimplePeriod.java new file mode 100644 index 0000000000000000000000000000000000000000..1c5d8726bed9862c692e56e8b654cb75e5c2eff3 --- /dev/null +++ b/test/java/time/test/java/time/MockSimplePeriod.java @@ -0,0 +1,174 @@ +/* + * 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. + * + * 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) 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 test.java.time; + +import java.time.*; + +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.FOREVER; +import static java.time.temporal.ChronoUnit.SECONDS; + +import java.util.Objects; + +import java.time.temporal.Temporal; +import java.time.temporal.TemporalSubtractor; +import java.time.temporal.TemporalAdder; +import java.time.temporal.TemporalUnit; + +/** + * Mock period of time measured using a single unit, such as {@code 3 Days}. + */ +public final class MockSimplePeriod + implements TemporalAdder, TemporalSubtractor, Comparable { + + /** + * A constant for a period of zero, measured in days. + */ + public static final MockSimplePeriod ZERO_DAYS = new MockSimplePeriod(0, DAYS); + /** + * A constant for a period of zero, measured in seconds. + */ + public static final MockSimplePeriod ZERO_SECONDS = new MockSimplePeriod(0, SECONDS); + + /** + * The amount of the period. + */ + private final long amount; + /** + * The unit the period is measured in. + */ + private final TemporalUnit unit; + + /** + * Obtains a {@code MockSimplePeriod} from an amount and unit. + *

    + * The parameters represent the two parts of a phrase like '6 Days'. + * + * @param amount the amount of the period, measured in terms of the unit, positive or negative + * @param unit the unit that the period is measured in, must not be the 'Forever' unit, not null + * @return the {@code MockSimplePeriod} instance, not null + * @throws DateTimeException if the period unit is {@link java.time.temporal.ChronoUnit#FOREVER}. + */ + public static MockSimplePeriod of(long amount, TemporalUnit unit) { + return new MockSimplePeriod(amount, unit); + } + + private MockSimplePeriod(long amount, TemporalUnit unit) { + Objects.requireNonNull(unit, "unit"); + if (unit == FOREVER) { + throw new DateTimeException("Cannot create a period of the Forever unit"); + } + this.amount = amount; + this.unit = unit; + } + + //----------------------------------------------------------------------- + public long getAmount() { + return amount; + } + + public TemporalUnit getUnit() { + return unit; + } + + //------------------------------------------------------------------------- + @Override + public Temporal addTo(Temporal temporal) { + return temporal.plus(amount, unit); + } + + @Override + public Temporal subtractFrom(Temporal temporal) { + return temporal.minus(amount, unit); + } + + //----------------------------------------------------------------------- + @Override + public int compareTo(MockSimplePeriod otherPeriod) { + if (unit.equals(otherPeriod.getUnit()) == false) { + throw new IllegalArgumentException("Units cannot be compared: " + unit + " and " + otherPeriod.getUnit()); + } + return Long.compare(amount, otherPeriod.amount); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof MockSimplePeriod) { + MockSimplePeriod other = (MockSimplePeriod) obj; + return this.amount == other.amount && + this.unit.equals(other.unit); + } + return false; + } + + @Override + public int hashCode() { + return unit.hashCode() ^ (int) (amount ^ (amount >>> 32)); + } + + @Override + public String toString() { + return amount + " " + unit.getName(); + } + +} diff --git a/test/java/time/test/java/time/TestClock_Fixed.java b/test/java/time/test/java/time/TestClock_Fixed.java new file mode 100644 index 0000000000000000000000000000000000000000..cf07ceea07b1935d150de77af4526ba336594672 --- /dev/null +++ b/test/java/time/test/java/time/TestClock_Fixed.java @@ -0,0 +1,95 @@ +/* + * 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. + * + * 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 test.java.time; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; + +import java.time.Clock; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; + +import org.testng.annotations.Test; + +/** + * Test fixed clock. + */ +@Test +public class TestClock_Fixed { + + private static final ZoneId PARIS = ZoneId.of("Europe/Paris"); + private static final Instant INSTANT = LocalDateTime.of(2008, 6, 30, 11, 30, 10, 500).atZone(ZoneOffset.ofHours(2)).toInstant(); + + //------------------------------------------------------------------------- + public void test_withZone_same() { + Clock test = Clock.fixed(INSTANT, PARIS); + Clock changed = test.withZone(PARIS); + assertSame(test, changed); + } + + //----------------------------------------------------------------------- + public void test_toString() { + Clock test = Clock.fixed(INSTANT, PARIS); + assertEquals(test.toString(), "FixedClock[2008-06-30T09:30:10.000000500Z,Europe/Paris]"); + } + +} diff --git a/test/java/time/test/java/time/TestClock_Offset.java b/test/java/time/test/java/time/TestClock_Offset.java new file mode 100644 index 0000000000000000000000000000000000000000..ed62300e681174aca3841a447fa3ea243c952271 --- /dev/null +++ b/test/java/time/test/java/time/TestClock_Offset.java @@ -0,0 +1,93 @@ +/* + * 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. + * + * 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 test.java.time; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; + +import java.time.Clock; +import java.time.Duration; +import java.time.ZoneId; + +import org.testng.annotations.Test; + +/** + * Test offset clock. + */ +@Test +public class TestClock_Offset { + + private static final ZoneId PARIS = ZoneId.of("Europe/Paris"); + private static final Duration OFFSET = Duration.ofSeconds(2); + + //------------------------------------------------------------------------- + public void test_withZone_same() { + Clock test = Clock.offset(Clock.system(PARIS), OFFSET); + Clock changed = test.withZone(PARIS); + assertSame(test, changed); + } + + //----------------------------------------------------------------------- + public void test_toString() { + Clock test = Clock.offset(Clock.systemUTC(), OFFSET); + assertEquals(test.toString(), "OffsetClock[SystemClock[Z],PT2S]"); + } + +} diff --git a/test/java/time/test/java/time/TestClock_System.java b/test/java/time/test/java/time/TestClock_System.java new file mode 100644 index 0000000000000000000000000000000000000000..00e9bbffe84d72cdc62588c7e847f7290271f423 --- /dev/null +++ b/test/java/time/test/java/time/TestClock_System.java @@ -0,0 +1,89 @@ +/* + * 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. + * + * 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 test.java.time; + +import java.time.*; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; + +import org.testng.annotations.Test; + +/** + * Test system clock. + */ +@Test +public class TestClock_System { + + private static final ZoneId PARIS = ZoneId.of("Europe/Paris"); + + public void test_withZone_same() { + Clock test = Clock.system(PARIS); + Clock changed = test.withZone(PARIS); + assertSame(test, changed); + } + + //----------------------------------------------------------------------- + public void test_toString() { + Clock test = Clock.system(PARIS); + assertEquals(test.toString(), "SystemClock[Europe/Paris]"); + } + +} diff --git a/test/java/time/test/java/time/TestClock_Tick.java b/test/java/time/test/java/time/TestClock_Tick.java new file mode 100644 index 0000000000000000000000000000000000000000..0e8fdca4e0523ef77617a41ed51009e7b9bf8e14 --- /dev/null +++ b/test/java/time/test/java/time/TestClock_Tick.java @@ -0,0 +1,97 @@ +/* + * 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. + * + * 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 test.java.time; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; + +import java.time.Clock; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +import org.testng.annotations.Test; + +/** + * Test tick clock. + */ +@Test +public class TestClock_Tick { + + private static final ZoneId MOSCOW = ZoneId.of("Europe/Moscow"); + private static final ZoneId PARIS = ZoneId.of("Europe/Paris"); + private static final ZonedDateTime ZDT = LocalDateTime.of(2008, 6, 30, 11, 30, 10, 500).atZone(ZoneOffset.ofHours(2)); + + //------------------------------------------------------------------------- + public void test_withZone_same() { + Clock test = Clock.tick(Clock.system(PARIS), Duration.ofMillis(500)); + Clock changed = test.withZone(PARIS); + assertSame(test, changed); + } + + //----------------------------------------------------------------------- + public void test_toString() { + Clock test = Clock.tick(Clock.systemUTC(), Duration.ofMillis(500)); + assertEquals(test.toString(), "TickClock[SystemClock[Z],PT0.5S]"); + } + +} diff --git a/test/java/time/test/java/time/TestDuration.java b/test/java/time/test/java/time/TestDuration.java new file mode 100644 index 0000000000000000000000000000000000000000..2b2d8105003fbca0738e1482e4d1dbe45f90e6d5 --- /dev/null +++ b/test/java/time/test/java/time/TestDuration.java @@ -0,0 +1,239 @@ +/* + * 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. + * + * 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 test.java.time; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import java.time.Duration; + +import org.testng.annotations.Test; + +/** + * Test Duration. + */ +@Test +public class TestDuration extends AbstractTest { + + //----------------------------------------------------------------------- + @Test + public void test_immutable() { + assertImmutable(Duration.class); + } + + //----------------------------------------------------------------------- + @Test(groups={"implementation"}) + public void test_interfaces() { + assertTrue(Serializable.class.isAssignableFrom(Duration.class)); + assertTrue(Comparable.class.isAssignableFrom(Duration.class)); + } + + //----------------------------------------------------------------------- + // serialization + //----------------------------------------------------------------------- + @Test(groups={"implementation"}) + public void test_deserializationSingleton() throws Exception { + Duration orginal = Duration.ZERO; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(orginal); + out.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + Duration ser = (Duration) in.readObject(); + assertSame(ser, Duration.ZERO); + } + + @Test(groups={"implementation"}) + public void plus_zeroReturnsThis() { + Duration t = Duration.ofSeconds(-1); + assertSame(t.plus(Duration.ZERO), t); + } + + @Test(groups={"implementation"}) + public void plus_zeroSingleton() { + Duration t = Duration.ofSeconds(-1); + assertSame(t.plus(Duration.ofSeconds(1)), Duration.ZERO); + } + + @Test(groups={"implementation"}) + public void plusSeconds_zeroReturnsThis() { + Duration t = Duration.ofSeconds(-1); + assertSame(t.plusSeconds(0), t); + } + + @Test(groups={"implementation"}) + public void plusSeconds_zeroSingleton() { + Duration t = Duration.ofSeconds(-1); + assertSame(t.plusSeconds(1), Duration.ZERO); + } + + @Test(groups={"implementation"}) + public void plusMillis_zeroReturnsThis() { + Duration t = Duration.ofSeconds(-1, 2000000); + assertSame(t.plusMillis(0), t); + } + + @Test(groups={"implementation"}) + public void plusMillis_zeroSingleton() { + Duration t = Duration.ofSeconds(-1, 2000000); + assertSame(t.plusMillis(998), Duration.ZERO); + } + + @Test(groups={"implementation"}) + public void plusNanos_zeroReturnsThis() { + Duration t = Duration.ofSeconds(-1, 2000000); + assertSame(t.plusNanos(0), t); + } + + @Test(groups={"implementation"}) + public void plusNanos_zeroSingleton() { + Duration t = Duration.ofSeconds(-1, 2000000); + assertSame(t.plusNanos(998000000), Duration.ZERO); + } + + @Test(groups={"implementation"}) + public void minus_zeroReturnsThis() { + Duration t = Duration.ofSeconds(1); + assertSame(t.minus(Duration.ZERO), t); + } + + @Test(groups={"implementation"}) + public void minus_zeroSingleton() { + Duration t = Duration.ofSeconds(1); + assertSame(t.minus(Duration.ofSeconds(1)), Duration.ZERO); + } + + @Test(groups={"implementation"}) + public void minusSeconds_zeroReturnsThis() { + Duration t = Duration.ofSeconds(1); + assertSame(t.minusSeconds(0), t); + } + + @Test(groups={"implementation"}) + public void minusSeconds_zeroSingleton() { + Duration t = Duration.ofSeconds(1); + assertSame(t.minusSeconds(1), Duration.ZERO); + } + + @Test(groups={"implementation"}) + public void minusMillis_zeroReturnsThis() { + Duration t = Duration.ofSeconds(1, 2000000); + assertSame(t.minusMillis(0), t); + } + + @Test(groups={"implementation"}) + public void minusMillis_zeroSingleton() { + Duration t = Duration.ofSeconds(1, 2000000); + assertSame(t.minusMillis(1002), Duration.ZERO); + } + + @Test(groups={"implementation"}) + public void minusNanos_zeroReturnsThis() { + Duration t = Duration.ofSeconds(1, 2000000); + assertSame(t.minusNanos(0), t); + } + + @Test(groups={"implementation"}) + public void minusNanos_zeroSingleton() { + Duration t = Duration.ofSeconds(1, 2000000); + assertSame(t.minusNanos(1002000000), Duration.ZERO); + } + + @Test(groups={"implementation"}) + public void test_abs_same() { + Duration base = Duration.ofSeconds(12); + assertSame(base.abs(), base); + } + + void doTest_comparisons_Duration(Duration... durations) { + for (int i = 0; i < durations.length; i++) { + Duration a = durations[i]; + for (int j = 0; j < durations.length; j++) { + Duration b = durations[j]; + if (i < j) { + assertEquals(a.compareTo(b)< 0, true, a + " <=> " + b); + assertEquals(a.isLessThan(b), true, a + " <=> " + b); + assertEquals(a.isGreaterThan(b), false, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else if (i > j) { + assertEquals(a.compareTo(b) > 0, true, a + " <=> " + b); + assertEquals(a.isLessThan(b), false, a + " <=> " + b); + assertEquals(a.isGreaterThan(b), true, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else { + assertEquals(a.compareTo(b), 0, a + " <=> " + b); + assertEquals(a.isLessThan(b), false, a + " <=> " + b); + assertEquals(a.isGreaterThan(b), false, a + " <=> " + b); + assertEquals(a.equals(b), true, a + " <=> " + b); + } + } + } + } + +} diff --git a/test/java/time/test/java/time/TestInstant.java b/test/java/time/test/java/time/TestInstant.java new file mode 100644 index 0000000000000000000000000000000000000000..cf135a1ae2d45ef12af781faca7ecd57c0c15ffd --- /dev/null +++ b/test/java/time/test/java/time/TestInstant.java @@ -0,0 +1,77 @@ +/* + * 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. + * + * 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 test.java.time; + +import java.time.Instant; + +import org.testng.annotations.Test; + +/** + * Test Instant. + */ +@Test +public class TestInstant extends AbstractTest { + + @Test + public void test_immutable() { + assertImmutable(Instant.class); + } + +} diff --git a/test/java/time/test/java/time/TestLocalDate.java b/test/java/time/test/java/time/TestLocalDate.java new file mode 100644 index 0000000000000000000000000000000000000000..44033123cdf6b6a9ce8d350d1411cc9c505ea467 --- /dev/null +++ b/test/java/time/test/java/time/TestLocalDate.java @@ -0,0 +1,447 @@ +/* + * 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. + * + * 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 test.java.time; + +import static java.time.temporal.ChronoField.YEAR; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.time.LocalDate; +import java.time.Month; +import java.time.temporal.ChronoUnit; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test LocalDate. + */ +@Test +public class TestLocalDate extends AbstractTest { + + private LocalDate TEST_2007_07_15; + + @BeforeMethod(groups={"tck", "implementation"}) + public void setUp() { + TEST_2007_07_15 = LocalDate.of(2007, 7, 15); + } + + //----------------------------------------------------------------------- + @Test + public void test_immutable() { + assertImmutable(LocalDate.class); + } + + //----------------------------------------------------------------------- + // Since plusDays/minusDays actually depends on MJDays, it cannot be used for testing + private LocalDate next(LocalDate date) { + int newDayOfMonth = date.getDayOfMonth() + 1; + if (newDayOfMonth <= date.getMonth().length(isIsoLeap(date.getYear()))) { + return date.withDayOfMonth(newDayOfMonth); + } + date = date.withDayOfMonth(1); + if (date.getMonth() == Month.DECEMBER) { + date = date.withYear(date.getYear() + 1); + } + return date.with(date.getMonth().plus(1)); + } + + private LocalDate previous(LocalDate date) { + int newDayOfMonth = date.getDayOfMonth() - 1; + if (newDayOfMonth > 0) { + return date.withDayOfMonth(newDayOfMonth); + } + date = date.with(date.getMonth().minus(1)); + if (date.getMonth() == Month.DECEMBER) { + date = date.withYear(date.getYear() - 1); + } + return date.withDayOfMonth(date.getMonth().length(isIsoLeap(date.getYear()))); + } + + @Test(groups={"implementation"}) + public void test_with_DateTimeField_long_noChange_same() { + LocalDate t = TEST_2007_07_15.with(YEAR, 2007); + assertSame(t, TEST_2007_07_15); + } + + @Test(groups={"implementation"}) + public void test_withYear_int_noChange_same() { + LocalDate t = TEST_2007_07_15.withYear(2007); + assertSame(t, TEST_2007_07_15); + } + + @Test(groups={"implementation"}) + public void test_withMonth_int_noChange_same() { + LocalDate t = TEST_2007_07_15.withMonth(7); + assertSame(t, TEST_2007_07_15); + } + + @Test(groups={"implementation"}) + public void test_withDayOfMonth_noChange_same() { + LocalDate t = TEST_2007_07_15.withDayOfMonth(15); + assertSame(t, TEST_2007_07_15); + } + + @Test(groups={"implementation"}) + public void test_withDayOfYear_noChange_same() { + LocalDate t = TEST_2007_07_15.withDayOfYear(31 + 28 + 31 + 30 + 31 + 30 + 15); + assertSame(t, TEST_2007_07_15); + } + + @Test(groups={"implementation"}) + public void test_plus_Period_zero() { + LocalDate t = TEST_2007_07_15.plus(MockSimplePeriod.ZERO_DAYS); + assertSame(t, TEST_2007_07_15); + } + + @Test(groups={"implementation"}) + public void test_plus_longPeriodUnit_zero() { + LocalDate t = TEST_2007_07_15.plus(0, ChronoUnit.DAYS); + assertSame(t, TEST_2007_07_15); + } + + @Test(groups={"implementation"}) + public void test_plusYears_long_noChange_same() { + LocalDate t = TEST_2007_07_15.plusYears(0); + assertSame(t, TEST_2007_07_15); + } + + @Test(groups={"implementation"}) + public void test_plusMonths_long_noChange_same() { + LocalDate t = TEST_2007_07_15.plusMonths(0); + assertSame(t, TEST_2007_07_15); + } + + //----------------------------------------------------------------------- + // plusWeeks() + //----------------------------------------------------------------------- + @DataProvider(name="samplePlusWeeksSymmetry") + Object[][] provider_samplePlusWeeksSymmetry() { + return new Object[][] { + {LocalDate.of(-1, 1, 1)}, + {LocalDate.of(-1, 2, 28)}, + {LocalDate.of(-1, 3, 1)}, + {LocalDate.of(-1, 12, 31)}, + {LocalDate.of(0, 1, 1)}, + {LocalDate.of(0, 2, 28)}, + {LocalDate.of(0, 2, 29)}, + {LocalDate.of(0, 3, 1)}, + {LocalDate.of(0, 12, 31)}, + {LocalDate.of(2007, 1, 1)}, + {LocalDate.of(2007, 2, 28)}, + {LocalDate.of(2007, 3, 1)}, + {LocalDate.of(2007, 12, 31)}, + {LocalDate.of(2008, 1, 1)}, + {LocalDate.of(2008, 2, 28)}, + {LocalDate.of(2008, 2, 29)}, + {LocalDate.of(2008, 3, 1)}, + {LocalDate.of(2008, 12, 31)}, + {LocalDate.of(2099, 1, 1)}, + {LocalDate.of(2099, 2, 28)}, + {LocalDate.of(2099, 3, 1)}, + {LocalDate.of(2099, 12, 31)}, + {LocalDate.of(2100, 1, 1)}, + {LocalDate.of(2100, 2, 28)}, + {LocalDate.of(2100, 3, 1)}, + {LocalDate.of(2100, 12, 31)}, + }; + } + + @Test(dataProvider="samplePlusWeeksSymmetry", groups={"implementation"}) + public void test_plusWeeks_symmetry(LocalDate reference) { + for (int weeks = 0; weeks < 365 * 8; weeks++) { + LocalDate t = reference.plusWeeks(weeks).plusWeeks(-weeks); + assertEquals(t, reference); + + t = reference.plusWeeks(-weeks).plusWeeks(weeks); + assertEquals(t, reference); + } + } + + @Test(groups={"implementation"}) + public void test_plusWeeks_noChange_same() { + LocalDate t = TEST_2007_07_15.plusWeeks(0); + assertSame(t, TEST_2007_07_15); + } + + //----------------------------------------------------------------------- + // plusDays() + //----------------------------------------------------------------------- + @DataProvider(name="samplePlusDaysSymmetry") + Object[][] provider_samplePlusDaysSymmetry() { + return new Object[][] { + {LocalDate.of(-1, 1, 1)}, + {LocalDate.of(-1, 2, 28)}, + {LocalDate.of(-1, 3, 1)}, + {LocalDate.of(-1, 12, 31)}, + {LocalDate.of(0, 1, 1)}, + {LocalDate.of(0, 2, 28)}, + {LocalDate.of(0, 2, 29)}, + {LocalDate.of(0, 3, 1)}, + {LocalDate.of(0, 12, 31)}, + {LocalDate.of(2007, 1, 1)}, + {LocalDate.of(2007, 2, 28)}, + {LocalDate.of(2007, 3, 1)}, + {LocalDate.of(2007, 12, 31)}, + {LocalDate.of(2008, 1, 1)}, + {LocalDate.of(2008, 2, 28)}, + {LocalDate.of(2008, 2, 29)}, + {LocalDate.of(2008, 3, 1)}, + {LocalDate.of(2008, 12, 31)}, + {LocalDate.of(2099, 1, 1)}, + {LocalDate.of(2099, 2, 28)}, + {LocalDate.of(2099, 3, 1)}, + {LocalDate.of(2099, 12, 31)}, + {LocalDate.of(2100, 1, 1)}, + {LocalDate.of(2100, 2, 28)}, + {LocalDate.of(2100, 3, 1)}, + {LocalDate.of(2100, 12, 31)}, + }; + } + + @Test(dataProvider="samplePlusDaysSymmetry", groups={"implementation"}) + public void test_plusDays_symmetry(LocalDate reference) { + for (int days = 0; days < 365 * 8; days++) { + LocalDate t = reference.plusDays(days).plusDays(-days); + assertEquals(t, reference); + + t = reference.plusDays(-days).plusDays(days); + assertEquals(t, reference); + } + } + + @Test(groups={"implementation"}) + public void test_plusDays_noChange_same() { + LocalDate t = TEST_2007_07_15.plusDays(0); + assertSame(t, TEST_2007_07_15); + } + + @Test(groups={"implementation"}) + public void test_minus_Period_zero() { + LocalDate t = TEST_2007_07_15.minus(MockSimplePeriod.ZERO_DAYS); + assertSame(t, TEST_2007_07_15); + } + + @Test(groups={"implementation"}) + public void test_minus_longPeriodUnit_zero() { + LocalDate t = TEST_2007_07_15.minus(0, ChronoUnit.DAYS); + assertSame(t, TEST_2007_07_15); + } + + @Test(groups={"implementation"}) + public void test_minusYears_long_noChange_same() { + LocalDate t = TEST_2007_07_15.minusYears(0); + assertSame(t, TEST_2007_07_15); + } + + @Test(groups={"implementation"}) + public void test_minusMonths_long_noChange_same() { + LocalDate t = TEST_2007_07_15.minusMonths(0); + assertSame(t, TEST_2007_07_15); + } + + //----------------------------------------------------------------------- + // minusWeeks() + //----------------------------------------------------------------------- + @DataProvider(name="sampleMinusWeeksSymmetry") + Object[][] provider_sampleMinusWeeksSymmetry() { + return new Object[][] { + {LocalDate.of(-1, 1, 1)}, + {LocalDate.of(-1, 2, 28)}, + {LocalDate.of(-1, 3, 1)}, + {LocalDate.of(-1, 12, 31)}, + {LocalDate.of(0, 1, 1)}, + {LocalDate.of(0, 2, 28)}, + {LocalDate.of(0, 2, 29)}, + {LocalDate.of(0, 3, 1)}, + {LocalDate.of(0, 12, 31)}, + {LocalDate.of(2007, 1, 1)}, + {LocalDate.of(2007, 2, 28)}, + {LocalDate.of(2007, 3, 1)}, + {LocalDate.of(2007, 12, 31)}, + {LocalDate.of(2008, 1, 1)}, + {LocalDate.of(2008, 2, 28)}, + {LocalDate.of(2008, 2, 29)}, + {LocalDate.of(2008, 3, 1)}, + {LocalDate.of(2008, 12, 31)}, + {LocalDate.of(2099, 1, 1)}, + {LocalDate.of(2099, 2, 28)}, + {LocalDate.of(2099, 3, 1)}, + {LocalDate.of(2099, 12, 31)}, + {LocalDate.of(2100, 1, 1)}, + {LocalDate.of(2100, 2, 28)}, + {LocalDate.of(2100, 3, 1)}, + {LocalDate.of(2100, 12, 31)}, + }; + } + + @Test(dataProvider="sampleMinusWeeksSymmetry", groups={"implementation"}) + public void test_minusWeeks_symmetry(LocalDate reference) { + for (int weeks = 0; weeks < 365 * 8; weeks++) { + LocalDate t = reference.minusWeeks(weeks).minusWeeks(-weeks); + assertEquals(t, reference); + + t = reference.minusWeeks(-weeks).minusWeeks(weeks); + assertEquals(t, reference); + } + } + + @Test(groups={"implementation"}) + public void test_minusWeeks_noChange_same() { + LocalDate t = TEST_2007_07_15.minusWeeks(0); + assertSame(t, TEST_2007_07_15); + } + + //----------------------------------------------------------------------- + // minusDays() + //----------------------------------------------------------------------- + @DataProvider(name="sampleMinusDaysSymmetry") + Object[][] provider_sampleMinusDaysSymmetry() { + return new Object[][] { + {LocalDate.of(-1, 1, 1)}, + {LocalDate.of(-1, 2, 28)}, + {LocalDate.of(-1, 3, 1)}, + {LocalDate.of(-1, 12, 31)}, + {LocalDate.of(0, 1, 1)}, + {LocalDate.of(0, 2, 28)}, + {LocalDate.of(0, 2, 29)}, + {LocalDate.of(0, 3, 1)}, + {LocalDate.of(0, 12, 31)}, + {LocalDate.of(2007, 1, 1)}, + {LocalDate.of(2007, 2, 28)}, + {LocalDate.of(2007, 3, 1)}, + {LocalDate.of(2007, 12, 31)}, + {LocalDate.of(2008, 1, 1)}, + {LocalDate.of(2008, 2, 28)}, + {LocalDate.of(2008, 2, 29)}, + {LocalDate.of(2008, 3, 1)}, + {LocalDate.of(2008, 12, 31)}, + {LocalDate.of(2099, 1, 1)}, + {LocalDate.of(2099, 2, 28)}, + {LocalDate.of(2099, 3, 1)}, + {LocalDate.of(2099, 12, 31)}, + {LocalDate.of(2100, 1, 1)}, + {LocalDate.of(2100, 2, 28)}, + {LocalDate.of(2100, 3, 1)}, + {LocalDate.of(2100, 12, 31)}, + }; + } + + @Test(dataProvider="sampleMinusDaysSymmetry", groups={"implementation"}) + public void test_minusDays_symmetry(LocalDate reference) { + for (int days = 0; days < 365 * 8; days++) { + LocalDate t = reference.minusDays(days).minusDays(-days); + assertEquals(t, reference); + + t = reference.minusDays(-days).minusDays(days); + assertEquals(t, reference); + } + } + + @Test(groups={"implementation"}) + public void test_minusDays_noChange_same() { + LocalDate t = TEST_2007_07_15.minusDays(0); + assertSame(t, TEST_2007_07_15); + } + + @Test(groups={"implementation"}) + public void test_toEpochDay_fromMJDays_symmetry() { + long date_0000_01_01 = -678941 - 40587; + + LocalDate test = LocalDate.of(0, 1, 1); + for (long i = date_0000_01_01; i < 700000; i++) { + assertEquals(LocalDate.ofEpochDay(test.toEpochDay()), test); + test = next(test); + } + test = LocalDate.of(0, 1, 1); + for (long i = date_0000_01_01; i > -2000000; i--) { + assertEquals(LocalDate.ofEpochDay(test.toEpochDay()), test); + test = previous(test); + } + } + + void doTest_comparisons_LocalDate(LocalDate... localDates) { + for (int i = 0; i < localDates.length; i++) { + LocalDate a = localDates[i]; + for (int j = 0; j < localDates.length; j++) { + LocalDate b = localDates[j]; + if (i < j) { + assertTrue(a.compareTo(b) < 0, a + " <=> " + b); + assertEquals(a.isBefore(b), true, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else if (i > j) { + assertTrue(a.compareTo(b) > 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), true, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else { + assertEquals(a.compareTo(b), 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), true, a + " <=> " + b); + } + } + } + } + +} diff --git a/test/java/time/test/java/time/TestLocalDateTime.java b/test/java/time/test/java/time/TestLocalDateTime.java new file mode 100644 index 0000000000000000000000000000000000000000..d850be50715d8a54e20c169bc9218c4282f3ddb5 --- /dev/null +++ b/test/java/time/test/java/time/TestLocalDateTime.java @@ -0,0 +1,570 @@ +/* + * 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. + * + * 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 test.java.time; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.time.temporal.ChronoUnit; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test LocalDateTime. + */ +@Test +public class TestLocalDateTime extends AbstractTest { + + private LocalDateTime TEST_2007_07_15_12_30_40_987654321 = LocalDateTime.of(2007, 7, 15, 12, 30, 40, 987654321); + + //----------------------------------------------------------------------- + @Test + public void test_immutable() { + assertImmutable(LocalDateTime.class); + } + + //----------------------------------------------------------------------- + @DataProvider(name="sampleDates") + Object[][] provider_sampleDates() { + return new Object[][] { + {2008, 7, 5}, + {2007, 7, 5}, + {2006, 7, 5}, + {2005, 7, 5}, + {2004, 1, 1}, + {-1, 1, 2}, + }; + } + + @DataProvider(name="sampleTimes") + Object[][] provider_sampleTimes() { + return new Object[][] { + {0, 0, 0, 0}, + {0, 0, 0, 1}, + {0, 0, 1, 0}, + {0, 0, 1, 1}, + {0, 1, 0, 0}, + {0, 1, 0, 1}, + {0, 1, 1, 0}, + {0, 1, 1, 1}, + {1, 0, 0, 0}, + {1, 0, 0, 1}, + {1, 0, 1, 0}, + {1, 0, 1, 1}, + {1, 1, 0, 0}, + {1, 1, 0, 1}, + {1, 1, 1, 0}, + {1, 1, 1, 1}, + }; + } + + @Test(groups={"implementation"}) + public void test_withYear_int_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.withYear(2007); + assertSame(t.getDate(), TEST_2007_07_15_12_30_40_987654321.getDate()); + assertSame(t.getTime(), TEST_2007_07_15_12_30_40_987654321.getTime()); + } + + @Test(groups={"implementation"}) + public void test_withMonth_int_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.withMonth(7); + assertSame(t.getDate(), TEST_2007_07_15_12_30_40_987654321.getDate()); + assertSame(t.getTime(), TEST_2007_07_15_12_30_40_987654321.getTime()); + } + + @Test(groups={"implementation"}) + public void test_withDayOfMonth_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.withDayOfMonth(15); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_withDayOfYear_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.withDayOfYear(31 + 28 + 31 + 30 + 31 + 30 + 15); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_withHour_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.withHour(12); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_withHour_toMidnight() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(1, 0)).withHour(0); + assertSame(t.getTime(), LocalTime.MIDNIGHT); + } + + @Test(groups={"implementation"}) + public void test_withHour_toMidday() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(1, 0)).withHour(12); + assertSame(t.getTime(), LocalTime.NOON); + } + + @Test(groups={"implementation"}) + public void test_withMinute_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.withMinute(30); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_withMinute_toMidnight() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(0, 1)).withMinute(0); + assertSame(t.getTime(), LocalTime.MIDNIGHT); + } + + @Test(groups={"implementation"}) + public void test_withMinute_toMidday() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(12, 1)).withMinute(0); + assertSame(t.getTime(), LocalTime.NOON); + } + + @Test(groups={"implementation"}) + public void test_withSecond_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.withSecond(40); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_withSecond_toMidnight() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(0, 0, 1)).withSecond(0); + assertSame(t.getTime(), LocalTime.MIDNIGHT); + } + + @Test(groups={"implementation"}) + public void test_withSecond_toMidday() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(12, 0, 1)).withSecond(0); + assertSame(t.getTime(), LocalTime.NOON); + } + + @Test(groups={"implementation"}) + public void test_withNanoOfSecond_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.withNano(987654321); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_withNanoOfSecond_toMidnight() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(0, 0, 0, 1)).withNano(0); + assertSame(t.getTime(), LocalTime.MIDNIGHT); + } + + @Test(groups={"implementation"}) + public void test_withNanoOfSecond_toMidday() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(12, 0, 0, 1)).withNano(0); + assertSame(t.getTime(), LocalTime.NOON); + } + + @Test(groups={"implementation"}) + public void test_plus_adjuster_zero() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plus(Period.ZERO); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_plus_Period_zero() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plus(MockSimplePeriod.ZERO_DAYS); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_plus_longPeriodUnit_zero() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plus(0, ChronoUnit.DAYS); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_plusYears_int_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusYears(0); + assertSame(TEST_2007_07_15_12_30_40_987654321, t); + } + + @Test(groups={"implementation"}) + public void test_plusMonths_int_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusMonths(0); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_plusWeeks_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusWeeks(0); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_plusDays_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusDays(0); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_plusHours_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusHours(0); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_plusHours_toMidnight() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(23, 0)).plusHours(1); + assertSame(t.getTime(), LocalTime.MIDNIGHT); + } + + @Test(groups={"implementation"}) + public void test_plusHours_toMidday() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(11, 0)).plusHours(1); + assertSame(t.getTime(), LocalTime.NOON); + } + + @Test(groups={"implementation"}) + public void test_plusMinutes_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusMinutes(0); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_plusMinutes_noChange_oneDay_same() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusMinutes(24 * 60); + assertSame(t.getTime(), TEST_2007_07_15_12_30_40_987654321.getTime()); + } + + @Test(groups={"implementation"}) + public void test_plusMinutes_toMidnight() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(23, 59)).plusMinutes(1); + assertSame(t.getTime(), LocalTime.MIDNIGHT); + } + + @Test(groups={"implementation"}) + public void test_plusMinutes_toMidday() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(11, 59)).plusMinutes(1); + assertSame(t.getTime(), LocalTime.NOON); + } + + @Test(groups={"implementation"}) + public void test_plusSeconds_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusSeconds(0); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_plusSeconds_noChange_oneDay_same() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusSeconds(24 * 60 * 60); + assertSame(t.getTime(), TEST_2007_07_15_12_30_40_987654321.getTime()); + } + + @Test(groups={"implementation"}) + public void test_plusSeconds_toMidnight() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(23, 59, 59)).plusSeconds(1); + assertSame(t.getTime(), LocalTime.MIDNIGHT); + } + + @Test(groups={"implementation"}) + public void test_plusSeconds_toMidday() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(11, 59, 59)).plusSeconds(1); + assertSame(t.getTime(), LocalTime.NOON); + } + + @Test(groups={"implementation"}) + public void test_plusNanos_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusNanos(0); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_plusNanos_noChange_oneDay_same() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.plusNanos(24 * 60 * 60 * 1000000000L); + assertSame(t.getTime(), TEST_2007_07_15_12_30_40_987654321.getTime()); + } + + @Test(groups={"implementation"}) + public void test_plusNanos_toMidnight() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(23, 59, 59, 999999999)).plusNanos(1); + assertSame(t.getTime(), LocalTime.MIDNIGHT); + } + + @Test(groups={"implementation"}) + public void test_plusNanos_toMidday() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(11, 59, 59, 999999999)).plusNanos(1); + assertSame(t.getTime(), LocalTime.NOON); + } + + @Test(groups={"implementation"}) + public void test_minus_adjuster_zero() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minus(Period.ZERO); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_minus_Period_zero() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minus(MockSimplePeriod.ZERO_DAYS); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_minus_longPeriodUnit_zero() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minus(0, ChronoUnit.DAYS); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_minusYears_int_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusYears(0); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_minusMonths_int_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusMonths(0); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_minusWeeks_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusWeeks(0); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_minusDays_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusDays(0); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_minusHours_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusHours(0); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_minusHours_toMidnight() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(1, 0)).minusHours(1); + assertSame(t.getTime(), LocalTime.MIDNIGHT); + } + + @Test(groups={"implementation"}) + public void test_minusHours_toMidday() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(13, 0)).minusHours(1); + assertSame(t.getTime(), LocalTime.NOON); + } + + @Test(groups={"implementation"}) + public void test_minusMinutes_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusMinutes(0); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_minusMinutes_noChange_oneDay_same() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusMinutes(24 * 60); + assertSame(t.getTime(), TEST_2007_07_15_12_30_40_987654321.getTime()); + } + + @Test(groups={"implementation"}) + public void test_minusMinutes_toMidnight() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(0, 1)).minusMinutes(1); + assertSame(t.getTime(), LocalTime.MIDNIGHT); + } + + @Test(groups={"implementation"}) + public void test_minusMinutes_toMidday() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(12, 1)).minusMinutes(1); + assertSame(t.getTime(), LocalTime.NOON); + } + + @Test(groups={"implementation"}) + public void test_minusSeconds_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusSeconds(0); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_minusSeconds_noChange_oneDay() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusSeconds(24 * 60 * 60); + assertEquals(t.getDate(), TEST_2007_07_15_12_30_40_987654321.getDate().minusDays(1)); + assertSame(t.getTime(), TEST_2007_07_15_12_30_40_987654321.getTime()); + } + + @Test(groups={"implementation"}) + public void test_minusSeconds_toMidnight() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(0, 0, 1)).minusSeconds(1); + assertSame(t.getTime(), LocalTime.MIDNIGHT); + } + + @Test(groups={"implementation"}) + public void test_minusSeconds_toMidday() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(12, 0, 1)).minusSeconds(1); + assertSame(t.getTime(), LocalTime.NOON); + } + + @Test(groups={"implementation"}) + public void test_minusNanos_noChange() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusNanos(0); + assertSame(t, TEST_2007_07_15_12_30_40_987654321); + } + + @Test(groups={"implementation"}) + public void test_minusNanos_noChange_oneDay() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.minusNanos(24 * 60 * 60 * 1000000000L); + assertEquals(t.getDate(), TEST_2007_07_15_12_30_40_987654321.getDate().minusDays(1)); + assertSame(t.getTime(), TEST_2007_07_15_12_30_40_987654321.getTime()); + } + + @Test(groups={"implementation"}) + public void test_minusNanos_toMidnight() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(0, 0, 0, 1)).minusNanos(1); + assertSame(t.getTime(), LocalTime.MIDNIGHT); + } + + @Test(groups={"implementation"}) + public void test_minusNanos_toMidday() { + LocalDateTime t = TEST_2007_07_15_12_30_40_987654321.with(LocalTime.of(12, 0, 0, 1)).minusNanos(1); + assertSame(t.getTime(), LocalTime.NOON); + } + + //----------------------------------------------------------------------- + // getDate() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleDates", groups={"implementation"}) + public void test_getDate(int year, int month, int day) { + LocalDate d = LocalDate.of(year, month, day); + LocalDateTime dt = LocalDateTime.of(d, LocalTime.MIDNIGHT); + assertSame(dt.getDate(), d); + } + + //----------------------------------------------------------------------- + // getTime() + //----------------------------------------------------------------------- + @Test(dataProvider="sampleTimes", groups={"implementation"}) + public void test_getTime(int h, int m, int s, int ns) { + LocalTime t = LocalTime.of(h, m, s, ns); + LocalDateTime dt = LocalDateTime.of(LocalDate.of(2011, 7, 30), t); + assertSame(dt.getTime(), t); + } + + void test_comparisons_LocalDateTime(LocalDate... localDates) { + test_comparisons_LocalDateTime( + localDates, + LocalTime.MIDNIGHT, + LocalTime.of(0, 0, 0, 999999999), + LocalTime.of(0, 0, 59, 0), + LocalTime.of(0, 0, 59, 999999999), + LocalTime.of(0, 59, 0, 0), + LocalTime.of(0, 59, 59, 999999999), + LocalTime.NOON, + LocalTime.of(12, 0, 0, 999999999), + LocalTime.of(12, 0, 59, 0), + LocalTime.of(12, 0, 59, 999999999), + LocalTime.of(12, 59, 0, 0), + LocalTime.of(12, 59, 59, 999999999), + LocalTime.of(23, 0, 0, 0), + LocalTime.of(23, 0, 0, 999999999), + LocalTime.of(23, 0, 59, 0), + LocalTime.of(23, 0, 59, 999999999), + LocalTime.of(23, 59, 0, 0), + LocalTime.of(23, 59, 59, 999999999) + ); + } + + void test_comparisons_LocalDateTime(LocalDate[] localDates, LocalTime... localTimes) { + LocalDateTime[] localDateTimes = new LocalDateTime[localDates.length * localTimes.length]; + int i = 0; + + for (LocalDate localDate : localDates) { + for (LocalTime localTime : localTimes) { + localDateTimes[i++] = LocalDateTime.of(localDate, localTime); + } + } + + doTest_comparisons_LocalDateTime(localDateTimes); + } + + void doTest_comparisons_LocalDateTime(LocalDateTime[] localDateTimes) { + for (int i = 0; i < localDateTimes.length; i++) { + LocalDateTime a = localDateTimes[i]; + for (int j = 0; j < localDateTimes.length; j++) { + LocalDateTime b = localDateTimes[j]; + if (i < j) { + assertTrue(a.compareTo(b) < 0, a + " <=> " + b); + assertEquals(a.isBefore(b), true, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else if (i > j) { + assertTrue(a.compareTo(b) > 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), true, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else { + assertEquals(a.compareTo(b), 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), true, a + " <=> " + b); + } + } + } + } + +} diff --git a/test/java/time/test/java/time/TestLocalTime.java b/test/java/time/test/java/time/TestLocalTime.java new file mode 100644 index 0000000000000000000000000000000000000000..0a0b27796b172738596349d3121971c6d89e3595 --- /dev/null +++ b/test/java/time/test/java/time/TestLocalTime.java @@ -0,0 +1,188 @@ +/* + * 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. + * + * 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 test.java.time; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; + +import java.time.LocalTime; + +import org.testng.annotations.Test; + +/** + * Test LocalTime. + */ +@Test +public class TestLocalTime extends AbstractTest { + + //----------------------------------------------------------------------- + @Test + public void test_immutable() { + assertImmutable(LocalTime.class); + } + + //----------------------------------------------------------------------- + private void check(LocalTime time, int h, int m, int s, int n) { + assertEquals(time.getHour(), h); + assertEquals(time.getMinute(), m); + assertEquals(time.getSecond(), s); + assertEquals(time.getNano(), n); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck","implementation"}) + public void constant_MIDNIGHT() { + check(LocalTime.MIDNIGHT, 0, 0, 0, 0); + } + + @Test(groups={"implementation"}) + public void constant_MIDNIGHT_same() { + assertSame(LocalTime.MIDNIGHT, LocalTime.MIDNIGHT); + assertSame(LocalTime.MIDNIGHT, LocalTime.of(0, 0)); + } + + @Test(groups={"tck","implementation"}) + public void constant_MIDDAY() { + check(LocalTime.NOON, 12, 0, 0, 0); + } + + @Test(groups={"implementation"}) + public void constant_MIDDAY_same() { + assertSame(LocalTime.NOON, LocalTime.NOON); + assertSame(LocalTime.NOON, LocalTime.of(12, 0)); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck","implementation"}) + public void constant_MIN_TIME() { + check(LocalTime.MIN, 0, 0, 0, 0); + } + + @Test(groups={"implementation"}) + public void constant_MIN_TIME_same() { + assertSame(LocalTime.MIN, LocalTime.of(0, 0)); + } + + @Test(groups={"tck","implementation"}) + public void constant_MAX_TIME() { + check(LocalTime.MAX, 23, 59, 59, 999999999); + } + + @Test(groups={"implementation"}) + public void constant_MAX_TIME_same() { + assertSame(LocalTime.NOON, LocalTime.NOON); + assertSame(LocalTime.NOON, LocalTime.of(12, 0)); + } + + @Test(groups={"implementation"}) + public void factory_time_2ints_singletons() { + for (int i = 0; i < 24; i++) { + LocalTime test1 = LocalTime.of(i, 0); + LocalTime test2 = LocalTime.of(i, 0); + assertSame(test1, test2); + } + } + + @Test(groups={"implementation"}) + public void factory_time_3ints_singletons() { + for (int i = 0; i < 24; i++) { + LocalTime test1 = LocalTime.of(i, 0, 0); + LocalTime test2 = LocalTime.of(i, 0, 0); + assertSame(test1, test2); + } + } + + @Test(groups={"implementation"}) + public void factory_time_4ints_singletons() { + for (int i = 0; i < 24; i++) { + LocalTime test1 = LocalTime.of(i, 0, 0, 0); + LocalTime test2 = LocalTime.of(i, 0, 0, 0); + assertSame(test1, test2); + } + } + + @Test(groups={"implementation"}) + public void factory_ofSecondOfDay_singletons() { + for (int i = 0; i < 24; i++) { + LocalTime test1 = LocalTime.ofSecondOfDay(i * 60L * 60L); + LocalTime test2 = LocalTime.of(i, 0); + assertSame(test1, test2); + } + } + + @Test(groups={"implementation"}) + public void factory_ofSecondOfDay7_long_int_singletons() { + for (int i = 0; i < 24; i++) { + LocalTime test1 = LocalTime.ofSecondOfDay(i * 60L * 60L, 0); + LocalTime test2 = LocalTime.of(i, 0); + assertSame(test1, test2); + } + } + + @Test(groups={"implementation"}) + public void factory_ofNanoOfDay_singletons() { + for (int i = 0; i < 24; i++) { + LocalTime test1 = LocalTime.ofNanoOfDay(i * 1000000000L * 60L * 60L); + LocalTime test2 = LocalTime.of(i, 0); + assertSame(test1, test2); + } + } + +} diff --git a/test/java/time/test/java/time/TestPeriod.java b/test/java/time/test/java/time/TestPeriod.java new file mode 100644 index 0000000000000000000000000000000000000000..f7caf9e334b7557610aa6999ab8197a93462a34e --- /dev/null +++ b/test/java/time/test/java/time/TestPeriod.java @@ -0,0 +1,1575 @@ +/* + * 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. + * + * 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 test.java.time; + +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.HALF_DAYS; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MICROS; +import static java.time.temporal.ChronoUnit.MILLIS; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static java.time.temporal.ChronoUnit.YEARS; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import java.time.DateTimeException; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; +import java.time.Period; +import java.time.temporal.YearMonth; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test. + */ +@Test +public class TestPeriod extends AbstractTest { + + //----------------------------------------------------------------------- + // basics + //----------------------------------------------------------------------- + public void test_interfaces() { + assertTrue(Serializable.class.isAssignableFrom(Period.class)); + } + + @DataProvider(name="serialization") + Object[][] data_serialization() { + return new Object[][] { + {Period.ZERO}, + {Period.of(0, DAYS)}, + {Period.of(1, DAYS)}, + {Period.of(1, 2, 3, 4, 5, 6)}, + }; + } + + @Test(dataProvider="serialization") + public void test_serialization(Period period) throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(period); + oos.close(); + + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream( + baos.toByteArray())); + if (period.isZero()) { + assertSame(ois.readObject(), period); + } else { + assertEquals(ois.readObject(), period); + } + } + + @Test + public void test_immutable() { + assertImmutable(Period.class); + } + + //----------------------------------------------------------------------- + // factories + //----------------------------------------------------------------------- + public void factory_zeroSingleton() { + assertSame(Period.ZERO, Period.ZERO); + assertSame(Period.of(0, 0, 0, 0, 0, 0), Period.ZERO); + assertSame(Period.of(0, 0, 0, 0, 0, 0, 0), Period.ZERO); + assertSame(Period.ofDate(0, 0, 0), Period.ZERO); + assertSame(Period.ofTime(0, 0, 0), Period.ZERO); + assertSame(Period.ofTime(0, 0, 0, 0), Period.ZERO); + assertSame(Period.of(0, YEARS), Period.ZERO); + assertSame(Period.of(0, MONTHS), Period.ZERO); + assertSame(Period.of(0, DAYS), Period.ZERO); + assertSame(Period.of(0, HOURS), Period.ZERO); + assertSame(Period.of(0, MINUTES), Period.ZERO); + assertSame(Period.of(0, SECONDS), Period.ZERO); + assertSame(Period.of(0, NANOS), Period.ZERO); + } + + //----------------------------------------------------------------------- + // of(PeriodProvider) + //----------------------------------------------------------------------- + public void factory_of_ints() { + assertPeriod(Period.of(1, 2, 3, 4, 5, 6), 1, 2, 3, 4, 5, 6, 0); + assertPeriod(Period.of(0, 2, 3, 4, 5, 6), 0, 2, 3, 4, 5, 6, 0); + assertPeriod(Period.of(1, 0, 0, 0, 0, 0), 1, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.of(0, 0, 0, 0, 0, 0), 0, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.of(-1, -2, -3, -4, -5, -6), -1, -2, -3, -4, -5, -6, 0); + } + + //----------------------------------------------------------------------- + // ofDate + //----------------------------------------------------------------------- + public void factory_ofDate_ints() { + assertPeriod(Period.ofDate(1, 2, 3), 1, 2, 3, 0, 0, 0, 0); + assertPeriod(Period.ofDate(0, 2, 3), 0, 2, 3, 0, 0, 0, 0); + assertPeriod(Period.ofDate(1, 0, 0), 1, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.ofDate(0, 0, 0), 0, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.ofDate(-1, -2, -3), -1, -2, -3, 0, 0, 0, 0); + } + + //----------------------------------------------------------------------- + // ofTime + //----------------------------------------------------------------------- + public void factory_ofTime_3ints() { + assertPeriod(Period.ofTime(1, 2, 3), 0, 0, 0, 1, 2, 3, 0); + assertPeriod(Period.ofTime(0, 2, 3), 0, 0, 0, 0, 2, 3, 0); + assertPeriod(Period.ofTime(1, 0, 0), 0, 0, 0, 1, 0, 0, 0); + assertPeriod(Period.ofTime(0, 0, 0), 0, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.ofTime(-1, -2, -3), 0, 0, 0, -1, -2, -3, 0); + } + + public void factory_ofTime_4ints() { + assertPeriod(Period.ofTime(1, 2, 3, 4), 0, 0, 0, 1, 2, 3, 4); + assertPeriod(Period.ofTime(0, 2, 3, 4), 0, 0, 0, 0, 2, 3, 4); + assertPeriod(Period.ofTime(1, 0, 0, 0), 0, 0, 0, 1, 0, 0, 0); + assertPeriod(Period.ofTime(0, 0, 0, 0), 0, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.ofTime(-1, -2, -3, -4), 0, 0, 0, -1, -2, -3, -4); + } + + //----------------------------------------------------------------------- + // of one field + //----------------------------------------------------------------------- + public void test_factory_of_intPeriodUnit() { + assertEquals(Period.of(1, YEARS), Period.of(1, YEARS)); + assertEquals(Period.of(2, MONTHS), Period.of(2, MONTHS)); + assertEquals(Period.of(3, DAYS), Period.of(3, DAYS)); + + assertEquals(Period.of(1, HALF_DAYS), Period.of(12, HOURS)); + assertEquals(Period.of(Integer.MAX_VALUE / (3600 * 8), HOURS), Period.of(Integer.MAX_VALUE / (3600 * 8), HOURS)); + assertEquals(Period.of(-1, MINUTES), Period.of(-1, MINUTES)); + assertEquals(Period.of(-2, SECONDS), Period.of(-2, SECONDS)); + assertEquals(Period.of(Integer.MIN_VALUE, NANOS), Period.of(Integer.MIN_VALUE, NANOS)); + assertEquals(Period.of(2, MILLIS), Period.of(2000000, NANOS)); + assertEquals(Period.of(2, MICROS), Period.of(2000, NANOS)); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_factory_of_intPeriodUnit_null() { + Period.of(1, null); + } + + //----------------------------------------------------------------------- + public void factory_years() { + assertPeriod(Period.of(1, YEARS), 1, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.of(0, YEARS), 0, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.of(-1, YEARS), -1, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.of(Integer.MAX_VALUE, YEARS), Integer.MAX_VALUE, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.of(Integer.MIN_VALUE, YEARS), Integer.MIN_VALUE, 0, 0, 0, 0, 0, 0); + } + + public void factory_months() { + assertPeriod(Period.of(1, MONTHS), 0, 1, 0, 0, 0, 0, 0); + assertPeriod(Period.of(0, MONTHS), 0, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.of(-1, MONTHS), 0, -1, 0, 0, 0, 0, 0); + assertPeriod(Period.of(Integer.MAX_VALUE, MONTHS), 0, Integer.MAX_VALUE, 0, 0, 0, 0, 0); + assertPeriod(Period.of(Integer.MIN_VALUE, MONTHS), 0, Integer.MIN_VALUE, 0, 0, 0, 0, 0); + } + + public void factory_days() { + assertPeriod(Period.of(1, DAYS), 0, 0, 1, 0, 0, 0, 0); + assertPeriod(Period.of(0, DAYS), 0, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.of(-1, DAYS), 0, 0, -1, 0, 0, 0, 0); + assertPeriod(Period.of(Integer.MAX_VALUE, DAYS), 0, 0, Integer.MAX_VALUE, 0, 0, 0, 0); + assertPeriod(Period.of(Integer.MIN_VALUE, DAYS), 0, 0, Integer.MIN_VALUE, 0, 0, 0, 0); + } + + public void factory_hours() { + assertPeriod(Period.of(1, HOURS), 0, 0, 0, 1, 0, 0, 0); + assertPeriod(Period.of(0, HOURS), 0, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.of(-1, HOURS), 0, 0, 0, -1, 0, 0, 0); + assertPeriod(Period.of(Integer.MAX_VALUE / (3600 * 8), HOURS), 0, 0, 0, Integer.MAX_VALUE / (3600 * 8), 0, 0, 0); + assertPeriod(Period.of(Integer.MIN_VALUE / (3600 * 8), HOURS), 0, 0, 0, Integer.MIN_VALUE / (3600 * 8), 0, 0, 0); + } + + public void factory_minutes() { + assertPeriod(Period.of(1, MINUTES), 0, 0, 0, 0, 1, 0, 0); + assertPeriod(Period.of(0, MINUTES), 0, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.of(-1, MINUTES), 0, 0, 0, 0, -1, 0, 0); + int val = Integer.MAX_VALUE / (60 * 8); + assertPeriod(Period.of(val, MINUTES), 0, 0, 0, + (int) (val / 60L), + (int) (val % 60), + 0, 0); + val = Integer.MIN_VALUE / (60 * 8); + assertPeriod(Period.of(val, MINUTES), 0, 0, 0, + (int) (val / 60L), + (int) (val % 60), + 0, 0); + } + + public void factory_seconds() { + assertPeriod(Period.of(1, SECONDS), 0, 0, 0, 0, 0, 1, 0); + assertPeriod(Period.of(0, SECONDS), 0, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.of(-1, SECONDS), 0, 0, 0, 0, 0, -1, 0); + assertPeriod(Period.of(Integer.MAX_VALUE, SECONDS), 0, 0, 0, + (int) (Integer.MAX_VALUE / 3600L), + (int) ((Integer.MAX_VALUE / 60L) % 60), + (int) (Integer.MAX_VALUE % 60), + 0); + assertPeriod(Period.of(Integer.MIN_VALUE, SECONDS), 0, 0, 0, + (int) (Integer.MIN_VALUE / 3600L), + (int) ((Integer.MIN_VALUE / 60L) % 60), + (int) (Integer.MIN_VALUE % 60), + 0); + } + + public void factory_nanos() { + assertPeriod(Period.of(1, NANOS), 0, 0, 0, 0, 0, 0, 1); + assertPeriod(Period.of(0, NANOS), 0, 0, 0, 0, 0, 0, 0); + assertPeriod(Period.of(-1, NANOS), 0, 0, 0, 0, 0, 0, -1); + assertPeriod(Period.of(Long.MAX_VALUE, NANOS), 0, 0, 0, + (int) (Long.MAX_VALUE / 3600_000_000_000L), + (int) ((Long.MAX_VALUE / 60_000_000_000L) % 60), + (int) ((Long.MAX_VALUE / 1_000_000_000L) % 60), + Long.MAX_VALUE % 1_000_000_000L); + assertPeriod(Period.of(Long.MIN_VALUE, NANOS), 0, 0, 0, + (int) (Long.MIN_VALUE / 3600_000_000_000L), + (int) ((Long.MIN_VALUE / 60_000_000_000L) % 60), + (int) ((Long.MIN_VALUE / 1_000_000_000L) % 60), + Long.MIN_VALUE % 1_000_000_000L); + } + + //----------------------------------------------------------------------- + // of(Duration) + //----------------------------------------------------------------------- + public void factory_duration() { + assertPeriod(Period.of(Duration.ofSeconds(2, 3)), 0, 0, 0, 0, 0, 2, 3); + assertPeriod(Period.of(Duration.ofSeconds(59, 3)), 0, 0, 0, 0, 0, 59, 3); + assertPeriod(Period.of(Duration.ofSeconds(60, 3)), 0, 0, 0, 0, 1, 0, 3); + assertPeriod(Period.of(Duration.ofSeconds(61, 3)), 0, 0, 0, 0, 1, 1, 3); + assertPeriod(Period.of(Duration.ofSeconds(3599, 3)), 0, 0, 0, 0, 59, 59, 3); + assertPeriod(Period.of(Duration.ofSeconds(3600, 3)), 0, 0, 0, 1, 0, 0, 3); + } + + public void factory_duration_negative() { + assertPeriod(Period.of(Duration.ofSeconds(-2, 3)), 0, 0, 0, 0, 0, -1, -999999997); + assertPeriod(Period.of(Duration.ofSeconds(-59, 3)), 0, 0, 0, 0, 0, -58, -999999997); + assertPeriod(Period.of(Duration.ofSeconds(-60, 3)), 0, 0, 0, 0, 0, -59, -999999997); + assertPeriod(Period.of(Duration.ofSeconds(-60, -3)), 0, 0, 0, 0, -1, 0, -3); + + assertPeriod(Period.of(Duration.ofSeconds(2, -3)), 0, 0, 0, 0, 0, 1, 999999997); + } + + public void factory_duration_big() { + Duration dur = Duration.ofSeconds(Integer.MAX_VALUE, 3); + long secs = Integer.MAX_VALUE; + assertPeriod(Period.of(dur), 0, 0, 0, (int) (secs / 3600), (int) ((secs % 3600) / 60), (int) (secs % 60), 3); + } + + @Test(expectedExceptions=NullPointerException.class) + public void factory_duration_null() { + Period.of((Duration) null); + } + + //----------------------------------------------------------------------- + // between + //----------------------------------------------------------------------- + @DataProvider(name="betweenDates") + Object[][] data_betweenDates() { + return new Object[][] { + {2010, 1, 1, 2010, 1, 1, 0, 0, 0}, + {2010, 1, 1, 2010, 1, 2, 0, 0, 1}, + {2010, 1, 1, 2010, 2, 1, 0, 1, 0}, + {2010, 1, 1, 2010, 2, 2, 0, 1, 1}, + {2010, 1, 1, 2011, 1, 1, 1, 0, 0}, + + {2010, 6, 12, 2010, 1, 1, 0, -5, -11}, + {2010, 6, 12, 2010, 1, 2, 0, -5, -10}, + {2010, 6, 12, 2010, 2, 1, 0, -4, -11}, + {2010, 6, 12, 2010, 9, 24, 0, 3, 12}, + + {2010, 6, 12, 2009, 1, 1, -1, -5, -11}, + {2010, 6, 12, 2009, 1, 2, -1, -5, -10}, + {2010, 6, 12, 2009, 2, 1, -1, -4, -11}, + {2010, 6, 12, 2009, 9, 24, 0, -9, 12}, + + {2010, 6, 12, 2008, 1, 1, -2, -5, -11}, + {2010, 6, 12, 2008, 1, 2, -2, -5, -10}, + {2010, 6, 12, 2008, 2, 1, -2, -4, -11}, + {2010, 6, 12, 2008, 9, 24, -1, -9, 12}, + }; + } + + @Test(dataProvider="betweenDates") + public void factory_between_LocalDate(int y1, int m1, int d1, int y2, int m2, int d2, int ye, int me, int de) { + LocalDate start = LocalDate.of(y1, m1, d1); + LocalDate end = LocalDate.of(y2, m2, d2); + Period test = Period.between(start, end); + assertPeriod(test, ye, me, de, 0, 0, 0, 0); + //assertEquals(start.plus(test), end); + } + + @DataProvider(name="betweenTimes") + Object[][] data_betweenTimes() { + return new Object[][] { + {12, 30, 40, 12, 30, 45, 0, 0, 5}, + {12, 30, 40, 12, 35, 40, 0, 5, 0}, + {12, 30, 40, 13, 30, 40, 1, 0, 0}, + + {12, 30, 40, 12, 30, 35, 0, 0, -5}, + {12, 30, 40, 12, 25, 40, 0, -5, 0}, + {12, 30, 40, 11, 30, 40, -1, 0, 0}, + }; + } + + @Test(dataProvider="betweenTimes") + public void factory_between_LocalTime(int h1, int m1, int s1, int h2, int m2, int s2, int he, int me, int se) { + LocalTime start = LocalTime.of(h1, m1, s1); + LocalTime end = LocalTime.of(h2, m2, s2); + Period test = Period.between(start, end); + assertPeriod(test, 0, 0, 0, he, me, se, 0); + //assertEquals(start.plus(test), end); + } + + public void factory_between_YearMonth() { + assertPeriod(Period.between(YearMonth.of(2012, 6), YearMonth.of(2013, 7)), 1, 1, 0, 0, 0, 0, 0); + assertPeriod(Period.between(YearMonth.of(2012, 6), YearMonth.of(2013, 3)), 0, 9, 0, 0, 0, 0, 0); + assertPeriod(Period.between(YearMonth.of(2012, 6), YearMonth.of(2011, 7)), 0, -11, 0, 0, 0, 0, 0); + } + + public void factory_between_Month() { + assertPeriod(Period.between(Month.FEBRUARY, Month.MAY), 0, 3, 0, 0, 0, 0, 0); + assertPeriod(Period.between(Month.NOVEMBER, Month.MAY), 0, -6, 0, 0, 0, 0, 0); + } + + //----------------------------------------------------------------------- + // betweenISO + //----------------------------------------------------------------------- + @DataProvider(name="betweenISO") + Object[][] data_betweenISO() { + return new Object[][] { + {2010, 1, 1, 2010, 1, 1, 0, 0, 0}, + {2010, 1, 1, 2010, 1, 2, 0, 0, 1}, + {2010, 1, 1, 2010, 1, 31, 0, 0, 30}, + {2010, 1, 1, 2010, 2, 1, 0, 1, 0}, + {2010, 1, 1, 2010, 2, 28, 0, 1, 27}, + {2010, 1, 1, 2010, 3, 1, 0, 2, 0}, + {2010, 1, 1, 2010, 12, 31, 0, 11, 30}, + {2010, 1, 1, 2011, 1, 1, 1, 0, 0}, + {2010, 1, 1, 2011, 12, 31, 1, 11, 30}, + {2010, 1, 1, 2012, 1, 1, 2, 0, 0}, + + {2010, 1, 10, 2010, 1, 1, 0, 0, -9}, + {2010, 1, 10, 2010, 1, 2, 0, 0, -8}, + {2010, 1, 10, 2010, 1, 9, 0, 0, -1}, + {2010, 1, 10, 2010, 1, 10, 0, 0, 0}, + {2010, 1, 10, 2010, 1, 11, 0, 0, 1}, + {2010, 1, 10, 2010, 1, 31, 0, 0, 21}, + {2010, 1, 10, 2010, 2, 1, 0, 0, 22}, + {2010, 1, 10, 2010, 2, 9, 0, 0, 30}, + {2010, 1, 10, 2010, 2, 10, 0, 1, 0}, + {2010, 1, 10, 2010, 2, 28, 0, 1, 18}, + {2010, 1, 10, 2010, 3, 1, 0, 1, 19}, + {2010, 1, 10, 2010, 3, 9, 0, 1, 27}, + {2010, 1, 10, 2010, 3, 10, 0, 2, 0}, + {2010, 1, 10, 2010, 12, 31, 0, 11, 21}, + {2010, 1, 10, 2011, 1, 1, 0, 11, 22}, + {2010, 1, 10, 2011, 1, 9, 0, 11, 30}, + {2010, 1, 10, 2011, 1, 10, 1, 0, 0}, + + {2010, 3, 30, 2011, 5, 1, 1, 1, 1}, + {2010, 4, 30, 2011, 5, 1, 1, 0, 1}, + + {2010, 2, 28, 2012, 2, 27, 1, 11, 30}, + {2010, 2, 28, 2012, 2, 28, 2, 0, 0}, + {2010, 2, 28, 2012, 2, 29, 2, 0, 1}, + + {2012, 2, 28, 2014, 2, 27, 1, 11, 30}, + {2012, 2, 28, 2014, 2, 28, 2, 0, 0}, + {2012, 2, 28, 2014, 3, 1, 2, 0, 1}, + + {2012, 2, 29, 2014, 2, 28, 1, 11, 30}, + {2012, 2, 29, 2014, 3, 1, 2, 0, 1}, + {2012, 2, 29, 2014, 3, 2, 2, 0, 2}, + + {2012, 2, 29, 2016, 2, 28, 3, 11, 30}, + {2012, 2, 29, 2016, 2, 29, 4, 0, 0}, + {2012, 2, 29, 2016, 3, 1, 4, 0, 1}, + + {2010, 1, 1, 2009, 12, 31, 0, 0, -1}, + {2010, 1, 1, 2009, 12, 30, 0, 0, -2}, + {2010, 1, 1, 2009, 12, 2, 0, 0, -30}, + {2010, 1, 1, 2009, 12, 1, 0, -1, 0}, + {2010, 1, 1, 2009, 11, 30, 0, -1, -1}, + {2010, 1, 1, 2009, 11, 2, 0, -1, -29}, + {2010, 1, 1, 2009, 11, 1, 0, -2, 0}, + {2010, 1, 1, 2009, 1, 2, 0, -11, -30}, + {2010, 1, 1, 2009, 1, 1, -1, 0, 0}, + + {2010, 1, 15, 2010, 1, 15, 0, 0, 0}, + {2010, 1, 15, 2010, 1, 14, 0, 0, -1}, + {2010, 1, 15, 2010, 1, 1, 0, 0, -14}, + {2010, 1, 15, 2009, 12, 31, 0, 0, -15}, + {2010, 1, 15, 2009, 12, 16, 0, 0, -30}, + {2010, 1, 15, 2009, 12, 15, 0, -1, 0}, + {2010, 1, 15, 2009, 12, 14, 0, -1, -1}, + + {2010, 2, 28, 2009, 3, 1, 0, -11, -27}, + {2010, 2, 28, 2009, 2, 28, -1, 0, 0}, + {2010, 2, 28, 2009, 2, 27, -1, 0, -1}, + + {2010, 2, 28, 2008, 2, 29, -1, -11, -28}, + {2010, 2, 28, 2008, 2, 28, -2, 0, 0}, + {2010, 2, 28, 2008, 2, 27, -2, 0, -1}, + + {2012, 2, 29, 2009, 3, 1, -2, -11, -28}, + {2012, 2, 29, 2009, 2, 28, -3, 0, -1}, + {2012, 2, 29, 2009, 2, 27, -3, 0, -2}, + + {2012, 2, 29, 2008, 3, 1, -3, -11, -28}, + {2012, 2, 29, 2008, 2, 29, -4, 0, 0}, + {2012, 2, 29, 2008, 2, 28, -4, 0, -1}, + }; + } + + @Test(dataProvider="betweenISO") + public void factory_betweenISO_LocalDate(int y1, int m1, int d1, int y2, int m2, int d2, int ye, int me, int de) { + LocalDate start = LocalDate.of(y1, m1, d1); + LocalDate end = LocalDate.of(y2, m2, d2); + Period test = Period.betweenISO(start, end); + assertPeriod(test, ye, me, de, 0, 0, 0, 0); + //assertEquals(start.plus(test), end); + } + + @Test(expectedExceptions=NullPointerException.class) + public void factory_betweenISO_LocalDate_nullFirst() { + Period.betweenISO((LocalDate) null, LocalDate.of(2010, 1, 1)); + } + + @Test(expectedExceptions=NullPointerException.class) + public void factory_betweenISO_LocalDate_nullSecond() { + Period.betweenISO(LocalDate.of(2010, 1, 1), (LocalDate) null); + } + + //------------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class) + public void factory_betweenISO_LocalTime_nullFirst() { + Period.betweenISO((LocalTime) null, LocalTime.of(12, 30)); + } + + @Test(expectedExceptions=NullPointerException.class) + public void factory_betweenISO_LocalTime_nullSecond() { + Period.betweenISO(LocalTime.of(12, 30), (LocalTime) null); + } + + //----------------------------------------------------------------------- + // parse() + //----------------------------------------------------------------------- + @Test(dataProvider="toStringAndParse") + public void test_parse(Period test, String expected) { + assertEquals(test, Period.parse(expected)); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_parse_nullText() { + Period.parse((String) null); + } + + //----------------------------------------------------------------------- + // isZero() + //----------------------------------------------------------------------- + public void test_isZero() { + assertEquals(Period.of(1, 2, 3, 4, 5, 6, 7).isZero(), false); + assertEquals(Period.of(1, 2, 3, 0, 0, 0, 0).isZero(), false); + assertEquals(Period.of(0, 0, 0, 4, 5, 6, 7).isZero(), false); + assertEquals(Period.of(1, 0, 0, 0, 0, 0, 0).isZero(), false); + assertEquals(Period.of(0, 2, 0, 0, 0, 0, 0).isZero(), false); + assertEquals(Period.of(0, 0, 3, 0, 0, 0, 0).isZero(), false); + assertEquals(Period.of(0, 0, 0, 4, 0, 0, 0).isZero(), false); + assertEquals(Period.of(0, 0, 0, 0, 5, 0, 0).isZero(), false); + assertEquals(Period.of(0, 0, 0, 0, 0, 6, 0).isZero(), false); + assertEquals(Period.of(0, 0, 0, 0, 0, 0, 7).isZero(), false); + assertEquals(Period.of(0, 0, 0, 0, 0, 0).isZero(), true); + } + + //----------------------------------------------------------------------- + // isPositive() + //----------------------------------------------------------------------- + public void test_isPositive() { + assertEquals(Period.of(1, 2, 3, 4, 5, 6, 7).isPositive(), true); + assertEquals(Period.of(1, 2, 3, 0, 0, 0, 0).isPositive(), true); + assertEquals(Period.of(0, 0, 0, 4, 5, 6, 7).isPositive(), true); + assertEquals(Period.of(1, 0, 0, 0, 0, 0, 0).isPositive(), true); + assertEquals(Period.of(0, 2, 0, 0, 0, 0, 0).isPositive(), true); + assertEquals(Period.of(0, 0, 3, 0, 0, 0, 0).isPositive(), true); + assertEquals(Period.of(0, 0, 0, 4, 0, 0, 0).isPositive(), true); + assertEquals(Period.of(0, 0, 0, 0, 5, 0, 0).isPositive(), true); + assertEquals(Period.of(0, 0, 0, 0, 0, 6, 0).isPositive(), true); + assertEquals(Period.of(0, 0, 0, 0, 0, 0, 7).isPositive(), true); + assertEquals(Period.of(-1, -2, -3, -4, -5, -6, -7).isPositive(), false); + assertEquals(Period.of(-1, -2, 3, 4, -5, -6, -7).isPositive(), false); + assertEquals(Period.of(-1, 0, 0, 0, 0, 0, 0).isPositive(), false); + assertEquals(Period.of(0, -2, 0, 0, 0, 0, 0).isPositive(), false); + assertEquals(Period.of(0, 0, -3, 0, 0, 0, 0).isPositive(), false); + assertEquals(Period.of(0, 0, 0, -4, 0, 0, 0).isPositive(), false); + assertEquals(Period.of(0, 0, 0, 0, -5, 0, 0).isPositive(), false); + assertEquals(Period.of(0, 0, 0, 0, 0, -6, 0).isPositive(), false); + assertEquals(Period.of(0, 0, 0, 0, 0, 0, -7).isPositive(), false); + assertEquals(Period.of(0, 0, 0, 0, 0, 0).isPositive(), false); + } + + //----------------------------------------------------------------------- + // withYears() + //----------------------------------------------------------------------- + public void test_withYears() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.withYears(10), 10, 2, 3, 4, 5, 6, 7); + } + + public void test_withYears_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.withYears(1), test); + } + + public void test_withYears_toZero() { + Period test = Period.of(1, YEARS); + assertSame(test.withYears(0), Period.ZERO); + } + + //----------------------------------------------------------------------- + // withMonths() + //----------------------------------------------------------------------- + public void test_withMonths() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.withMonths(10), 1, 10, 3, 4, 5, 6, 7); + } + + public void test_withMonths_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.withMonths(2), test); + } + + public void test_withMonths_toZero() { + Period test = Period.of(1, MONTHS); + assertSame(test.withMonths(0), Period.ZERO); + } + + //----------------------------------------------------------------------- + // withDays() + //----------------------------------------------------------------------- + public void test_withDays() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.withDays(10), 1, 2, 10, 4, 5, 6, 7); + } + + public void test_withDays_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.withDays(3), test); + } + + public void test_withDays_toZero() { + Period test = Period.of(1, DAYS); + assertSame(test.withDays(0), Period.ZERO); + } + + //----------------------------------------------------------------------- + // withTimeNanos() + //----------------------------------------------------------------------- + public void test_withNanos() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.withTimeNanos(10), 1, 2, 3, 0, 0, 0, 10); + } + + public void test_withNanos_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.withTimeNanos(((4 * 60 + 5) * 60 + 6) * 1_000_000_000L + 7), test); + } + + public void test_withNanos_toZero() { + Period test = Period.of(1, NANOS); + assertSame(test.withTimeNanos(0), Period.ZERO); + } + + + + //----------------------------------------------------------------------- + // plusYears() + //----------------------------------------------------------------------- + public void test_plusYears() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.plus(10, YEARS), 11, 2, 3, 4, 5, 6, 7); + assertPeriod(test.plus(Period.of(10, YEARS)), 11, 2, 3, 4, 5, 6, 7); + } + + public void test_plusYears_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.plus(0, YEARS), test); + assertPeriod(test.plus(Period.of(0, YEARS)), 1, 2, 3, 4, 5, 6, 7); + } + + public void test_plusYears_toZero() { + Period test = Period.of(-1, YEARS); + assertSame(test.plus(1, YEARS), Period.ZERO); + assertSame(test.plus(Period.of(1, YEARS)), Period.ZERO); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_plusYears_overflowTooBig() { + Period test = Period.of(Integer.MAX_VALUE, YEARS); + test.plus(1, YEARS); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_plusYears_overflowTooSmall() { + Period test = Period.of(Integer.MIN_VALUE, YEARS); + test.plus(-1, YEARS); + } + + //----------------------------------------------------------------------- + // plusMonths() + //----------------------------------------------------------------------- + public void test_plusMonths() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.plus(10, MONTHS), 1, 12, 3, 4, 5, 6, 7); + assertPeriod(test.plus(Period.of(10, MONTHS)), 1, 12, 3, 4, 5, 6, 7); + } + + public void test_plusMonths_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.plus(0, MONTHS), test); + assertEquals(test.plus(Period.of(0, MONTHS)), test); + } + + public void test_plusMonths_toZero() { + Period test = Period.of(-1, MONTHS); + assertSame(test.plus(1, MONTHS), Period.ZERO); + assertSame(test.plus(Period.of(1, MONTHS)), Period.ZERO); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_plusMonths_overflowTooBig() { + Period test = Period.of(Integer.MAX_VALUE, MONTHS); + test.plus(1, MONTHS); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_plusMonths_overflowTooSmall() { + Period test = Period.of(Integer.MIN_VALUE, MONTHS); + test.plus(-1, MONTHS); + } + + //----------------------------------------------------------------------- + // plusDays() + //----------------------------------------------------------------------- + public void test_plusDays() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.plus(10, DAYS), 1, 2, 13, 4, 5, 6, 7); + } + + public void test_plusDays_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.plus(0, DAYS), test); + } + + public void test_plusDays_toZero() { + Period test = Period.of(-1, DAYS); + assertSame(test.plus(1, DAYS), Period.ZERO); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_plusDays_overflowTooBig() { + Period test = Period.of(Integer.MAX_VALUE, DAYS); + test.plus(1, DAYS); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_plusDays_overflowTooSmall() { + Period test = Period.of(Integer.MIN_VALUE, DAYS); + test.plus(-1, DAYS); + } + + //----------------------------------------------------------------------- + // plusHours() + //----------------------------------------------------------------------- + public void test_plusHours() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.plus(10, HOURS), 1, 2, 3, 14, 5, 6, 7); + } + + public void test_plusHours_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.plus(0, HOURS), test); + } + + public void test_plusHours_toZero() { + Period test = Period.of(-1, HOURS); + assertSame(test.plus(1, HOURS), Period.ZERO); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_plusHours_overflowTooBig() { + Period test = Period.of(Integer.MAX_VALUE, HOURS); + test.plus(1, HOURS); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_plusHours_overflowTooSmall() { + Period test = Period.of(Integer.MIN_VALUE, HOURS); + test.plus(-1, HOURS); + } + + //----------------------------------------------------------------------- + // plusMinutes() + //----------------------------------------------------------------------- + public void test_plusMinutes() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.plus(10, MINUTES), 1, 2, 3, 4, 15, 6, 7); + } + + public void test_plusMinutes_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.plus(0, MINUTES), test); + } + + public void test_plusMinutes_toZero() { + Period test = Period.of(-1, MINUTES); + assertSame(test.plus(1, MINUTES), Period.ZERO); + } + + //----------------------------------------------------------------------- + // plusSeconds() + //----------------------------------------------------------------------- + public void test_plusSeconds() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.plus(10, SECONDS), 1, 2, 3, 4, 5, 16, 7); + } + + public void test_plusSeconds_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.plus(0, SECONDS), test); + } + + public void test_plusSeconds_toZero() { + Period test = Period.of(-1, SECONDS); + assertSame(test.plus(1, SECONDS), Period.ZERO); + } + + //----------------------------------------------------------------------- + // plusNanos() + //----------------------------------------------------------------------- + public void test_plusNanos() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.plus(10, NANOS), 1, 2, 3, 4, 5, 6, 17); + } + + public void test_plusNanos_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.plus(0, NANOS), test); + } + + public void test_plusNanos_toZero() { + Period test = Period.of(-1, NANOS); + assertSame(test.plus(1, NANOS), Period.ZERO); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_plusNanos_overflowTooBig() { + Period test = Period.of(Long.MAX_VALUE, NANOS); + test.plus(1, NANOS); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_plusNanos_overflowTooSmall() { + Period test = Period.of(Long.MIN_VALUE, NANOS); + test.plus(-1, NANOS); + } + + //----------------------------------------------------------------------- + // minusYears() + //----------------------------------------------------------------------- + public void test_minusYears() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.minus(10, YEARS), -9, 2, 3, 4, 5, 6, 7); + } + + public void test_minusYears_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.minus(0, YEARS), test); + } + + public void test_minusYears_toZero() { + Period test = Period.of(1, YEARS); + assertSame(test.minus(1, YEARS), Period.ZERO); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_minusYears_overflowTooBig() { + Period test = Period.of(Integer.MAX_VALUE, YEARS); + test.minus(-1, YEARS); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_minusYears_overflowTooSmall() { + Period test = Period.of(Integer.MIN_VALUE, YEARS); + test.minus(1, YEARS); + } + + //----------------------------------------------------------------------- + // minusMonths() + //----------------------------------------------------------------------- + public void test_minusMonths() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.minus(10, MONTHS), 1, -8, 3, 4, 5, 6, 7); + } + + public void test_minusMonths_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.minus(0, MONTHS), test); + } + + public void test_minusMonths_toZero() { + Period test = Period.of(1, MONTHS); + assertSame(test.minus(1, MONTHS), Period.ZERO); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_minusMonths_overflowTooBig() { + Period test = Period.of(Integer.MAX_VALUE, MONTHS); + test.minus(-1, MONTHS); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_minusMonths_overflowTooSmall() { + Period test = Period.of(Integer.MIN_VALUE, MONTHS); + test.minus(1, MONTHS); + } + + //----------------------------------------------------------------------- + // minusDays() + //----------------------------------------------------------------------- + public void test_minusDays() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.minus(10, DAYS), 1, 2, -7, 4, 5, 6, 7); + } + + public void test_minusDays_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.minus(0, DAYS), test); + } + + public void test_minusDays_toZero() { + Period test = Period.of(1, DAYS); + assertSame(test.minus(1, DAYS), Period.ZERO); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_minusDays_overflowTooBig() { + Period test = Period.of(Integer.MAX_VALUE, DAYS); + test.minus(-1, DAYS); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_minusDays_overflowTooSmall() { + Period test = Period.of(Integer.MIN_VALUE, DAYS); + test.minus(1, DAYS); + } + + //----------------------------------------------------------------------- + // minusHours() + //----------------------------------------------------------------------- + public void test_minusHours() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.minus(10, HOURS), 1, 2, 3, -5, -54, -53, -999999993); + assertEquals(test.minus(10, HOURS).plus(10, HOURS), test); + } + + public void test_minusHours_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.minus(0, HOURS), test); + } + + public void test_minusHours_toZero() { + Period test = Period.of(1, HOURS); + assertSame(test.minus(1, HOURS), Period.ZERO); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_minusHours_overflowTooBig() { + Period test = Period.of(Integer.MAX_VALUE, HOURS); + test.minus(-1, HOURS); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_minusHours_overflowTooSmall() { + Period test = Period.of(Integer.MIN_VALUE, HOURS); + test.minus(1, HOURS); + } + + //----------------------------------------------------------------------- + // minusMinutes() + //----------------------------------------------------------------------- + public void test_minusMinutes() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.minus(10, MINUTES), 1, 2, 3, 3, 55, 6, 7); + assertEquals(test.minus(10, MINUTES).plus(10, MINUTES), test); + } + + public void test_minusMinutes_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.minus(0, MINUTES), test); + } + + public void test_minusMinutes_toZero() { + Period test = Period.of(1, MINUTES); + assertSame(test.minus(1, MINUTES), Period.ZERO); + } + + //----------------------------------------------------------------------- + // minusSeconds() + //----------------------------------------------------------------------- + public void test_minusSeconds() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.minus(10, SECONDS), 1, 2, 3, 4, 4, 56, 7); + assertEquals(test.minus(10, SECONDS).plus(10, SECONDS), test); + } + + public void test_minusSeconds_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.minus(0, SECONDS), test); + } + + public void test_minusSeconds_toZero() { + Period test = Period.of(1, SECONDS); + assertSame(test.minus(1, SECONDS), Period.ZERO); + } + + //----------------------------------------------------------------------- + // minusNanos() + //----------------------------------------------------------------------- + public void test_minusNanos() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.minus(10, NANOS), 1, 2, 3, 4, 5, 5, 999999997); + } + + public void test_minusNanos_noChange() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.minus(0, NANOS), test); + } + + public void test_minusNanos_toZero() { + Period test = Period.of(1, NANOS); + assertSame(test.minus(1, NANOS), Period.ZERO); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_minusNanos_overflowTooBig() { + Period test = Period.of(Long.MAX_VALUE, NANOS); + test.minus(-1, NANOS); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_minusNanos_overflowTooSmall() { + Period test = Period.of(Long.MIN_VALUE, NANOS); + test.minus(1, NANOS); + } + + //----------------------------------------------------------------------- + // multipliedBy() + //----------------------------------------------------------------------- + public void test_multipliedBy() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.multipliedBy(2), 2, 4, 6, 8, 10, 12, 14); + assertPeriod(test.multipliedBy(-3), -3, -6, -9, -12, -15, -18, -21); + } + + public void test_multipliedBy_zeroBase() { + assertSame(Period.ZERO.multipliedBy(2), Period.ZERO); + } + + public void test_multipliedBy_zero() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.multipliedBy(0), Period.ZERO); + } + + public void test_multipliedBy_one() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertSame(test.multipliedBy(1), test); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_multipliedBy_overflowTooBig() { + Period test = Period.of(Integer.MAX_VALUE / 2 + 1, YEARS); + test.multipliedBy(2); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_multipliedBy_overflowTooSmall() { + Period test = Period.of(Integer.MIN_VALUE / 2 - 1, YEARS); + test.multipliedBy(2); + } + + //----------------------------------------------------------------------- + // negated() + //----------------------------------------------------------------------- + public void test_negated() { + Period test = Period.of(1, 2, 3, 4, 5, 6, 7); + assertPeriod(test.negated(), -1, -2, -3, -4, -5, -6, -7); + } + + public void test_negated_zero() { + assertSame(Period.ZERO.negated(), Period.ZERO); + } + + public void test_negated_max() { + assertPeriod(Period.of(Integer.MAX_VALUE, YEARS).negated(), -Integer.MAX_VALUE, 0, 0, 0, 0, 0, 0); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_negated_overflow() { + Period.of(Integer.MIN_VALUE, YEARS).negated(); + } + + //----------------------------------------------------------------------- + // normalizedHoursToDays() + //----------------------------------------------------------------------- + @DataProvider(name="normalizedHoursToDays") + Object[][] data_normalizedHoursToDays() { + return new Object[][] { + {0, 0, 0, 0}, + {1, 0, 1, 0}, + {-1, 0, -1, 0}, + + {1, 1, 1, 1}, + {1, 23, 1, 23}, + {1, 24, 2, 0}, + {1, 25, 2, 1}, + + {1, -1, 0, 23}, + {1, -23, 0, 1}, + {1, -24, 0, 0}, + {1, -25, 0, -1}, + {1, -47, 0, -23}, + {1, -48, -1, 0}, + {1, -49, -1, -1}, + + {-1, 1, 0, -23}, + {-1, 23, 0, -1}, + {-1, 24, 0, 0}, + {-1, 25, 0, 1}, + {-1, 47, 0, 23}, + {-1, 48, 1, 0}, + {-1, 49, 1, 1}, + + {-1, -1, -1, -1}, + {-1, -23, -1, -23}, + {-1, -24, -2, 0}, + {-1, -25, -2, -1}, + }; + } + + @Test(dataProvider="normalizedHoursToDays") + public void test_normalizedHoursToDays(int inputDays, int inputHours, int expectedDays, int expectedHours) { + assertPeriod(Period.of(0, 0, inputDays, inputHours, 0, 0, 0).normalizedHoursToDays(), 0, 0, expectedDays, expectedHours, 0, 0, 0); + } + + @Test(dataProvider="normalizedHoursToDays") + public void test_normalizedHoursToDays_yearsMonthsUnaffected(int inputDays, int inputHours, int expectedDays, int expectedHours) { + assertPeriod(Period.of(12, 6, inputDays, inputHours, 0, 0, 0).normalizedHoursToDays(), 12, 6, expectedDays, expectedHours, 0, 0, 0); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_normalizedHoursToDays_min() { + Period base = Period.of(0, 0, Integer.MIN_VALUE, -24, 0, 0, 0); + base.normalizedHoursToDays(); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_normalizedHoursToDays_max() { + Period base = Period.of(0, 0, Integer.MAX_VALUE, 24, 0, 0, 0); + base.normalizedHoursToDays(); + } + + //----------------------------------------------------------------------- + // normalizedDaysToHours() + //----------------------------------------------------------------------- + @DataProvider(name="normalizedDaysToHours") + Object[][] data_normalizedDaysToHours() { + return new Object[][] { + {0, 0, 0, 0, 0}, + + {1, 0, 0, 24, 0}, + {1, 1, 0, 25, 0}, + {1, 23, 0, 47, 0}, + {1, 24, 0, 48, 0}, + {1, 25, 0, 49, 0}, + {2, 23, 0, 71, 0}, + {2, 24, 0, 72, 0}, + {2, 25, 0, 73, 0}, + + {1, 0, 0, 24, 0}, + {1, -1, 0, 23, 0}, + {1, -23, 0, 1, 0}, + {1, -24, 0, 0, 0}, + {1, -25, 0, -1, 0}, + {2, -23, 0, 25, 0}, + {2, -24, 0, 24, 0}, + {2, -25, 0, 23, 0}, + + {-1, 0, 0, -24, 0}, + {-1, 1, 0, -23, 0}, + {-1, 23, 0, -1, 0}, + {-1, 24, 0, 0, 0}, + {-1, 25, 0, 1, 0}, + {-2, 23, 0, -25, 0}, + {-2, 24, 0, -24, 0}, + {-2, 25, 0, -23, 0}, + + {-1, 0, 0, -24, 0}, + {-1, -1, 0, -25, 0}, + {-1, -23, 0, -47, 0}, + {-1, -24, 0, -48, 0}, + {-1, -25, 0, -49, 0}, + + // minutes + {1, -1, -1, 22, 59}, + {1, -23, -1, 0, 59}, + {1, -24, -1, 0, -1}, + {1, -25, -1, -1, -1}, + {-1, 1, 1, -22, -59}, + {-1, 23, 1, 0, -59}, + {-1, 24, 1, 0, 1}, + {-1, 25, 1, 1, 1}, + }; + } + + @Test(dataProvider="normalizedDaysToHours") + public void test_normalizedDaysToHours(int inputDays, int inputHours, int inputMinutes, int expectedHours, int expectedMinutes) { + assertPeriod(Period.of(0, 0, inputDays, inputHours, inputMinutes, 0).normalizedDaysToHours(), 0, 0, 0, expectedHours, expectedMinutes, 0, 0); + } + + @Test(dataProvider="normalizedDaysToHours") + public void test_normalizedDaysToHours_yearsMonthsUnaffected(int inputDays, int inputHours, int inputMinutes, int expectedHours, int expectedMinutes) { + assertPeriod(Period.of(12, 6, inputDays, inputHours, inputMinutes, 0).normalizedDaysToHours(), 12, 6, 0, expectedHours, expectedMinutes, 0, 0); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_normalizedDaysToHours_min() { + Period base = Period.of(0, 0, -1_000, -10_000_000, 0, 0, 0); + base.normalizedDaysToHours(); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_normalizedDaysToHours_max() { + Period base = Period.of(0, 0, 1_000, 10_000_000, 0, 0, 0); + base.normalizedDaysToHours(); + } + + //----------------------------------------------------------------------- + // normalizedMonthsISO() + //----------------------------------------------------------------------- + @DataProvider(name="normalizedMonthsISO") + Object[][] data_normalizedMonthsISO() { + return new Object[][] { + {0, 0, 0, 0}, + {1, 0, 1, 0}, + {-1, 0, -1, 0}, + + {1, 1, 1, 1}, + {1, 2, 1, 2}, + {1, 11, 1, 11}, + {1, 12, 2, 0}, + {1, 13, 2, 1}, + {1, 23, 2, 11}, + {1, 24, 3, 0}, + {1, 25, 3, 1}, + + {1, -1, 0, 11}, + {1, -2, 0, 10}, + {1, -11, 0, 1}, + {1, -12, 0, 0}, + {1, -13, 0, -1}, + {1, -23, 0, -11}, + {1, -24, -1, 0}, + {1, -25, -1, -1}, + {1, -35, -1, -11}, + {1, -36, -2, 0}, + {1, -37, -2, -1}, + + {-1, 1, 0, -11}, + {-1, 11, 0, -1}, + {-1, 12, 0, 0}, + {-1, 13, 0, 1}, + {-1, 23, 0, 11}, + {-1, 24, 1, 0}, + {-1, 25, 1, 1}, + + {-1, -1, -1, -1}, + {-1, -11, -1, -11}, + {-1, -12, -2, 0}, + {-1, -13, -2, -1}, + }; + } + + @Test(dataProvider="normalizedMonthsISO") + public void test_normalizedMonthsISO(int inputYears, int inputMonths, int expectedYears, int expectedMonths) { + assertPeriod(Period.ofDate(inputYears, inputMonths, 0).normalizedMonthsISO(), expectedYears, expectedMonths, 0, 0, 0, 0, 0); + } + + @Test(dataProvider="normalizedMonthsISO") + public void test_normalizedMonthsISO_daysTimeUnaffected(int inputYears, int inputMonths, int expectedYears, int expectedMonths) { + assertPeriod(Period.of(inputYears, inputMonths, 5, 12, 30, 0, 0).normalizedMonthsISO(), expectedYears, expectedMonths, 5, 12, 30, 0, 0); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_normalizedMonthsISO_min() { + Period base = Period.ofDate(Integer.MIN_VALUE, -12, 0); + base.normalizedMonthsISO(); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void test_normalizedMonthsISO_max() { + Period base = Period.ofDate(Integer.MAX_VALUE, 12, 0); + base.normalizedMonthsISO(); + } + + //----------------------------------------------------------------------- + // addTo() + //----------------------------------------------------------------------- + @DataProvider(name="addTo") + Object[][] data_addTo() { + return new Object[][] { + {pymd(0, 0, 0), date(2012, 6, 30), date(2012, 6, 30)}, + + {pymd(1, 0, 0), date(2012, 6, 10), date(2013, 6, 10)}, + {pymd(0, 1, 0), date(2012, 6, 10), date(2012, 7, 10)}, + {pymd(0, 0, 1), date(2012, 6, 10), date(2012, 6, 11)}, + + {pymd(-1, 0, 0), date(2012, 6, 10), date(2011, 6, 10)}, + {pymd(0, -1, 0), date(2012, 6, 10), date(2012, 5, 10)}, + {pymd(0, 0, -1), date(2012, 6, 10), date(2012, 6, 9)}, + + {pymd(1, 2, 3), date(2012, 6, 27), date(2013, 8, 30)}, + {pymd(1, 2, 3), date(2012, 6, 28), date(2013, 8, 31)}, + {pymd(1, 2, 3), date(2012, 6, 29), date(2013, 9, 1)}, + {pymd(1, 2, 3), date(2012, 6, 30), date(2013, 9, 2)}, + {pymd(1, 2, 3), date(2012, 7, 1), date(2013, 9, 4)}, + + {pymd(1, 0, 0), date(2011, 2, 28), date(2012, 2, 28)}, + {pymd(4, 0, 0), date(2011, 2, 28), date(2015, 2, 28)}, + {pymd(1, 0, 0), date(2012, 2, 29), date(2013, 2, 28)}, + {pymd(4, 0, 0), date(2012, 2, 29), date(2016, 2, 29)}, + + {pymd(1, 1, 0), date(2011, 1, 29), date(2012, 2, 29)}, + {pymd(1, 2, 0), date(2012, 2, 29), date(2013, 4, 29)}, + }; + } + + @Test(dataProvider="addTo") + public void test_addTo(Period period, LocalDate baseDate, LocalDate expected) { + assertEquals(period.addTo(baseDate), expected); + } + + @Test(dataProvider="addTo") + public void test_addTo_usingLocalDatePlus(Period period, LocalDate baseDate, LocalDate expected) { + assertEquals(baseDate.plus(period), expected); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_addTo_nullZero() { + Period.ZERO.addTo(null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_addTo_nullNonZero() { + Period.of(2, DAYS).addTo(null); + } + + //----------------------------------------------------------------------- + // subtractFrom() + //----------------------------------------------------------------------- + @DataProvider(name="subtractFrom") + Object[][] data_subtractFrom() { + return new Object[][] { + {pymd(0, 0, 0), date(2012, 6, 30), date(2012, 6, 30)}, + + {pymd(1, 0, 0), date(2012, 6, 10), date(2011, 6, 10)}, + {pymd(0, 1, 0), date(2012, 6, 10), date(2012, 5, 10)}, + {pymd(0, 0, 1), date(2012, 6, 10), date(2012, 6, 9)}, + + {pymd(-1, 0, 0), date(2012, 6, 10), date(2013, 6, 10)}, + {pymd(0, -1, 0), date(2012, 6, 10), date(2012, 7, 10)}, + {pymd(0, 0, -1), date(2012, 6, 10), date(2012, 6, 11)}, + + {pymd(1, 2, 3), date(2012, 8, 30), date(2011, 6, 27)}, + {pymd(1, 2, 3), date(2012, 8, 31), date(2011, 6, 27)}, + {pymd(1, 2, 3), date(2012, 9, 1), date(2011, 6, 28)}, + {pymd(1, 2, 3), date(2012, 9, 2), date(2011, 6, 29)}, + {pymd(1, 2, 3), date(2012, 9, 3), date(2011, 6, 30)}, + {pymd(1, 2, 3), date(2012, 9, 4), date(2011, 7, 1)}, + + {pymd(1, 0, 0), date(2011, 2, 28), date(2010, 2, 28)}, + {pymd(4, 0, 0), date(2011, 2, 28), date(2007, 2, 28)}, + {pymd(1, 0, 0), date(2012, 2, 29), date(2011, 2, 28)}, + {pymd(4, 0, 0), date(2012, 2, 29), date(2008, 2, 29)}, + + {pymd(1, 1, 0), date(2013, 3, 29), date(2012, 2, 29)}, + {pymd(1, 2, 0), date(2012, 2, 29), date(2010, 12, 29)}, + }; + } + + @Test(dataProvider="subtractFrom") + public void test_subtractFrom(Period period, LocalDate baseDate, LocalDate expected) { + assertEquals(period.subtractFrom(baseDate), expected); + } + + @Test(dataProvider="subtractFrom") + public void test_subtractFrom_usingLocalDateMinus(Period period, LocalDate baseDate, LocalDate expected) { + assertEquals(baseDate.minus(period), expected); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_subtractFrom_nullZero() { + Period.ZERO.subtractFrom(null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_subtractFrom_nullNonZero() { + Period.of(2, DAYS).subtractFrom(null); + } + + //----------------------------------------------------------------------- + // toDuration() + //----------------------------------------------------------------------- + public void test_toDuration() { + assertEquals(Period.ZERO.toDuration(), Duration.of(0, SECONDS)); + assertEquals(Period.of(0, 0, 0, 4, 5, 6, 7).toDuration(), Duration.ofSeconds((4 * 60 + 5) * 60L + 6, 7)); + } + + public void test_toDuration_calculation() { + assertEquals(Period.of(0, 0, 0, 2, 0, 0, 0).toDuration(), Duration.ofSeconds(2 * 3600)); + assertEquals(Period.of(0, 0, 0, 0, 2, 0, 0).toDuration(), Duration.of(120, SECONDS)); + assertEquals(Period.of(0, 0, 0, 0, 0, 2, 0).toDuration(), Duration.of(2, SECONDS)); + + assertEquals(Period.of(0, 0, 0, 0, 0, 3, 1000000000L - 1).toDuration(), Duration.ofSeconds(3, 999999999)); + assertEquals(Period.of(0, 0, 0, 0, 0, 3, 1000000000L).toDuration(), Duration.ofSeconds(4, 0)); + } + + public void test_toDuration_negatives() { + assertEquals(Period.of(0, 0, 0, 0, 0, 2, 1).toDuration(), Duration.ofSeconds(2, 1)); + assertEquals(Period.of(0, 0, 0, 0, 0, 2, -1).toDuration(), Duration.ofSeconds(1, 999999999)); + assertEquals(Period.of(0, 0, 0, 0, 0, -2, 1).toDuration(), Duration.ofSeconds(-2, 1)); + assertEquals(Period.of(0, 0, 0, 0, 0, -2, -1).toDuration(), Duration.ofSeconds(-3, 999999999)); + } + + @Test(expectedExceptions=DateTimeException.class) + public void test_toDuration_years() { + Period.of(1, 0, 0, 4, 5, 6, 7).toDuration(); + } + + @Test(expectedExceptions=DateTimeException.class) + public void test_toDuration_months() { + Period.of(0, 1, 0, 4, 5, 6, 7).toDuration(); + } + + @Test(expectedExceptions=DateTimeException.class) + public void test_toDuration_days() { + Duration test = Period.of(0, 0, 1, 4, 5, 6, 7).toDuration(); + assertEquals(test, Duration.ofSeconds(101106, 7L)); + } + + //----------------------------------------------------------------------- + // equals() / hashCode() + //----------------------------------------------------------------------- + public void test_equals() { + assertEquals(Period.of(1, 0, 0, 0, 0, 0).equals(Period.of(1, YEARS)), true); + assertEquals(Period.of(0, 1, 0, 0, 0, 0).equals(Period.of(1, MONTHS)), true); + assertEquals(Period.of(0, 0, 1, 0, 0, 0).equals(Period.of(1, DAYS)), true); + assertEquals(Period.of(0, 0, 0, 1, 0, 0).equals(Period.of(1, HOURS)), true); + assertEquals(Period.of(0, 0, 0, 0, 1, 0).equals(Period.of(1, MINUTES)), true); + assertEquals(Period.of(0, 0, 0, 0, 0, 1).equals(Period.of(1, SECONDS)), true); + assertEquals(Period.of(1, 2, 3, 0, 0, 0).equals(Period.ofDate(1, 2, 3)), true); + assertEquals(Period.of(0, 0, 0, 1, 2, 3).equals(Period.ofTime(1, 2, 3)), true); + assertEquals(Period.of(1, 2, 3, 4, 5, 6).equals(Period.of(1, 2, 3, 4, 5, 6)), true); + + assertEquals(Period.of(1, YEARS).equals(Period.of(1, YEARS)), true); + assertEquals(Period.of(1, YEARS).equals(Period.of(2, YEARS)), false); + + assertEquals(Period.of(1, MONTHS).equals(Period.of(1, MONTHS)), true); + assertEquals(Period.of(1, MONTHS).equals(Period.of(2, MONTHS)), false); + + assertEquals(Period.of(1, DAYS).equals(Period.of(1, DAYS)), true); + assertEquals(Period.of(1, DAYS).equals(Period.of(2, DAYS)), false); + + assertEquals(Period.of(1, HOURS).equals(Period.of(1, HOURS)), true); + assertEquals(Period.of(1, HOURS).equals(Period.of(2, HOURS)), false); + + assertEquals(Period.of(1, MINUTES).equals(Period.of(1, MINUTES)), true); + assertEquals(Period.of(1, MINUTES).equals(Period.of(2, MINUTES)), false); + + assertEquals(Period.of(1, SECONDS).equals(Period.of(1, SECONDS)), true); + assertEquals(Period.of(1, SECONDS).equals(Period.of(2, SECONDS)), false); + + assertEquals(Period.ofDate(1, 2, 3).equals(Period.ofDate(1, 2, 3)), true); + assertEquals(Period.ofDate(1, 2, 3).equals(Period.ofDate(0, 2, 3)), false); + assertEquals(Period.ofDate(1, 2, 3).equals(Period.ofDate(1, 0, 3)), false); + assertEquals(Period.ofDate(1, 2, 3).equals(Period.ofDate(1, 2, 0)), false); + + assertEquals(Period.ofTime(1, 2, 3).equals(Period.ofTime(1, 2, 3)), true); + assertEquals(Period.ofTime(1, 2, 3).equals(Period.ofTime(0, 2, 3)), false); + assertEquals(Period.ofTime(1, 2, 3).equals(Period.ofTime(1, 0, 3)), false); + assertEquals(Period.ofTime(1, 2, 3).equals(Period.ofTime(1, 2, 0)), false); + } + + public void test_equals_self() { + Period test = Period.of(1, 2, 3, 4, 5, 6); + assertEquals(test.equals(test), true); + } + + public void test_equals_null() { + Period test = Period.of(1, 2, 3, 4, 5, 6); + assertEquals(test.equals(null), false); + } + + public void test_equals_otherClass() { + Period test = Period.of(1, 2, 3, 4, 5, 6); + assertEquals(test.equals(""), false); + } + + //----------------------------------------------------------------------- + public void test_hashCode() { + Period test5 = Period.of(5, DAYS); + Period test6 = Period.of(6, DAYS); + Period test5M = Period.of(5, MONTHS); + Period test5Y = Period.of(5, YEARS); + assertEquals(test5.hashCode() == test5.hashCode(), true); + assertEquals(test5.hashCode() == test6.hashCode(), false); + assertEquals(test5.hashCode() == test5M.hashCode(), false); + assertEquals(test5.hashCode() == test5Y.hashCode(), false); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="toStringAndParse") + Object[][] data_toString() { + return new Object[][] { + {Period.ZERO, "PT0S"}, + {Period.of(0, DAYS), "PT0S"}, + {Period.of(1, YEARS), "P1Y"}, + {Period.of(1, MONTHS), "P1M"}, + {Period.of(1, DAYS), "P1D"}, + {Period.of(1, HOURS), "PT1H"}, + {Period.of(1, MINUTES), "PT1M"}, + {Period.of(1, SECONDS), "PT1S"}, + {Period.of(12, SECONDS), "PT12S"}, + {Period.of(123, SECONDS), "PT2M3S"}, + {Period.of(1234, SECONDS), "PT20M34S"}, + {Period.of(-1, SECONDS), "PT-1S"}, + {Period.of(-12, SECONDS), "PT-12S"}, + {Period.of(-123, SECONDS), "PT-2M-3S"}, + {Period.of(-1234, SECONDS), "PT-20M-34S"}, + {Period.of(1, 2, 3, 4, 5, 6), "P1Y2M3DT4H5M6S"}, + {Period.of(1, 2, 3, 4, 5, 6, 700000000), "P1Y2M3DT4H5M6.7S"}, + {Period.of(0, 0, 0, 0, 0, 0, 100000000), "PT0.1S"}, + {Period.of(0, 0, 0, 0, 0, 0, -100000000), "PT-0.1S"}, + {Period.of(0, 0, 0, 0, 0, 1, -900000000), "PT0.1S"}, + {Period.of(0, 0, 0, 0, 0, -1, 900000000), "PT-0.1S"}, + {Period.of(0, 0, 0, 0, 0, 1, 100000000), "PT1.1S"}, + {Period.of(0, 0, 0, 0, 0, 1, -100000000), "PT0.9S"}, + {Period.of(0, 0, 0, 0, 0, -1, 100000000), "PT-0.9S"}, + {Period.of(0, 0, 0, 0, 0, -1, -100000000), "PT-1.1S"}, + {Period.of(0, 0, 0, 0, 0, 0, 10000000), "PT0.01S"}, + {Period.of(0, 0, 0, 0, 0, 0, -10000000), "PT-0.01S"}, + {Period.of(0, 0, 0, 0, 0, 0, 1000000), "PT0.001S"}, + {Period.of(0, 0, 0, 0, 0, 0, -1000000), "PT-0.001S"}, + {Period.of(0, 0, 0, 0, 0, 0, 1000), "PT0.000001S"}, + {Period.of(0, 0, 0, 0, 0, 0, -1000), "PT-0.000001S"}, + {Period.of(0, 0, 0, 0, 0, 0, 1), "PT0.000000001S"}, + {Period.of(0, 0, 0, 0, 0, 0, -1), "PT-0.000000001S"}, + }; + } + + @Test(groups="tck", dataProvider="toStringAndParse") + public void test_toString(Period input, String expected) { + assertEquals(input.toString(), expected); + } + + //----------------------------------------------------------------------- + private void assertPeriod(Period test, int y, int mo, int d, int h, int mn, int s, long n) { + assertEquals(test.getYears(), y, "years"); + assertEquals(test.getMonths(), mo, "months"); + assertEquals(test.getDays(), d, "days"); + assertEquals(test.getHours(), h, "hours"); + assertEquals(test.getMinutes(), mn, "mins"); + assertEquals(test.getSeconds(), s, "secs"); + assertEquals(test.getNanos(), n, "nanos"); + assertEquals(test.getTimeNanos(), (((h * 60L + mn) * 60 + s) * 1_000_000_000L + n), "total nanos"); + } + + private static Period pymd(int y, int m, int d) { + return Period.ofDate(y, m, d); + } + + private static LocalDate date(int y, int m, int d) { + return LocalDate.of(y, m, d); + } + +} diff --git a/test/java/time/test/java/time/TestPeriodParser.java b/test/java/time/test/java/time/TestPeriodParser.java new file mode 100644 index 0000000000000000000000000000000000000000..1d3a8abe37363cafeeb92e9ff695e61da02b8f76 --- /dev/null +++ b/test/java/time/test/java/time/TestPeriodParser.java @@ -0,0 +1,281 @@ +/* + * 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. + * + * 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 test.java.time; + +import java.time.*; + +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static java.time.temporal.ChronoUnit.YEARS; +import static org.testng.Assert.assertEquals; + +import java.time.format.DateTimeParseException; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test PeriodParser. + */ +@Test +public class TestPeriodParser { + + //----------------------------------------------------------------------- + // parse(String) + //----------------------------------------------------------------------- + @DataProvider(name="Parse") + Object[][] provider_factory_parse() { + return new Object[][] { + {"Pt0S", Period.ZERO}, + {"pT0S", Period.ZERO}, + {"PT0S", Period.ZERO}, + {"Pt0s", Period.ZERO}, + {"pt0s", Period.ZERO}, + {"P0Y0M0DT0H0M0.0S", Period.ZERO}, + + {"P1Y", Period.of(1, YEARS)}, + {"P100Y", Period.of(100, YEARS)}, + {"P-25Y", Period.of(-25, YEARS)}, + {"P" + Integer.MAX_VALUE + "Y", Period.of(Integer.MAX_VALUE, YEARS)}, + {"P" + Integer.MIN_VALUE + "Y", Period.of(Integer.MIN_VALUE, YEARS)}, + + {"P1M", Period.of(1, MONTHS)}, + {"P0M", Period.of(0, MONTHS)}, + {"P-1M", Period.of(-1, MONTHS)}, + {"P" + Integer.MAX_VALUE + "M", Period.of(Integer.MAX_VALUE, MONTHS)}, + {"P" + Integer.MIN_VALUE + "M", Period.of(Integer.MIN_VALUE, MONTHS)}, + + {"P1D", Period.of(1, DAYS)}, + {"P0D", Period.of(0, DAYS)}, + {"P-1D", Period.of(-1, DAYS)}, + {"P" + Integer.MAX_VALUE + "D", Period.of(Integer.MAX_VALUE, DAYS)}, + {"P" + Integer.MIN_VALUE + "D", Period.of(Integer.MIN_VALUE, DAYS)}, + + {"P2Y3M25D", Period.ofDate(2, 3, 25)}, + + {"PT1H", Period.of(1, HOURS)}, + {"PT-1H", Period.of(-1, HOURS)}, + {"PT24H", Period.of(24, HOURS)}, + {"PT-24H", Period.of(-24, HOURS)}, + {"PT" + Integer.MAX_VALUE / (3600 * 8) + "H", Period.of(Integer.MAX_VALUE / (3600 * 8), HOURS)}, + {"PT" + Integer.MIN_VALUE / (3600 * 8) + "H", Period.of(Integer.MIN_VALUE / (3600 * 8), HOURS)}, + + {"PT1M", Period.of(1, MINUTES)}, + {"PT-1M", Period.of(-1, MINUTES)}, + {"PT60M", Period.of(60, MINUTES)}, + {"PT-60M", Period.of(-60, MINUTES)}, + {"PT" + Integer.MAX_VALUE / (60 * 8) + "M", Period.of(Integer.MAX_VALUE / (60 * 8), MINUTES)}, + {"PT" + Integer.MIN_VALUE / (60 * 8) + "M", Period.of(Integer.MIN_VALUE / (60 * 8), MINUTES)}, + + {"PT1S", Period.of(1, SECONDS)}, + {"PT-1S", Period.of(-1, SECONDS)}, + {"PT60S", Period.of(60, SECONDS)}, + {"PT-60S", Period.of(-60, SECONDS)}, + {"PT" + Integer.MAX_VALUE + "S", Period.of(Integer.MAX_VALUE, SECONDS)}, + {"PT" + Integer.MIN_VALUE + "S", Period.of(Integer.MIN_VALUE, SECONDS)}, + + {"PT0.1S", Period.of( 0, 0, 0, 0, 0, 0, 100000000 ) }, + {"PT-0.1S", Period.of( 0, 0, 0, 0, 0, 0, -100000000 ) }, + {"PT1.1S", Period.of( 0, 0, 0, 0, 0, 1, 100000000 ) }, + {"PT-1.1S", Period.of( 0, 0, 0, 0, 0, -1, -100000000 ) }, + {"PT1.0001S", Period.of(1, SECONDS).plus( 100000, NANOS ) }, + {"PT1.0000001S", Period.of(1, SECONDS).plus( 100, NANOS ) }, + {"PT1.123456789S", Period.of( 0, 0, 0, 0, 0, 1, 123456789 ) }, + {"PT1.999999999S", Period.of( 0, 0, 0, 0, 0, 1, 999999999 ) }, + + }; + } + + @Test(dataProvider="Parse") + public void factory_parse(String text, Period expected) { + Period p = Period.parse(text); + assertEquals(p, expected); + } + + @Test(dataProvider="Parse") + public void factory_parse_comma(String text, Period expected) { + if (text.contains(".")) { + text = text.replace('.', ','); + Period p = Period.parse(text); + assertEquals(p, expected); + } + } + + @DataProvider(name="ParseFailures") + Object[][] provider_factory_parseFailures() { + return new Object[][] { + {"", 0}, + {"PTS", 2}, + {"AT0S", 0}, + {"PA0S", 1}, + {"PT0A", 3}, + + {"PT+S", 2}, + {"PT-S", 2}, + {"PT.S", 2}, + {"PTAS", 2}, + + {"PT+0S", 2}, + {"PT-0S", 2}, + {"PT+1S", 2}, + {"PT-.S", 2}, + + {"PT1ABC2S", 3}, + {"PT1.1ABC2S", 5}, + + {"PT123456789123456789123456789S", 2}, + {"PT0.1234567891S", 4}, + {"PT1.S", 2}, + {"PT.1S", 2}, + + {"PT2.-3S", 2}, + {"PT-2.-3S", 2}, + + {"P1Y1MT1DT1M1S", 7}, + {"P1Y1MT1HT1M1S", 8}, + {"P1YMD", 3}, + {"PT1ST1D", 4}, + {"P1Y2Y", 4}, + {"PT1M+3S", 4}, + + {"PT1S1", 4}, + {"PT1S.", 4}, + {"PT1SA", 4}, + {"PT1M1", 4}, + {"PT1M.", 4}, + {"PT1MA", 4}, + }; + } + + @Test(dataProvider="ParseFailures", expectedExceptions=DateTimeParseException.class) + public void factory_parseFailures(String text, int errPos) { + try { + Period.parse(text); + } catch (DateTimeParseException ex) { + assertEquals(ex.getParsedString(), text); + assertEquals(ex.getErrorIndex(), errPos); + throw ex; + } + } + + @Test(dataProvider="ParseFailures", expectedExceptions=DateTimeParseException.class) + public void factory_parseFailures_comma(String text, int errPos) { + text = text.replace('.', ','); + try { + Period.parse(text); + } catch (DateTimeParseException ex) { + assertEquals(ex.getParsedString(), text); + assertEquals(ex.getErrorIndex(), errPos); + throw ex; + } + } + + @Test(expectedExceptions=DateTimeParseException.class) + public void factory_parse_tooBig() { + String text = "PT" + Long.MAX_VALUE + "1S"; + Period.parse(text); + } + + @Test(expectedExceptions=DateTimeParseException.class) + public void factory_parse_tooBig_decimal() { + String text = "PT" + Long.MAX_VALUE + "1.1S"; + Period.parse(text); + } + + @Test(expectedExceptions=DateTimeParseException.class) + public void factory_parse_tooSmall() { + String text = "PT" + Long.MIN_VALUE + "1S"; + Period.parse(text); + } + + @Test(expectedExceptions=DateTimeParseException.class) + public void factory_parse_tooSmall_decimal() { + String text = "PT" + Long.MIN_VALUE + ".1S"; + Period.parse(text); + } + + @Test(expectedExceptions=NullPointerException.class) + public void factory_parse_null() { + Period.parse(null); + } + + @DataProvider(name="ParseSequenceFailures") + Object[][] provider_factory_parseSequenceFailures() { + return new Object[][] { + {"P0M0Y0DT0H0M0.0S"}, + {"P0M0D0YT0H0M0.0S"}, + {"P0S0D0YT0S0M0.0H"}, + {"PT0M0H0.0S"}, + {"PT0M0H"}, + {"PT0S0M"}, + {"PT0.0M2S"}, + }; + } + + @Test(dataProvider="ParseSequenceFailures", expectedExceptions=DateTimeParseException.class) + public void factory_parse_badSequence(String text) { + Period.parse(text); + } + +} diff --git a/test/java/time/test/java/time/TestZoneId.java b/test/java/time/test/java/time/TestZoneId.java new file mode 100644 index 0000000000000000000000000000000000000000..5e9380391df23f3e7c5282700a806043e5b49542 --- /dev/null +++ b/test/java/time/test/java/time/TestZoneId.java @@ -0,0 +1,1084 @@ +/* + * 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. + * + * 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 test.java.time; + +import java.time.*; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.SimpleTimeZone; +import java.util.TimeZone; + +import java.time.temporal.Queries; +import java.time.temporal.TemporalQuery; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; +import java.time.format.TextStyle; +import java.time.zone.ZoneOffsetTransition; +import java.time.zone.ZoneRules; +import java.time.zone.ZoneRulesException; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test ZoneId. + */ +@Test +public class TestZoneId extends AbstractTest { + + private static final ZoneId ZONE_PARIS = ZoneId.of("Europe/Paris"); + public static final String LATEST_TZDB = "2010i"; + private static final int OVERLAP = 2; + private static final int GAP = 0; + + //----------------------------------------------------------------------- + // Basics + //----------------------------------------------------------------------- + public void test_immutable() { + Class cls = ZoneId.class; + assertTrue(Modifier.isPublic(cls.getModifiers())); + Field[] fields = cls.getDeclaredFields(); + for (Field field : fields) { + if (Modifier.isStatic(field.getModifiers()) == false) { + assertTrue(Modifier.isPrivate(field.getModifiers())); + assertTrue(Modifier.isFinal(field.getModifiers()) || + (Modifier.isVolatile(field.getModifiers()) && Modifier.isTransient(field.getModifiers()))); + } + } + } + + public void test_serialization_UTC() throws Exception { + ZoneId test = ZoneOffset.UTC; + assertSerializableAndSame(test); + } + + public void test_serialization_fixed() throws Exception { + ZoneId test = ZoneId.of("UTC+01:30"); + assertSerializable(test); + } + + public void test_serialization_Europe() throws Exception { + ZoneId test = ZoneId.of("Europe/London"); + assertSerializable(test); + } + + public void test_serialization_America() throws Exception { + ZoneId test = ZoneId.of("America/Chicago"); + assertSerializable(test); + } + + //----------------------------------------------------------------------- + // UTC + //----------------------------------------------------------------------- + public void test_constant_UTC() { + ZoneId test = ZoneOffset.UTC; + assertEquals(test.getId(), "Z"); + assertEquals(test.getText(TextStyle.FULL, Locale.UK), "Z"); + assertEquals(test.getRules().isFixedOffset(), true); + assertEquals(test.getRules().getOffset(Instant.ofEpochSecond(0L)), ZoneOffset.UTC); + checkOffset(test.getRules(), createLDT(2008, 6, 30), ZoneOffset.UTC, 1); + assertSame(test, ZoneId.of("UTC+00")); + } + + //----------------------------------------------------------------------- + // OLD_IDS_PRE_2005 + //----------------------------------------------------------------------- + public void test_constant_OLD_IDS_PRE_2005() { + Map ids = ZoneId.OLD_IDS_PRE_2005; + assertEquals(ids.get("EST"), "America/Indianapolis"); + assertEquals(ids.get("MST"), "America/Phoenix"); + assertEquals(ids.get("HST"), "Pacific/Honolulu"); + assertEquals(ids.get("ACT"), "Australia/Darwin"); + assertEquals(ids.get("AET"), "Australia/Sydney"); + assertEquals(ids.get("AGT"), "America/Argentina/Buenos_Aires"); + assertEquals(ids.get("ART"), "Africa/Cairo"); + assertEquals(ids.get("AST"), "America/Anchorage"); + assertEquals(ids.get("BET"), "America/Sao_Paulo"); + assertEquals(ids.get("BST"), "Asia/Dhaka"); + assertEquals(ids.get("CAT"), "Africa/Harare"); + assertEquals(ids.get("CNT"), "America/St_Johns"); + assertEquals(ids.get("CST"), "America/Chicago"); + assertEquals(ids.get("CTT"), "Asia/Shanghai"); + assertEquals(ids.get("EAT"), "Africa/Addis_Ababa"); + assertEquals(ids.get("ECT"), "Europe/Paris"); + assertEquals(ids.get("IET"), "America/Indiana/Indianapolis"); + assertEquals(ids.get("IST"), "Asia/Kolkata"); + assertEquals(ids.get("JST"), "Asia/Tokyo"); + assertEquals(ids.get("MIT"), "Pacific/Apia"); + assertEquals(ids.get("NET"), "Asia/Yerevan"); + assertEquals(ids.get("NST"), "Pacific/Auckland"); + assertEquals(ids.get("PLT"), "Asia/Karachi"); + assertEquals(ids.get("PNT"), "America/Phoenix"); + assertEquals(ids.get("PRT"), "America/Puerto_Rico"); + assertEquals(ids.get("PST"), "America/Los_Angeles"); + assertEquals(ids.get("SST"), "Pacific/Guadalcanal"); + assertEquals(ids.get("VST"), "Asia/Ho_Chi_Minh"); + } + + @Test(expectedExceptions=UnsupportedOperationException.class) + public void test_constant_OLD_IDS_PRE_2005_immutable() { + Map ids = ZoneId.OLD_IDS_PRE_2005; + ids.clear(); + } + + //----------------------------------------------------------------------- + // OLD_IDS_POST_2005 + //----------------------------------------------------------------------- + public void test_constant_OLD_IDS_POST_2005() { + Map ids = ZoneId.OLD_IDS_POST_2005; + assertEquals(ids.get("EST"), "-05:00"); + assertEquals(ids.get("MST"), "-07:00"); + assertEquals(ids.get("HST"), "-10:00"); + assertEquals(ids.get("ACT"), "Australia/Darwin"); + assertEquals(ids.get("AET"), "Australia/Sydney"); + assertEquals(ids.get("AGT"), "America/Argentina/Buenos_Aires"); + assertEquals(ids.get("ART"), "Africa/Cairo"); + assertEquals(ids.get("AST"), "America/Anchorage"); + assertEquals(ids.get("BET"), "America/Sao_Paulo"); + assertEquals(ids.get("BST"), "Asia/Dhaka"); + assertEquals(ids.get("CAT"), "Africa/Harare"); + assertEquals(ids.get("CNT"), "America/St_Johns"); + assertEquals(ids.get("CST"), "America/Chicago"); + assertEquals(ids.get("CTT"), "Asia/Shanghai"); + assertEquals(ids.get("EAT"), "Africa/Addis_Ababa"); + assertEquals(ids.get("ECT"), "Europe/Paris"); + assertEquals(ids.get("IET"), "America/Indiana/Indianapolis"); + assertEquals(ids.get("IST"), "Asia/Kolkata"); + assertEquals(ids.get("JST"), "Asia/Tokyo"); + assertEquals(ids.get("MIT"), "Pacific/Apia"); + assertEquals(ids.get("NET"), "Asia/Yerevan"); + assertEquals(ids.get("NST"), "Pacific/Auckland"); + assertEquals(ids.get("PLT"), "Asia/Karachi"); + assertEquals(ids.get("PNT"), "America/Phoenix"); + assertEquals(ids.get("PRT"), "America/Puerto_Rico"); + assertEquals(ids.get("PST"), "America/Los_Angeles"); + assertEquals(ids.get("SST"), "Pacific/Guadalcanal"); + assertEquals(ids.get("VST"), "Asia/Ho_Chi_Minh"); + } + + @Test(expectedExceptions=UnsupportedOperationException.class) + public void test_constant_OLD_IDS_POST_2005_immutable() { + Map ids = ZoneId.OLD_IDS_POST_2005; + ids.clear(); + } + + //----------------------------------------------------------------------- + // system default + //----------------------------------------------------------------------- + public void test_systemDefault() { + ZoneId test = ZoneId.systemDefault(); + assertEquals(test.getId(), TimeZone.getDefault().getID()); + } + + @Test(expectedExceptions = DateTimeException.class) + public void test_systemDefault_unableToConvert_badFormat() { + TimeZone current = TimeZone.getDefault(); + try { + TimeZone.setDefault(new SimpleTimeZone(127, "Something Weird")); + ZoneId.systemDefault(); + } finally { + TimeZone.setDefault(current); + } + } + + @Test(expectedExceptions = ZoneRulesException.class) + public void test_systemDefault_unableToConvert_unknownId() { + TimeZone current = TimeZone.getDefault(); + try { + TimeZone.setDefault(new SimpleTimeZone(127, "SomethingWeird")); + ZoneId.systemDefault(); + } finally { + TimeZone.setDefault(current); + } + } + + //----------------------------------------------------------------------- + // mapped factory + //----------------------------------------------------------------------- + public void test_of_string_Map() { + Map map = new HashMap(); + map.put("LONDON", "Europe/London"); + map.put("PARIS", "Europe/Paris"); + ZoneId test = ZoneId.of("LONDON", map); + assertEquals(test.getId(), "Europe/London"); + } + + public void test_of_string_Map_lookThrough() { + Map map = new HashMap(); + map.put("LONDON", "Europe/London"); + map.put("PARIS", "Europe/Paris"); + ZoneId test = ZoneId.of("Europe/Madrid", map); + assertEquals(test.getId(), "Europe/Madrid"); + } + + public void test_of_string_Map_emptyMap() { + Map map = new HashMap(); + ZoneId test = ZoneId.of("Europe/Madrid", map); + assertEquals(test.getId(), "Europe/Madrid"); + } + + @Test(expectedExceptions=DateTimeException.class) + public void test_of_string_Map_badFormat() { + Map map = new HashMap(); + ZoneId.of("Not kknown", map); + } + + @Test(expectedExceptions=ZoneRulesException.class) + public void test_of_string_Map_unknown() { + Map map = new HashMap(); + ZoneId.of("Unknown", map); + } + + //----------------------------------------------------------------------- + // regular factory + //----------------------------------------------------------------------- + @DataProvider(name="String_UTC") + Object[][] data_of_string_UTC() { + return new Object[][] { + {""}, {"Z"}, + {"+00"},{"+0000"},{"+00:00"},{"+000000"},{"+00:00:00"}, + {"-00"},{"-0000"},{"-00:00"},{"-000000"},{"-00:00:00"}, + }; + } + + @Test(dataProvider="String_UTC") + public void test_of_string_UTC(String id) { + ZoneId test = ZoneId.of("UTC" + id); + assertSame(test, ZoneOffset.UTC); + } + + @Test(dataProvider="String_UTC") + public void test_of_string_GMT(String id) { + ZoneId test = ZoneId.of("GMT" + id); + assertSame(test, ZoneOffset.UTC); + } + + //----------------------------------------------------------------------- + @DataProvider(name="String_Fixed") + Object[][] data_of_string_Fixed() { + return new Object[][] { + {"Z", "Z"}, + {"+0", "Z"}, + {"+5", "+05:00"}, + {"+01", "+01:00"}, + {"+0100", "+01:00"},{"+01:00", "+01:00"}, + {"+010000", "+01:00"},{"+01:00:00", "+01:00"}, + {"+12", "+12:00"}, + {"+1234", "+12:34"},{"+12:34", "+12:34"}, + {"+123456", "+12:34:56"},{"+12:34:56", "+12:34:56"}, + {"-02", "-02:00"}, + {"-5", "-05:00"}, + {"-0200", "-02:00"},{"-02:00", "-02:00"}, + {"-020000", "-02:00"},{"-02:00:00", "-02:00"}, + }; + } + + @Test(dataProvider="String_Fixed") + public void test_of_string_offset(String input, String id) { + ZoneId test = ZoneId.of(input); + assertEquals(test.getId(), id); + assertEquals(test.getText(TextStyle.FULL, Locale.UK), id); + assertEquals(test.getRules().isFixedOffset(), true); + ZoneOffset offset = ZoneOffset.of(id); + assertEquals(test.getRules().getOffset(Instant.ofEpochSecond(0L)), offset); + checkOffset(test.getRules(), createLDT(2008, 6, 30), offset, 1); + } + + @Test(dataProvider="String_Fixed") + public void test_of_string_FixedUTC(String input, String id) { + ZoneId test = ZoneId.of("UTC" + input); + assertEquals(test.getId(), id); + assertEquals(test.getText(TextStyle.FULL, Locale.UK), id); + assertEquals(test.getRules().isFixedOffset(), true); + ZoneOffset offset = ZoneOffset.of(id); + assertEquals(test.getRules().getOffset(Instant.ofEpochSecond(0L)), offset); + checkOffset(test.getRules(), createLDT(2008, 6, 30), offset, 1); + } + + @Test(dataProvider="String_Fixed") + public void test_of_string_FixedGMT(String input, String id) { + ZoneId test = ZoneId.of("GMT" + input); + assertEquals(test.getId(), id); + assertEquals(test.getText(TextStyle.FULL, Locale.UK), id); + assertEquals(test.getRules().isFixedOffset(), true); + ZoneOffset offset = ZoneOffset.of(id); + assertEquals(test.getRules().getOffset(Instant.ofEpochSecond(0L)), offset); + checkOffset(test.getRules(), createLDT(2008, 6, 30), offset, 1); + } + + //----------------------------------------------------------------------- + @DataProvider(name="String_UTC_Invalid") + Object[][] data_of_string_UTC_invalid() { + return new Object[][] { + {"A"}, {"B"}, {"C"}, {"D"}, {"E"}, {"F"}, {"G"}, {"H"}, {"I"}, {"J"}, {"K"}, {"L"}, {"M"}, + {"N"}, {"O"}, {"P"}, {"Q"}, {"R"}, {"S"}, {"T"}, {"U"}, {"V"}, {"W"}, {"X"}, {"Y"}, + {"+0:00"}, {"+00:0"}, {"+0:0"}, + {"+000"}, {"+00000"}, + {"+0:00:00"}, {"+00:0:00"}, {"+00:00:0"}, {"+0:0:0"}, {"+0:0:00"}, {"+00:0:0"}, {"+0:00:0"}, + {"+01_00"}, {"+01;00"}, {"+01@00"}, {"+01:AA"}, + {"+19"}, {"+19:00"}, {"+18:01"}, {"+18:00:01"}, {"+1801"}, {"+180001"}, + {"-0:00"}, {"-00:0"}, {"-0:0"}, + {"-000"}, {"-00000"}, + {"-0:00:00"}, {"-00:0:00"}, {"-00:00:0"}, {"-0:0:0"}, {"-0:0:00"}, {"-00:0:0"}, {"-0:00:0"}, + {"-19"}, {"-19:00"}, {"-18:01"}, {"-18:00:01"}, {"-1801"}, {"-180001"}, + {"-01_00"}, {"-01;00"}, {"-01@00"}, {"-01:AA"}, + {"@01:00"}, + }; + } + + @Test(dataProvider="String_UTC_Invalid", expectedExceptions=DateTimeException.class) + public void test_of_string_UTC_invalid(String id) { + ZoneId.of("UTC" + id); + } + + @Test(dataProvider="String_UTC_Invalid", expectedExceptions=DateTimeException.class) + public void test_of_string_GMT_invalid(String id) { + ZoneId.of("GMT" + id); + } + + //----------------------------------------------------------------------- + @DataProvider(name="String_Invalid") + Object[][] data_of_string_invalid() { + // \u00ef is a random unicode character + return new Object[][] { + {""}, {":"}, {"#"}, + {"\u00ef"}, {"`"}, {"!"}, {"\""}, {"\u00ef"}, {"$"}, {"^"}, {"&"}, {"*"}, {"("}, {")"}, {"="}, + {"\\"}, {"|"}, {","}, {"<"}, {">"}, {"?"}, {";"}, {"'"}, {"["}, {"]"}, {"{"}, {"}"}, + {"\u00ef:A"}, {"`:A"}, {"!:A"}, {"\":A"}, {"\u00ef:A"}, {"$:A"}, {"^:A"}, {"&:A"}, {"*:A"}, {"(:A"}, {"):A"}, {"=:A"}, {"+:A"}, + {"\\:A"}, {"|:A"}, {",:A"}, {"<:A"}, {">:A"}, {"?:A"}, {";:A"}, {"::A"}, {"':A"}, {"@:A"}, {"~:A"}, {"[:A"}, {"]:A"}, {"{:A"}, {"}:A"}, + {"A:B#\u00ef"}, {"A:B#`"}, {"A:B#!"}, {"A:B#\""}, {"A:B#\u00ef"}, {"A:B#$"}, {"A:B#^"}, {"A:B#&"}, {"A:B#*"}, + {"A:B#("}, {"A:B#)"}, {"A:B#="}, {"A:B#+"}, + {"A:B#\\"}, {"A:B#|"}, {"A:B#,"}, {"A:B#<"}, {"A:B#>"}, {"A:B#?"}, {"A:B#;"}, {"A:B#:"}, + {"A:B#'"}, {"A:B#@"}, {"A:B#~"}, {"A:B#["}, {"A:B#]"}, {"A:B#{"}, {"A:B#}"}, + }; + } + + @Test(dataProvider="String_Invalid", expectedExceptions=DateTimeException.class) + public void test_of_string_invalid(String id) { + ZoneId.of(id); + } + + //----------------------------------------------------------------------- + public void test_of_string_GMT0() { + ZoneId test = ZoneId.of("GMT0"); + assertEquals(test.getId(), "Z"); + assertEquals(test.getRules().isFixedOffset(), true); + } + + //----------------------------------------------------------------------- + public void test_of_string_London() { + ZoneId test = ZoneId.of("Europe/London"); + assertEquals(test.getId(), "Europe/London"); + assertEquals(test.getRules().isFixedOffset(), false); + } + + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class) + public void test_of_string_null() { + ZoneId.of((String) null); + } + + @Test(expectedExceptions=ZoneRulesException.class) + public void test_of_string_unknown_simple() { + ZoneId.of("Unknown"); + } + + //------------------------------------------------------------------------- + // TODO: test by deserialization +// public void test_ofUnchecked_string_invalidNotChecked() { +// ZoneRegion test = ZoneRegion.ofLenient("Unknown"); +// assertEquals(test.getId(), "Unknown"); +// } +// +// public void test_ofUnchecked_string_invalidNotChecked_unusualCharacters() { +// ZoneRegion test = ZoneRegion.ofLenient("QWERTYUIOPASDFGHJKLZXCVBNM~/._+-"); +// assertEquals(test.getId(), "QWERTYUIOPASDFGHJKLZXCVBNM~/._+-"); +// } + + //----------------------------------------------------------------------- + // from(TemporalAccessor) + //----------------------------------------------------------------------- + public void test_factory_from_DateTimeAccessor_zoneId() { + TemporalAccessor mock = new TemporalAccessor() { + @Override + public boolean isSupported(TemporalField field) { + return false; + } + + @Override + public long getLong(TemporalField field) { + throw new DateTimeException("Mock"); + } + + @Override + public R query(TemporalQuery query) { + if (query == Queries.zoneId()) { + return (R) ZONE_PARIS; + } + return TemporalAccessor.super.query(query); + } + }; + assertEquals(ZoneId.from(mock), ZONE_PARIS); + } + + public void test_factory_from_DateTimeAccessor_offset() { + ZoneOffset offset = ZoneOffset.ofHours(1); + assertEquals(ZoneId.from(offset), offset); + } + + @Test(expectedExceptions=DateTimeException.class) + public void test_factory_from_DateTimeAccessor_invalid_noDerive() { + ZoneId.from(LocalTime.of(12, 30)); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_factory_from_DateTimeAccessor_null() { + ZoneId.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // Europe/London + //----------------------------------------------------------------------- + public void test_London() { + ZoneId test = ZoneId.of("Europe/London"); + assertEquals(test.getId(), "Europe/London"); + assertEquals(test.getRules().isFixedOffset(), false); + } + + public void test_London_getOffset() { + ZoneId test = ZoneId.of("Europe/London"); + assertEquals(test.getRules().getOffset(createInstant(2008, 1, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 2, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 4, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 5, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 6, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 7, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 8, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 9, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 11, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 12, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + } + + public void test_London_getOffset_toDST() { + ZoneId test = ZoneId.of("Europe/London"); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 24, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 25, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 26, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 27, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 28, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 29, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 30, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 31, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + // cutover at 01:00Z + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 30, 0, 59, 59, 999999999, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 30, 1, 0, 0, 0, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + } + + public void test_London_getOffset_fromDST() { + ZoneId test = ZoneId.of("Europe/London"); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 24, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 25, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 26, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 27, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 28, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 29, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 30, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 31, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + // cutover at 01:00Z + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 26, 0, 59, 59, 999999999, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 26, 1, 0, 0, 0, ZoneOffset.UTC)), ZoneOffset.ofHours(0)); + } + + public void test_London_getOffsetInfo() { + ZoneId test = ZoneId.of("Europe/London"); + checkOffset(test.getRules(), createLDT(2008, 1, 1), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), createLDT(2008, 2, 1), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 1), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), createLDT(2008, 4, 1), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 5, 1), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 6, 1), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 7, 1), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 8, 1), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 9, 1), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 1), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 11, 1), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), createLDT(2008, 12, 1), ZoneOffset.ofHours(0), 1); + } + + public void test_London_getOffsetInfo_toDST() { + ZoneId test = ZoneId.of("Europe/London"); + checkOffset(test.getRules(), createLDT(2008, 3, 24), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 25), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 26), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 27), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 28), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 29), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 30), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 31), ZoneOffset.ofHours(1), 1); + // cutover at 01:00Z + checkOffset(test.getRules(), LocalDateTime.of(2008, 3, 30, 0, 59, 59, 999999999), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), LocalDateTime.of(2008, 3, 30, 1, 30, 0, 0), ZoneOffset.ofHours(0), GAP); + checkOffset(test.getRules(), LocalDateTime.of(2008, 3, 30, 2, 0, 0, 0), ZoneOffset.ofHours(1), 1); + } + + public void test_London_getOffsetInfo_fromDST() { + ZoneId test = ZoneId.of("Europe/London"); + checkOffset(test.getRules(), createLDT(2008, 10, 24), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 25), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 26), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 27), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 28), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 29), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 30), ZoneOffset.ofHours(0), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 31), ZoneOffset.ofHours(0), 1); + // cutover at 01:00Z + checkOffset(test.getRules(), LocalDateTime.of(2008, 10, 26, 0, 59, 59, 999999999), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), LocalDateTime.of(2008, 10, 26, 1, 30, 0, 0), ZoneOffset.ofHours(1), OVERLAP); + checkOffset(test.getRules(), LocalDateTime.of(2008, 10, 26, 2, 0, 0, 0), ZoneOffset.ofHours(0), 1); + } + + public void test_London_getOffsetInfo_gap() { + ZoneId test = ZoneId.of("Europe/London"); + final LocalDateTime dateTime = LocalDateTime.of(2008, 3, 30, 1, 0, 0, 0); + ZoneOffsetTransition trans = checkOffset(test.getRules(), dateTime, ZoneOffset.ofHours(0), GAP); + assertEquals(trans.isGap(), true); + assertEquals(trans.isOverlap(), false); + assertEquals(trans.getOffsetBefore(), ZoneOffset.ofHours(0)); + assertEquals(trans.getOffsetAfter(), ZoneOffset.ofHours(1)); + assertEquals(trans.getInstant(), dateTime.toInstant(ZoneOffset.UTC)); + assertEquals(trans.getDateTimeBefore(), LocalDateTime.of(2008, 3, 30, 1, 0)); + assertEquals(trans.getDateTimeAfter(), LocalDateTime.of(2008, 3, 30, 2, 0)); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(-1)), false); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(0)), false); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(1)), false); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(2)), false); + assertEquals(trans.toString(), "Transition[Gap at 2008-03-30T01:00Z to +01:00]"); + + assertFalse(trans.equals(null)); + assertFalse(trans.equals(ZoneOffset.ofHours(0))); + assertTrue(trans.equals(trans)); + + final ZoneOffsetTransition otherTrans = test.getRules().getTransition(dateTime); + assertTrue(trans.equals(otherTrans)); + assertEquals(trans.hashCode(), otherTrans.hashCode()); + } + + public void test_London_getOffsetInfo_overlap() { + ZoneId test = ZoneId.of("Europe/London"); + final LocalDateTime dateTime = LocalDateTime.of(2008, 10, 26, 1, 0, 0, 0); + ZoneOffsetTransition trans = checkOffset(test.getRules(), dateTime, ZoneOffset.ofHours(1), OVERLAP); + assertEquals(trans.isGap(), false); + assertEquals(trans.isOverlap(), true); + assertEquals(trans.getOffsetBefore(), ZoneOffset.ofHours(1)); + assertEquals(trans.getOffsetAfter(), ZoneOffset.ofHours(0)); + assertEquals(trans.getInstant(), dateTime.toInstant(ZoneOffset.UTC)); + assertEquals(trans.getDateTimeBefore(), LocalDateTime.of(2008, 10, 26, 2, 0)); + assertEquals(trans.getDateTimeAfter(), LocalDateTime.of(2008, 10, 26, 1, 0)); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(-1)), false); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(0)), true); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(1)), true); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(2)), false); + assertEquals(trans.toString(), "Transition[Overlap at 2008-10-26T02:00+01:00 to Z]"); + + assertFalse(trans.equals(null)); + assertFalse(trans.equals(ZoneOffset.ofHours(1))); + assertTrue(trans.equals(trans)); + + final ZoneOffsetTransition otherTrans = test.getRules().getTransition(dateTime); + assertTrue(trans.equals(otherTrans)); + assertEquals(trans.hashCode(), otherTrans.hashCode()); + } + + //----------------------------------------------------------------------- + // Europe/Paris + //----------------------------------------------------------------------- + public void test_Paris() { + ZoneId test = ZoneId.of("Europe/Paris"); + assertEquals(test.getId(), "Europe/Paris"); + assertEquals(test.getRules().isFixedOffset(), false); + } + + public void test_Paris_getOffset() { + ZoneId test = ZoneId.of("Europe/Paris"); + assertEquals(test.getRules().getOffset(createInstant(2008, 1, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 2, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 4, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(2)); + assertEquals(test.getRules().getOffset(createInstant(2008, 5, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(2)); + assertEquals(test.getRules().getOffset(createInstant(2008, 6, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(2)); + assertEquals(test.getRules().getOffset(createInstant(2008, 7, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(2)); + assertEquals(test.getRules().getOffset(createInstant(2008, 8, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(2)); + assertEquals(test.getRules().getOffset(createInstant(2008, 9, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(2)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(2)); + assertEquals(test.getRules().getOffset(createInstant(2008, 11, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 12, 1, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + } + + public void test_Paris_getOffset_toDST() { + ZoneId test = ZoneId.of("Europe/Paris"); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 24, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 25, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 26, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 27, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 28, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 29, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 30, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 31, ZoneOffset.UTC)), ZoneOffset.ofHours(2)); + // cutover at 01:00Z + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 30, 0, 59, 59, 999999999, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 30, 1, 0, 0, 0, ZoneOffset.UTC)), ZoneOffset.ofHours(2)); + } + + public void test_Paris_getOffset_fromDST() { + ZoneId test = ZoneId.of("Europe/Paris"); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 24, ZoneOffset.UTC)), ZoneOffset.ofHours(2)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 25, ZoneOffset.UTC)), ZoneOffset.ofHours(2)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 26, ZoneOffset.UTC)), ZoneOffset.ofHours(2)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 27, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 28, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 29, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 30, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 31, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + // cutover at 01:00Z + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 26, 0, 59, 59, 999999999, ZoneOffset.UTC)), ZoneOffset.ofHours(2)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 26, 1, 0, 0, 0, ZoneOffset.UTC)), ZoneOffset.ofHours(1)); + } + + public void test_Paris_getOffsetInfo() { + ZoneId test = ZoneId.of("Europe/Paris"); + checkOffset(test.getRules(), createLDT(2008, 1, 1), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 2, 1), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 1), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 4, 1), ZoneOffset.ofHours(2), 1); + checkOffset(test.getRules(), createLDT(2008, 5, 1), ZoneOffset.ofHours(2), 1); + checkOffset(test.getRules(), createLDT(2008, 6, 1), ZoneOffset.ofHours(2), 1); + checkOffset(test.getRules(), createLDT(2008, 7, 1), ZoneOffset.ofHours(2), 1); + checkOffset(test.getRules(), createLDT(2008, 8, 1), ZoneOffset.ofHours(2), 1); + checkOffset(test.getRules(), createLDT(2008, 9, 1), ZoneOffset.ofHours(2), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 1), ZoneOffset.ofHours(2), 1); + checkOffset(test.getRules(), createLDT(2008, 11, 1), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 12, 1), ZoneOffset.ofHours(1), 1); + } + + public void test_Paris_getOffsetInfo_toDST() { + ZoneId test = ZoneId.of("Europe/Paris"); + checkOffset(test.getRules(), createLDT(2008, 3, 24), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 25), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 26), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 27), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 28), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 29), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 30), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 31), ZoneOffset.ofHours(2), 1); + // cutover at 01:00Z which is 02:00+01:00(local Paris time) + checkOffset(test.getRules(), LocalDateTime.of(2008, 3, 30, 1, 59, 59, 999999999), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), LocalDateTime.of(2008, 3, 30, 2, 30, 0, 0), ZoneOffset.ofHours(1), GAP); + checkOffset(test.getRules(), LocalDateTime.of(2008, 3, 30, 3, 0, 0, 0), ZoneOffset.ofHours(2), 1); + } + + public void test_Paris_getOffsetInfo_fromDST() { + ZoneId test = ZoneId.of("Europe/Paris"); + checkOffset(test.getRules(), createLDT(2008, 10, 24), ZoneOffset.ofHours(2), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 25), ZoneOffset.ofHours(2), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 26), ZoneOffset.ofHours(2), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 27), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 28), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 29), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 30), ZoneOffset.ofHours(1), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 31), ZoneOffset.ofHours(1), 1); + // cutover at 01:00Z which is 02:00+01:00(local Paris time) + checkOffset(test.getRules(), LocalDateTime.of(2008, 10, 26, 1, 59, 59, 999999999), ZoneOffset.ofHours(2), 1); + checkOffset(test.getRules(), LocalDateTime.of(2008, 10, 26, 2, 30, 0, 0), ZoneOffset.ofHours(2), OVERLAP); + checkOffset(test.getRules(), LocalDateTime.of(2008, 10, 26, 3, 0, 0, 0), ZoneOffset.ofHours(1), 1); + } + + public void test_Paris_getOffsetInfo_gap() { + ZoneId test = ZoneId.of("Europe/Paris"); + final LocalDateTime dateTime = LocalDateTime.of(2008, 3, 30, 2, 0, 0, 0); + ZoneOffsetTransition trans = checkOffset(test.getRules(), dateTime, ZoneOffset.ofHours(1), GAP); + assertEquals(trans.isGap(), true); + assertEquals(trans.isOverlap(), false); + assertEquals(trans.getOffsetBefore(), ZoneOffset.ofHours(1)); + assertEquals(trans.getOffsetAfter(), ZoneOffset.ofHours(2)); + assertEquals(trans.getInstant(), createInstant(2008, 3, 30, 1, 0, 0, 0, ZoneOffset.UTC)); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(0)), false); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(1)), false); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(2)), false); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(3)), false); + assertEquals(trans.toString(), "Transition[Gap at 2008-03-30T02:00+01:00 to +02:00]"); + + assertFalse(trans.equals(null)); + assertFalse(trans.equals(ZoneOffset.ofHours(1))); + assertTrue(trans.equals(trans)); + + final ZoneOffsetTransition otherDis = test.getRules().getTransition(dateTime); + assertTrue(trans.equals(otherDis)); + assertEquals(trans.hashCode(), otherDis.hashCode()); + } + + public void test_Paris_getOffsetInfo_overlap() { + ZoneId test = ZoneId.of("Europe/Paris"); + final LocalDateTime dateTime = LocalDateTime.of(2008, 10, 26, 2, 0, 0, 0); + ZoneOffsetTransition trans = checkOffset(test.getRules(), dateTime, ZoneOffset.ofHours(2), OVERLAP); + assertEquals(trans.isGap(), false); + assertEquals(trans.isOverlap(), true); + assertEquals(trans.getOffsetBefore(), ZoneOffset.ofHours(2)); + assertEquals(trans.getOffsetAfter(), ZoneOffset.ofHours(1)); + assertEquals(trans.getInstant(), createInstant(2008, 10, 26, 1, 0, 0, 0, ZoneOffset.UTC)); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(0)), false); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(1)), true); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(2)), true); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(3)), false); + assertEquals(trans.toString(), "Transition[Overlap at 2008-10-26T03:00+02:00 to +01:00]"); + + assertFalse(trans.equals(null)); + assertFalse(trans.equals(ZoneOffset.ofHours(2))); + assertTrue(trans.equals(trans)); + + final ZoneOffsetTransition otherDis = test.getRules().getTransition(dateTime); + assertTrue(trans.equals(otherDis)); + assertEquals(trans.hashCode(), otherDis.hashCode()); + } + + //----------------------------------------------------------------------- + // America/New_York + //----------------------------------------------------------------------- + public void test_NewYork() { + ZoneId test = ZoneId.of("America/New_York"); + assertEquals(test.getId(), "America/New_York"); + assertEquals(test.getRules().isFixedOffset(), false); + } + + public void test_NewYork_getOffset() { + ZoneId test = ZoneId.of("America/New_York"); + ZoneOffset offset = ZoneOffset.ofHours(-5); + assertEquals(test.getRules().getOffset(createInstant(2008, 1, 1, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getRules().getOffset(createInstant(2008, 2, 1, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 1, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getRules().getOffset(createInstant(2008, 4, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 5, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 6, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 7, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 8, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 9, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 11, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 12, 1, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getRules().getOffset(createInstant(2008, 1, 28, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getRules().getOffset(createInstant(2008, 2, 28, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 4, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 5, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 6, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 7, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 8, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 9, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 10, 28, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 11, 28, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getRules().getOffset(createInstant(2008, 12, 28, offset)), ZoneOffset.ofHours(-5)); + } + + public void test_NewYork_getOffset_toDST() { + ZoneId test = ZoneId.of("America/New_York"); + ZoneOffset offset = ZoneOffset.ofHours(-5); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 8, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 9, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 10, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 11, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 12, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 13, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 14, offset)), ZoneOffset.ofHours(-4)); + // cutover at 02:00 local + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 9, 1, 59, 59, 999999999, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getRules().getOffset(createInstant(2008, 3, 9, 2, 0, 0, 0, offset)), ZoneOffset.ofHours(-4)); + } + + public void test_NewYork_getOffset_fromDST() { + ZoneId test = ZoneId.of("America/New_York"); + ZoneOffset offset = ZoneOffset.ofHours(-4); + assertEquals(test.getRules().getOffset(createInstant(2008, 11, 1, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 11, 2, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 11, 3, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getRules().getOffset(createInstant(2008, 11, 4, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getRules().getOffset(createInstant(2008, 11, 5, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getRules().getOffset(createInstant(2008, 11, 6, offset)), ZoneOffset.ofHours(-5)); + assertEquals(test.getRules().getOffset(createInstant(2008, 11, 7, offset)), ZoneOffset.ofHours(-5)); + // cutover at 02:00 local + assertEquals(test.getRules().getOffset(createInstant(2008, 11, 2, 1, 59, 59, 999999999, offset)), ZoneOffset.ofHours(-4)); + assertEquals(test.getRules().getOffset(createInstant(2008, 11, 2, 2, 0, 0, 0, offset)), ZoneOffset.ofHours(-5)); + } + + public void test_NewYork_getOffsetInfo() { + ZoneId test = ZoneId.of("America/New_York"); + checkOffset(test.getRules(), createLDT(2008, 1, 1), ZoneOffset.ofHours(-5), 1); + checkOffset(test.getRules(), createLDT(2008, 2, 1), ZoneOffset.ofHours(-5), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 1), ZoneOffset.ofHours(-5), 1); + checkOffset(test.getRules(), createLDT(2008, 4, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 5, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 6, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 7, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 8, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 9, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 11, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 12, 1), ZoneOffset.ofHours(-5), 1); + checkOffset(test.getRules(), createLDT(2008, 1, 28), ZoneOffset.ofHours(-5), 1); + checkOffset(test.getRules(), createLDT(2008, 2, 28), ZoneOffset.ofHours(-5), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 4, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 5, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 6, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 7, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 8, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 9, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 10, 28), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 11, 28), ZoneOffset.ofHours(-5), 1); + checkOffset(test.getRules(), createLDT(2008, 12, 28), ZoneOffset.ofHours(-5), 1); + } + + public void test_NewYork_getOffsetInfo_toDST() { + ZoneId test = ZoneId.of("America/New_York"); + checkOffset(test.getRules(), createLDT(2008, 3, 8), ZoneOffset.ofHours(-5), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 9), ZoneOffset.ofHours(-5), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 10), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 11), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 12), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 13), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 3, 14), ZoneOffset.ofHours(-4), 1); + // cutover at 02:00 local + checkOffset(test.getRules(), LocalDateTime.of(2008, 3, 9, 1, 59, 59, 999999999), ZoneOffset.ofHours(-5), 1); + checkOffset(test.getRules(), LocalDateTime.of(2008, 3, 9, 2, 30, 0, 0), ZoneOffset.ofHours(-5), GAP); + checkOffset(test.getRules(), LocalDateTime.of(2008, 3, 9, 3, 0, 0, 0), ZoneOffset.ofHours(-4), 1); + } + + public void test_NewYork_getOffsetInfo_fromDST() { + ZoneId test = ZoneId.of("America/New_York"); + checkOffset(test.getRules(), createLDT(2008, 11, 1), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 11, 2), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), createLDT(2008, 11, 3), ZoneOffset.ofHours(-5), 1); + checkOffset(test.getRules(), createLDT(2008, 11, 4), ZoneOffset.ofHours(-5), 1); + checkOffset(test.getRules(), createLDT(2008, 11, 5), ZoneOffset.ofHours(-5), 1); + checkOffset(test.getRules(), createLDT(2008, 11, 6), ZoneOffset.ofHours(-5), 1); + checkOffset(test.getRules(), createLDT(2008, 11, 7), ZoneOffset.ofHours(-5), 1); + // cutover at 02:00 local + checkOffset(test.getRules(), LocalDateTime.of(2008, 11, 2, 0, 59, 59, 999999999), ZoneOffset.ofHours(-4), 1); + checkOffset(test.getRules(), LocalDateTime.of(2008, 11, 2, 1, 30, 0, 0), ZoneOffset.ofHours(-4), OVERLAP); + checkOffset(test.getRules(), LocalDateTime.of(2008, 11, 2, 2, 0, 0, 0), ZoneOffset.ofHours(-5), 1); + } + + public void test_NewYork_getOffsetInfo_gap() { + ZoneId test = ZoneId.of("America/New_York"); + final LocalDateTime dateTime = LocalDateTime.of(2008, 3, 9, 2, 0, 0, 0); + ZoneOffsetTransition trans = checkOffset(test.getRules(), dateTime, ZoneOffset.ofHours(-5), GAP); + assertEquals(trans.getOffsetBefore(), ZoneOffset.ofHours(-5)); + assertEquals(trans.getOffsetAfter(), ZoneOffset.ofHours(-4)); + assertEquals(trans.getInstant(), createInstant(2008, 3, 9, 2, 0, 0, 0, ZoneOffset.ofHours(-5))); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(-6)), false); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(-5)), false); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(-4)), false); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(-3)), false); + assertEquals(trans.toString(), "Transition[Gap at 2008-03-09T02:00-05:00 to -04:00]"); + + assertFalse(trans.equals(null)); + assertFalse(trans.equals(ZoneOffset.ofHours(-5))); + assertTrue(trans.equals(trans)); + + final ZoneOffsetTransition otherTrans = test.getRules().getTransition(dateTime); + assertTrue(trans.equals(otherTrans)); + + assertEquals(trans.hashCode(), otherTrans.hashCode()); + } + + public void test_NewYork_getOffsetInfo_overlap() { + ZoneId test = ZoneId.of("America/New_York"); + final LocalDateTime dateTime = LocalDateTime.of(2008, 11, 2, 1, 0, 0, 0); + ZoneOffsetTransition trans = checkOffset(test.getRules(), dateTime, ZoneOffset.ofHours(-4), OVERLAP); + assertEquals(trans.getOffsetBefore(), ZoneOffset.ofHours(-4)); + assertEquals(trans.getOffsetAfter(), ZoneOffset.ofHours(-5)); + assertEquals(trans.getInstant(), createInstant(2008, 11, 2, 2, 0, 0, 0, ZoneOffset.ofHours(-4))); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(-1)), false); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(-5)), true); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(-4)), true); + assertEquals(trans.isValidOffset(ZoneOffset.ofHours(2)), false); + assertEquals(trans.toString(), "Transition[Overlap at 2008-11-02T02:00-04:00 to -05:00]"); + + assertFalse(trans.equals(null)); + assertFalse(trans.equals(ZoneOffset.ofHours(-4))); + assertTrue(trans.equals(trans)); + + final ZoneOffsetTransition otherTrans = test.getRules().getTransition(dateTime); + assertTrue(trans.equals(otherTrans)); + + assertEquals(trans.hashCode(), otherTrans.hashCode()); + } + + //----------------------------------------------------------------------- + // getXxx() isXxx() + //----------------------------------------------------------------------- + public void test_get_Tzdb() { + ZoneId test = ZoneId.of("Europe/London"); + assertEquals(test.getId(), "Europe/London"); + assertEquals(test.getRules().isFixedOffset(), false); + } + + public void test_get_TzdbFixed() { + ZoneId test = ZoneId.of("+01:30"); + assertEquals(test.getId(), "+01:30"); + assertEquals(test.getRules().isFixedOffset(), true); + } + + //----------------------------------------------------------------------- + // equals() / hashCode() + //----------------------------------------------------------------------- + public void test_equals() { + ZoneId test1 = ZoneId.of("Europe/London"); + ZoneId test2 = ZoneId.of("Europe/Paris"); + ZoneId test2b = ZoneId.of("Europe/Paris"); + assertEquals(test1.equals(test2), false); + assertEquals(test2.equals(test1), false); + + assertEquals(test1.equals(test1), true); + assertEquals(test2.equals(test2), true); + assertEquals(test2.equals(test2b), true); + + assertEquals(test1.hashCode() == test1.hashCode(), true); + assertEquals(test2.hashCode() == test2.hashCode(), true); + assertEquals(test2.hashCode() == test2b.hashCode(), true); + } + + public void test_equals_null() { + assertEquals(ZoneId.of("Europe/London").equals(null), false); + } + + public void test_equals_notTimeZone() { + assertEquals(ZoneId.of("Europe/London").equals("Europe/London"), false); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="ToString") + Object[][] data_toString() { + return new Object[][] { + {"Europe/London", "Europe/London"}, + {"Europe/Paris", "Europe/Paris"}, + {"Europe/Berlin", "Europe/Berlin"}, + {"UTC", "Z"}, + {"UTC+01:00", "+01:00"}, + }; + } + + @Test(dataProvider="ToString") + public void test_toString(String id, String expected) { + ZoneId test = ZoneId.of(id); + assertEquals(test.toString(), expected); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + private Instant createInstant(int year, int month, int day, ZoneOffset offset) { + return LocalDateTime.of(year, month, day, 0, 0).toInstant(offset); + } + + private Instant createInstant(int year, int month, int day, int hour, int min, int sec, int nano, ZoneOffset offset) { + return LocalDateTime.of(year, month, day, hour, min, sec, nano).toInstant(offset); + } + + private ZonedDateTime createZDT(int year, int month, int day, int hour, int min, int sec, int nano, ZoneId zone) { + return LocalDateTime.of(year, month, day, hour, min, sec, nano).atZone(zone); + } + + private LocalDateTime createLDT(int year, int month, int day) { + return LocalDateTime.of(year, month, day, 0, 0); + } + + private ZoneOffsetTransition checkOffset(ZoneRules rules, LocalDateTime dateTime, ZoneOffset offset, int type) { + List validOffsets = rules.getValidOffsets(dateTime); + assertEquals(validOffsets.size(), type); + assertEquals(rules.getOffset(dateTime), offset); + if (type == 1) { + assertEquals(validOffsets.get(0), offset); + return null; + } else { + ZoneOffsetTransition zot = rules.getTransition(dateTime); + assertNotNull(zot); + assertEquals(zot.isOverlap(), type == 2); + assertEquals(zot.isGap(), type == 0); + assertEquals(zot.isValidOffset(offset), type == 2); + return zot; + } + } + +} diff --git a/test/java/time/test/java/time/TestZoneOffset.java b/test/java/time/test/java/time/TestZoneOffset.java new file mode 100644 index 0000000000000000000000000000000000000000..b6df12f1abf846ccf2d824b0148433eecd8c6319 --- /dev/null +++ b/test/java/time/test/java/time/TestZoneOffset.java @@ -0,0 +1,84 @@ +/* + * 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. + * + * 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 test.java.time; + +import static org.testng.Assert.assertSame; + +import java.time.ZoneOffset; + +import org.testng.annotations.Test; + +/** + * Test ZoneOffset. + */ +@Test +public class TestZoneOffset extends AbstractTest { + + @Test + public void test_immutable() { + assertImmutable(ZoneOffset.class); + } + + @Test + public void test_factory_ofTotalSecondsSame() { + assertSame(ZoneOffset.ofTotalSeconds(0), ZoneOffset.UTC); + } + +} diff --git a/test/java/time/test/java/time/TestZonedDateTime.java b/test/java/time/test/java/time/TestZonedDateTime.java new file mode 100644 index 0000000000000000000000000000000000000000..ee9bbaef8a34c5413040d040bc9b8aac49ac9f59 --- /dev/null +++ b/test/java/time/test/java/time/TestZonedDateTime.java @@ -0,0 +1,77 @@ +/* + * 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. + * + * 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 test.java.time; + +import java.time.ZonedDateTime; + +import org.testng.annotations.Test; + +/** + * Test ZonedDateTime. + */ +@Test +public class TestZonedDateTime extends AbstractTest { + + @Test + public void test_immutable() { + assertImmutable(ZonedDateTime.class); + } + +} diff --git a/test/java/time/test/java/time/format/AbstractTestPrinterParser.java b/test/java/time/test/java/time/format/AbstractTestPrinterParser.java new file mode 100644 index 0000000000000000000000000000000000000000..c1b0ad2a18ea65dd1fe7aa7dc7107624ed54749d --- /dev/null +++ b/test/java/time/test/java/time/format/AbstractTestPrinterParser.java @@ -0,0 +1,147 @@ +/* + * 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. + * + * 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) 2011-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 test.java.time.format; + +import java.time.format.*; + +import java.util.Locale; + +import java.time.DateTimeException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Abstract PrinterParser test. + */ +@Test +public class AbstractTestPrinterParser { + + protected StringBuilder buf; + protected DateTimeFormatterBuilder builder; + protected TemporalAccessor dta; + protected Locale locale; + protected DateTimeFormatSymbols symbols; + + + @BeforeMethod(groups={"tck"}) + public void setUp() { + buf = new StringBuilder(); + builder = new DateTimeFormatterBuilder(); + dta = ZonedDateTime.of(LocalDateTime.of(2011, 6, 30, 12, 30, 40, 0), ZoneId.of("Europe/Paris")); + locale = Locale.ENGLISH; + symbols = DateTimeFormatSymbols.STANDARD; + } + + protected void setCaseSensitive(boolean caseSensitive) { + if (caseSensitive) { + builder.parseCaseSensitive(); + } else { + builder.parseCaseInsensitive(); + } + } + + protected void setStrict(boolean strict) { + if (strict) { + builder.parseStrict(); + } else { + builder.parseLenient(); + } + } + + protected DateTimeFormatter getFormatter() { + return builder.toFormatter(locale).withSymbols(symbols); + } + + protected DateTimeFormatter getFormatter(char c) { + return builder.appendLiteral(c).toFormatter(locale).withSymbols(symbols); + } + + protected DateTimeFormatter getFormatter(String s) { + return builder.appendLiteral(s).toFormatter(locale).withSymbols(symbols); + } + + protected DateTimeFormatter getFormatter(TemporalField field, TextStyle style) { + return builder.appendText(field, style).toFormatter(locale).withSymbols(symbols); + } + + protected DateTimeFormatter getFormatter(TemporalField field, int minWidth, int maxWidth, SignStyle signStyle) { + return builder.appendValue(field, minWidth, maxWidth, signStyle).toFormatter(locale).withSymbols(symbols); + } + + protected DateTimeFormatter getFormatter(String pattern, String noOffsetText) { + return builder.appendOffset(pattern, noOffsetText).toFormatter(locale).withSymbols(symbols); + } + + protected static final TemporalAccessor EMPTY_DTA = new TemporalAccessor() { + public boolean isSupported(TemporalField field) { + return true; + } + @Override + public long getLong(TemporalField field) { + throw new DateTimeException("Mock"); + } + }; +} diff --git a/test/java/time/test/java/time/format/MockIOExceptionAppendable.java b/test/java/time/test/java/time/format/MockIOExceptionAppendable.java new file mode 100644 index 0000000000000000000000000000000000000000..8cdedccdf8fc9ebf7dc71c813fad9bbe6960f52a --- /dev/null +++ b/test/java/time/test/java/time/format/MockIOExceptionAppendable.java @@ -0,0 +1,84 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import java.time.format.*; + +import java.io.IOException; + +/** + * Mock Appendable that throws IOException. + */ +public class MockIOExceptionAppendable implements Appendable { + + public Appendable append(CharSequence csq) throws IOException { + throw new IOException(); + } + + public Appendable append(char c) throws IOException { + throw new IOException(); + } + + public Appendable append(CharSequence csq, int start, int end) + throws IOException { + throw new IOException(); + } + +} diff --git a/test/java/time/test/java/time/format/TestCharLiteralParser.java b/test/java/time/test/java/time/format/TestCharLiteralParser.java new file mode 100644 index 0000000000000000000000000000000000000000..c2fe6290a5d6ea718c3bd2320121250ec7e71541 --- /dev/null +++ b/test/java/time/test/java/time/format/TestCharLiteralParser.java @@ -0,0 +1,133 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.time.format.DateTimeBuilder; +import java.text.ParsePosition; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test CharLiteralPrinterParser. + */ +@Test(groups={"implementation"}) +public class TestCharLiteralParser extends AbstractTestPrinterParser { + + @DataProvider(name="success") + Object[][] data_success() { + return new Object[][] { + // match + {'a', true, "a", 0, 1}, + {'a', true, "aOTHER", 0, 1}, + {'a', true, "OTHERaOTHER", 5, 6}, + {'a', true, "OTHERa", 5, 6}, + + // no match + {'a', true, "", 0, 0}, + {'a', true, "a", 1, 1}, + {'a', true, "A", 0, 0}, + {'a', true, "b", 0, 0}, + {'a', true, "OTHERbOTHER", 5, 5}, + {'a', true, "OTHERb", 5, 5}, + + // case insensitive + {'a', false, "a", 0, 1}, + {'a', false, "A", 0, 1}, + }; + } + + @Test(dataProvider="success") + public void test_parse_success(char c, boolean caseSensitive, + String text, int pos, int expectedPos) { + setCaseSensitive(caseSensitive); + ParsePosition ppos = new ParsePosition(pos); + DateTimeBuilder result = + getFormatter(c).parseToBuilder(text, ppos); + if (ppos.getErrorIndex() != -1) { + assertEquals(ppos.getIndex(), expectedPos); + } else { + assertEquals(ppos.getIndex(), expectedPos); + assertEquals(result.getCalendricalList().size(), 0); + } + } + + //----------------------------------------------------------------------- + @DataProvider(name="error") + Object[][] data_error() { + return new Object[][] { + {'a', "a", -1, IndexOutOfBoundsException.class}, + {'a', "a", 2, IndexOutOfBoundsException.class}, + }; + } + + @Test(dataProvider="error") + public void test_parse_error(char c, String text, int pos, Class expected) { + try { + DateTimeBuilder result = + getFormatter(c).parseToBuilder(text, new ParsePosition(pos)); + assertTrue(false); + } catch (RuntimeException ex) { + assertTrue(expected.isInstance(ex)); + } + } +} diff --git a/test/java/time/test/java/time/format/TestCharLiteralPrinter.java b/test/java/time/test/java/time/format/TestCharLiteralPrinter.java new file mode 100644 index 0000000000000000000000000000000000000000..f2c1f7d65b1d371d17908be41e4f39ea17db6d61 --- /dev/null +++ b/test/java/time/test/java/time/format/TestCharLiteralPrinter.java @@ -0,0 +1,102 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import java.time.format.*; + +import static org.testng.Assert.assertEquals; + +import org.testng.annotations.Test; + +/** + * Test CharLiteralPrinterParser. + */ +@Test(groups={"implementation"}) +public class TestCharLiteralPrinter extends AbstractTestPrinterParser { + + //----------------------------------------------------------------------- + public void test_print_emptyCalendrical() throws Exception { + buf.append("EXISTING"); + getFormatter('a').printTo(EMPTY_DTA, buf); + assertEquals(buf.toString(), "EXISTINGa"); + } + + public void test_print_dateTime() throws Exception { + buf.append("EXISTING"); + getFormatter('a').printTo(dta, buf); + assertEquals(buf.toString(), "EXISTINGa"); + } + + public void test_print_emptyAppendable() throws Exception { + getFormatter('a').printTo(dta, buf); + assertEquals(buf.toString(), "a"); + } + + //----------------------------------------------------------------------- + public void test_toString() throws Exception { + assertEquals(getFormatter('a').toString(), "'a'"); + } + + //----------------------------------------------------------------------- + public void test_toString_apos() throws Exception { + assertEquals(getFormatter('\'').toString(), "''"); + } + +} diff --git a/test/java/time/test/java/time/format/TestDateTimeFormatSymbols.java b/test/java/time/test/java/time/format/TestDateTimeFormatSymbols.java new file mode 100644 index 0000000000000000000000000000000000000000..fe2d408a6f91b134be9f6bcb50f4fc720c01e551 --- /dev/null +++ b/test/java/time/test/java/time/format/TestDateTimeFormatSymbols.java @@ -0,0 +1,91 @@ +/* + * 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. + * + * 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) 2011-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 test.java.time.format; + +import java.time.format.*; + +import static org.testng.Assert.assertSame; + +import java.util.Locale; + +import org.testng.annotations.Test; + +/** + * Test DateTimeFormatSymbols. + */ +@Test +public class TestDateTimeFormatSymbols { + + @Test(groups={"implementation"}) + public void test_of_Locale_cached() { + DateTimeFormatSymbols loc1 = DateTimeFormatSymbols.of(Locale.CANADA); + DateTimeFormatSymbols loc2 = DateTimeFormatSymbols.of(Locale.CANADA); + assertSame(loc1, loc2); + } + + //----------------------------------------------------------------------- + @Test(groups={"implementation"}) + public void test_ofDefaultLocale_cached() { + DateTimeFormatSymbols loc1 = DateTimeFormatSymbols.ofDefaultLocale(); + DateTimeFormatSymbols loc2 = DateTimeFormatSymbols.ofDefaultLocale(); + assertSame(loc1, loc2); + } + +} diff --git a/test/java/time/test/java/time/format/TestDateTimeFormatter.java b/test/java/time/test/java/time/format/TestDateTimeFormatter.java new file mode 100644 index 0000000000000000000000000000000000000000..61b0806f958c27e04cdbb66cd4ae423aa2a014f6 --- /dev/null +++ b/test/java/time/test/java/time/format/TestDateTimeFormatter.java @@ -0,0 +1,95 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import java.time.format.*; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static org.testng.Assert.assertSame; + +import java.util.Locale; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Test DateTimeFormatter. + */ +@Test +public class TestDateTimeFormatter { + // TODO these tests are not tck, as they refer to a non-public class + // rewrite whole test case to use BASIC_FORMATTER or similar + + @BeforeMethod(groups={"tck"}) + public void setUp() { + } + + @Test(groups={"implementation"}) + public void test_withLocale_same() throws Exception { + DateTimeFormatter base = + new DateTimeFormatterBuilder().appendLiteral("ONE") + .appendValue(DAY_OF_MONTH, 1, 2, SignStyle.NOT_NEGATIVE) + .toFormatter(Locale.ENGLISH) + .withSymbols(DateTimeFormatSymbols.STANDARD); + DateTimeFormatter test = base.withLocale(Locale.ENGLISH); + assertSame(test, base); + } + +} diff --git a/test/java/time/test/java/time/format/TestDateTimeFormatters.java b/test/java/time/test/java/time/format/TestDateTimeFormatters.java new file mode 100644 index 0000000000000000000000000000000000000000..5e7ab3ef7cb2fa94c7c625da26b026950356c9b7 --- /dev/null +++ b/test/java/time/test/java/time/format/TestDateTimeFormatters.java @@ -0,0 +1,93 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import java.time.format.*; + +import static org.testng.Assert.assertTrue; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.util.Collections; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Test DateTimeFormatters. + */ +@Test +public class TestDateTimeFormatters { + + @BeforeMethod + public void setUp() { + } + + @Test(groups={"implementation"}) + @SuppressWarnings("rawtypes") + public void test_constructor() throws Exception { + for (Constructor constructor : DateTimeFormatters.class.getDeclaredConstructors()) { + assertTrue(Modifier.isPrivate(constructor.getModifiers())); + //constructor.setAccessible(true); + //constructor.newInstance(Collections.nCopies(constructor.getParameterTypes().length, null).toArray()); + } + } + +} diff --git a/test/java/time/test/java/time/format/TestDateTimePrintException.java b/test/java/time/test/java/time/format/TestDateTimePrintException.java new file mode 100644 index 0000000000000000000000000000000000000000..3a737f33ed28149b32cd4743818f090e6bd23f4b --- /dev/null +++ b/test/java/time/test/java/time/format/TestDateTimePrintException.java @@ -0,0 +1,95 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import java.time.format.*; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; + +import java.io.IOException; + +import org.testng.annotations.Test; + +/** + * Test DateTimePrintException. + */ +@Test +public class TestDateTimePrintException { + + @Test(groups={"implementation"}) + public void test_constructor_StringThrowable_notIOException_same() throws Exception { + IllegalArgumentException iaex = new IllegalArgumentException("INNER"); + DateTimePrintException ex = new DateTimePrintException("TEST", iaex); + assertEquals(ex.getMessage(), "TEST"); + assertSame(ex.getCause(), iaex); + ex.rethrowIOException(); // no effect + } + + @Test(expectedExceptions=IOException.class, groups={"implementation"}) + public void test_constructor_StringThrowable_IOException_same() throws Exception { + IOException ioex = new IOException("INNER"); + DateTimePrintException ex = new DateTimePrintException("TEST", ioex); + assertEquals(ex.getMessage(), "TEST"); + assertSame(ex.getCause(), ioex); + ex.rethrowIOException(); // rethrows + } + +} diff --git a/test/java/time/test/java/time/format/TestDateTimeTextProvider.java b/test/java/time/test/java/time/format/TestDateTimeTextProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..ab8cec39822147dce366fea0266a7ca7f27c7478 --- /dev/null +++ b/test/java/time/test/java/time/format/TestDateTimeTextProvider.java @@ -0,0 +1,186 @@ +/* + * 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. + * + * 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) 2011-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 test.java.time.format; + +import java.time.format.*; + +import static java.time.temporal.ChronoField.AMPM_OF_DAY; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static org.testng.Assert.assertEquals; + +import java.util.Locale; + +import java.time.ZonedDateTime; +import java.time.temporal.TemporalField; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test SimpleDateTimeTextProvider. + */ +@Test(groups={"implementation"}) +public class TestDateTimeTextProvider extends AbstractTestPrinterParser { + + Locale enUS = new Locale("en", "US"); + Locale ptBR = new Locale("pt", "BR"); + + //----------------------------------------------------------------------- + @DataProvider(name = "Text") + Object[][] data_text() { + return new Object[][] { + {DAY_OF_WEEK, 1, TextStyle.SHORT, enUS, "Mon"}, + {DAY_OF_WEEK, 2, TextStyle.SHORT, enUS, "Tue"}, + {DAY_OF_WEEK, 3, TextStyle.SHORT, enUS, "Wed"}, + {DAY_OF_WEEK, 4, TextStyle.SHORT, enUS, "Thu"}, + {DAY_OF_WEEK, 5, TextStyle.SHORT, enUS, "Fri"}, + {DAY_OF_WEEK, 6, TextStyle.SHORT, enUS, "Sat"}, + {DAY_OF_WEEK, 7, TextStyle.SHORT, enUS, "Sun"}, + + {DAY_OF_WEEK, 1, TextStyle.SHORT, ptBR, "Seg"}, + {DAY_OF_WEEK, 2, TextStyle.SHORT, ptBR, "Ter"}, + {DAY_OF_WEEK, 3, TextStyle.SHORT, ptBR, "Qua"}, + {DAY_OF_WEEK, 4, TextStyle.SHORT, ptBR, "Qui"}, + {DAY_OF_WEEK, 5, TextStyle.SHORT, ptBR, "Sex"}, + {DAY_OF_WEEK, 6, TextStyle.SHORT, ptBR, "S\u00E1b"}, + {DAY_OF_WEEK, 7, TextStyle.SHORT, ptBR, "Dom"}, + + {DAY_OF_WEEK, 1, TextStyle.FULL, enUS, "Monday"}, + {DAY_OF_WEEK, 2, TextStyle.FULL, enUS, "Tuesday"}, + {DAY_OF_WEEK, 3, TextStyle.FULL, enUS, "Wednesday"}, + {DAY_OF_WEEK, 4, TextStyle.FULL, enUS, "Thursday"}, + {DAY_OF_WEEK, 5, TextStyle.FULL, enUS, "Friday"}, + {DAY_OF_WEEK, 6, TextStyle.FULL, enUS, "Saturday"}, + {DAY_OF_WEEK, 7, TextStyle.FULL, enUS, "Sunday"}, + + {DAY_OF_WEEK, 1, TextStyle.FULL, ptBR, "Segunda-feira"}, + {DAY_OF_WEEK, 2, TextStyle.FULL, ptBR, "Ter\u00E7a-feira"}, + {DAY_OF_WEEK, 3, TextStyle.FULL, ptBR, "Quarta-feira"}, + {DAY_OF_WEEK, 4, TextStyle.FULL, ptBR, "Quinta-feira"}, + {DAY_OF_WEEK, 5, TextStyle.FULL, ptBR, "Sexta-feira"}, + {DAY_OF_WEEK, 6, TextStyle.FULL, ptBR, "S\u00E1bado"}, + {DAY_OF_WEEK, 7, TextStyle.FULL, ptBR, "Domingo"}, + + {MONTH_OF_YEAR, 1, TextStyle.SHORT, enUS, "Jan"}, + {MONTH_OF_YEAR, 2, TextStyle.SHORT, enUS, "Feb"}, + {MONTH_OF_YEAR, 3, TextStyle.SHORT, enUS, "Mar"}, + {MONTH_OF_YEAR, 4, TextStyle.SHORT, enUS, "Apr"}, + {MONTH_OF_YEAR, 5, TextStyle.SHORT, enUS, "May"}, + {MONTH_OF_YEAR, 6, TextStyle.SHORT, enUS, "Jun"}, + {MONTH_OF_YEAR, 7, TextStyle.SHORT, enUS, "Jul"}, + {MONTH_OF_YEAR, 8, TextStyle.SHORT, enUS, "Aug"}, + {MONTH_OF_YEAR, 9, TextStyle.SHORT, enUS, "Sep"}, + {MONTH_OF_YEAR, 10, TextStyle.SHORT, enUS, "Oct"}, + {MONTH_OF_YEAR, 11, TextStyle.SHORT, enUS, "Nov"}, + {MONTH_OF_YEAR, 12, TextStyle.SHORT, enUS, "Dec"}, + + {MONTH_OF_YEAR, 1, TextStyle.SHORT, ptBR, "Jan"}, + {MONTH_OF_YEAR, 2, TextStyle.SHORT, ptBR, "Fev"}, + {MONTH_OF_YEAR, 3, TextStyle.SHORT, ptBR, "Mar"}, + {MONTH_OF_YEAR, 4, TextStyle.SHORT, ptBR, "Abr"}, + {MONTH_OF_YEAR, 5, TextStyle.SHORT, ptBR, "Mai"}, + {MONTH_OF_YEAR, 6, TextStyle.SHORT, ptBR, "Jun"}, + {MONTH_OF_YEAR, 7, TextStyle.SHORT, ptBR, "Jul"}, + {MONTH_OF_YEAR, 8, TextStyle.SHORT, ptBR, "Ago"}, + {MONTH_OF_YEAR, 9, TextStyle.SHORT, ptBR, "Set"}, + {MONTH_OF_YEAR, 10, TextStyle.SHORT, ptBR, "Out"}, + {MONTH_OF_YEAR, 11, TextStyle.SHORT, ptBR, "Nov"}, + {MONTH_OF_YEAR, 12, TextStyle.SHORT, ptBR, "Dez"}, + + {MONTH_OF_YEAR, 1, TextStyle.FULL, enUS, "January"}, + {MONTH_OF_YEAR, 2, TextStyle.FULL, enUS, "February"}, + {MONTH_OF_YEAR, 3, TextStyle.FULL, enUS, "March"}, + {MONTH_OF_YEAR, 4, TextStyle.FULL, enUS, "April"}, + {MONTH_OF_YEAR, 5, TextStyle.FULL, enUS, "May"}, + {MONTH_OF_YEAR, 6, TextStyle.FULL, enUS, "June"}, + {MONTH_OF_YEAR, 7, TextStyle.FULL, enUS, "July"}, + {MONTH_OF_YEAR, 8, TextStyle.FULL, enUS, "August"}, + {MONTH_OF_YEAR, 9, TextStyle.FULL, enUS, "September"}, + {MONTH_OF_YEAR, 10, TextStyle.FULL, enUS, "October"}, + {MONTH_OF_YEAR, 11, TextStyle.FULL, enUS, "November"}, + {MONTH_OF_YEAR, 12, TextStyle.FULL, enUS, "December"}, + + {MONTH_OF_YEAR, 1, TextStyle.FULL, ptBR, "Janeiro"}, + {MONTH_OF_YEAR, 2, TextStyle.FULL, ptBR, "Fevereiro"}, + {MONTH_OF_YEAR, 3, TextStyle.FULL, ptBR, "Mar\u00E7o"}, + {MONTH_OF_YEAR, 4, TextStyle.FULL, ptBR, "Abril"}, + {MONTH_OF_YEAR, 5, TextStyle.FULL, ptBR, "Maio"}, + {MONTH_OF_YEAR, 6, TextStyle.FULL, ptBR, "Junho"}, + {MONTH_OF_YEAR, 7, TextStyle.FULL, ptBR, "Julho"}, + {MONTH_OF_YEAR, 8, TextStyle.FULL, ptBR, "Agosto"}, + {MONTH_OF_YEAR, 9, TextStyle.FULL, ptBR, "Setembro"}, + {MONTH_OF_YEAR, 10, TextStyle.FULL, ptBR, "Outubro"}, + {MONTH_OF_YEAR, 11, TextStyle.FULL, ptBR, "Novembro"}, + {MONTH_OF_YEAR, 12, TextStyle.FULL, ptBR, "Dezembro"}, + + {AMPM_OF_DAY, 0, TextStyle.SHORT, enUS, "AM"}, + {AMPM_OF_DAY, 1, TextStyle.SHORT, enUS, "PM"}, + + }; + } + + @Test(dataProvider = "Text") + public void test_getText(TemporalField field, Number value, TextStyle style, Locale locale, String expected) { + DateTimeFormatter fmt = getFormatter(field, style).withLocale(locale); + assertEquals(fmt.print(ZonedDateTime.now().with(field, value.longValue())), expected); + } + +} diff --git a/test/java/time/test/java/time/format/TestFractionPrinterParser.java b/test/java/time/test/java/time/format/TestFractionPrinterParser.java new file mode 100644 index 0000000000000000000000000000000000000000..4be3b6cce46e5fa242f5205fea071fea89f5fc98 --- /dev/null +++ b/test/java/time/test/java/time/format/TestFractionPrinterParser.java @@ -0,0 +1,343 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import java.time.format.*; + +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +import java.text.ParsePosition; +import java.time.DateTimeException; +import java.time.LocalTime; +import java.time.format.DateTimeBuilder; +import java.time.temporal.TemporalField; + +import test.java.time.temporal.MockFieldValue; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test FractionPrinterParser. + */ +@Test(groups={"implementation"}) +public class TestFractionPrinterParser extends AbstractTestPrinterParser { + + private DateTimeFormatter getFormatter(TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) { + return builder.appendFraction(field, minWidth, maxWidth, decimalPoint).toFormatter(locale).withSymbols(symbols); + } + + //----------------------------------------------------------------------- + // print + //----------------------------------------------------------------------- + @Test(expectedExceptions=DateTimeException.class) + public void test_print_emptyCalendrical() throws Exception { + getFormatter(NANO_OF_SECOND, 0, 9, true).printTo(EMPTY_DTA, buf); + } + + public void test_print_append() throws Exception { + buf.append("EXISTING"); + getFormatter(NANO_OF_SECOND, 0, 9, true).printTo(LocalTime.of(12, 30, 40, 3), buf); + assertEquals(buf.toString(), "EXISTING.000000003"); + } + + //----------------------------------------------------------------------- + @DataProvider(name="Nanos") + Object[][] provider_nanos() { + return new Object[][] { + {0, 9, 0, ""}, + {0, 9, 2, ".000000002"}, + {0, 9, 20, ".00000002"}, + {0, 9, 200, ".0000002"}, + {0, 9, 2000, ".000002"}, + {0, 9, 20000, ".00002"}, + {0, 9, 200000, ".0002"}, + {0, 9, 2000000, ".002"}, + {0, 9, 20000000, ".02"}, + {0, 9, 200000000, ".2"}, + {0, 9, 1, ".000000001"}, + {0, 9, 12, ".000000012"}, + {0, 9, 123, ".000000123"}, + {0, 9, 1234, ".000001234"}, + {0, 9, 12345, ".000012345"}, + {0, 9, 123456, ".000123456"}, + {0, 9, 1234567, ".001234567"}, + {0, 9, 12345678, ".012345678"}, + {0, 9, 123456789, ".123456789"}, + + {1, 9, 0, ".0"}, + {1, 9, 2, ".000000002"}, + {1, 9, 20, ".00000002"}, + {1, 9, 200, ".0000002"}, + {1, 9, 2000, ".000002"}, + {1, 9, 20000, ".00002"}, + {1, 9, 200000, ".0002"}, + {1, 9, 2000000, ".002"}, + {1, 9, 20000000, ".02"}, + {1, 9, 200000000, ".2"}, + + {2, 3, 0, ".00"}, + {2, 3, 2, ".000"}, + {2, 3, 20, ".000"}, + {2, 3, 200, ".000"}, + {2, 3, 2000, ".000"}, + {2, 3, 20000, ".000"}, + {2, 3, 200000, ".000"}, + {2, 3, 2000000, ".002"}, + {2, 3, 20000000, ".02"}, + {2, 3, 200000000, ".20"}, + {2, 3, 1, ".000"}, + {2, 3, 12, ".000"}, + {2, 3, 123, ".000"}, + {2, 3, 1234, ".000"}, + {2, 3, 12345, ".000"}, + {2, 3, 123456, ".000"}, + {2, 3, 1234567, ".001"}, + {2, 3, 12345678, ".012"}, + {2, 3, 123456789, ".123"}, + + {6, 6, 0, ".000000"}, + {6, 6, 2, ".000000"}, + {6, 6, 20, ".000000"}, + {6, 6, 200, ".000000"}, + {6, 6, 2000, ".000002"}, + {6, 6, 20000, ".000020"}, + {6, 6, 200000, ".000200"}, + {6, 6, 2000000, ".002000"}, + {6, 6, 20000000, ".020000"}, + {6, 6, 200000000, ".200000"}, + {6, 6, 1, ".000000"}, + {6, 6, 12, ".000000"}, + {6, 6, 123, ".000000"}, + {6, 6, 1234, ".000001"}, + {6, 6, 12345, ".000012"}, + {6, 6, 123456, ".000123"}, + {6, 6, 1234567, ".001234"}, + {6, 6, 12345678, ".012345"}, + {6, 6, 123456789, ".123456"}, + }; + } + + @Test(dataProvider="Nanos") + public void test_print_nanos(int minWidth, int maxWidth, int value, String result) throws Exception { + getFormatter(NANO_OF_SECOND, minWidth, maxWidth, true).printTo(new MockFieldValue(NANO_OF_SECOND, value), buf); + if (result == null) { + fail("Expected exception"); + } + assertEquals(buf.toString(), result); + } + + @Test(dataProvider="Nanos") + public void test_print_nanos_noDecimalPoint(int minWidth, int maxWidth, int value, String result) throws Exception { + getFormatter(NANO_OF_SECOND, minWidth, maxWidth, false).printTo(new MockFieldValue(NANO_OF_SECOND, value), buf); + if (result == null) { + fail("Expected exception"); + } + assertEquals(buf.toString(), (result.startsWith(".") ? result.substring(1) : result)); + } + + //----------------------------------------------------------------------- + @DataProvider(name="Seconds") + Object[][] provider_seconds() { + return new Object[][] { + {0, 9, 0, ""}, + {0, 9, 3, ".05"}, + {0, 9, 6, ".1"}, + {0, 9, 9, ".15"}, + {0, 9, 12, ".2"}, + {0, 9, 15, ".25"}, + {0, 9, 30, ".5"}, + {0, 9, 45, ".75"}, + + {2, 2, 0, ".00"}, + {2, 2, 3, ".05"}, + {2, 2, 6, ".10"}, + {2, 2, 9, ".15"}, + {2, 2, 12, ".20"}, + {2, 2, 15, ".25"}, + {2, 2, 30, ".50"}, + {2, 2, 45, ".75"}, + }; + } + + @Test(dataProvider="Seconds") + public void test_print_seconds(int minWidth, int maxWidth, int value, String result) throws Exception { + getFormatter(SECOND_OF_MINUTE, minWidth, maxWidth, true).printTo(new MockFieldValue(SECOND_OF_MINUTE, value), buf); + if (result == null) { + fail("Expected exception"); + } + assertEquals(buf.toString(), result); + } + + @Test(dataProvider="Seconds") + public void test_print_seconds_noDecimalPoint(int minWidth, int maxWidth, int value, String result) throws Exception { + getFormatter(SECOND_OF_MINUTE, minWidth, maxWidth, false).printTo(new MockFieldValue(SECOND_OF_MINUTE, value), buf); + if (result == null) { + fail("Expected exception"); + } + assertEquals(buf.toString(), (result.startsWith(".") ? result.substring(1) : result)); + } + + //----------------------------------------------------------------------- + // parse + //----------------------------------------------------------------------- + @Test(dataProvider="Nanos") + public void test_reverseParse(int minWidth, int maxWidth, int value, String result) throws Exception { + ParsePosition pos = new ParsePosition(0); + int expectedValue = fixParsedValue(maxWidth, value); + DateTimeBuilder dtb = getFormatter(NANO_OF_SECOND, minWidth, maxWidth, true).parseToBuilder(result, pos); + assertEquals(pos.getIndex(), result.length()); + assertParsed(dtb, NANO_OF_SECOND, value == 0 && minWidth == 0 ? null : (long) expectedValue); + } + + @Test(dataProvider="Nanos") + public void test_reverseParse_noDecimalPoint(int minWidth, int maxWidth, int value, String result) throws Exception { + ParsePosition pos = new ParsePosition((result.startsWith(".") ? 1 : 0)); + DateTimeBuilder dtb = getFormatter(NANO_OF_SECOND, minWidth, maxWidth, false).parseToBuilder(result, pos); + assertEquals(pos.getIndex(), result.length()); + int expectedValue = fixParsedValue(maxWidth, value); + assertParsed(dtb, NANO_OF_SECOND, value == 0 && minWidth == 0 ? null : (long) expectedValue); + } + + @Test(dataProvider="Nanos") + public void test_reverseParse_followedByNonDigit(int minWidth, int maxWidth, int value, String result) throws Exception { + ParsePosition pos = new ParsePosition(0); + int expectedValue = fixParsedValue(maxWidth, value); + DateTimeBuilder dtb = getFormatter(NANO_OF_SECOND, minWidth, maxWidth, true).parseToBuilder(result + " ", pos); + assertEquals(pos.getIndex(), result.length()); + assertParsed(dtb, NANO_OF_SECOND, value == 0 && minWidth == 0 ? null : (long) expectedValue); + } + +// @Test(dataProvider="Nanos") +// public void test_reverseParse_followedByNonDigit_noDecimalPoint(int minWidth, int maxWidth, int value, String result) throws Exception { +// FractionPrinterParser pp = new FractionPrinterParser(NANO_OF_SECOND, minWidth, maxWidth, false); +// int newPos = pp.parse(parseContext, result + " ", (result.startsWith(".") ? 1 : 0)); +// assertEquals(newPos, result.length()); +// int expectedValue = fixParsedValue(maxWidth, value); +// assertParsed(parseContext, NANO_OF_SECOND, value == 0 && minWidth == 0 ? null : (long) expectedValue); +// } + + @Test(dataProvider="Nanos") + public void test_reverseParse_preceededByNonDigit(int minWidth, int maxWidth, int value, String result) throws Exception { + ParsePosition pos = new ParsePosition(1); + int expectedValue = fixParsedValue(maxWidth, value); + DateTimeBuilder dtb = getFormatter(NANO_OF_SECOND, minWidth, maxWidth, true).parseToBuilder(" " + result, pos); + assertEquals(pos.getIndex(), result.length() + 1); + assertParsed(dtb, NANO_OF_SECOND, value == 0 && minWidth == 0 ? null : (long) expectedValue); + } + + private int fixParsedValue(int maxWidth, int value) { + if (maxWidth < 9) { + int power = (int) Math.pow(10, (9 - maxWidth)); + value = (value / power) * power; + } + return value; + } + + @Test(dataProvider="Seconds") + public void test_reverseParse_seconds(int minWidth, int maxWidth, int value, String result) throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter(SECOND_OF_MINUTE, minWidth, maxWidth, true).parseToBuilder(result, pos); + assertEquals(pos.getIndex(), result.length()); + assertParsed(dtb, SECOND_OF_MINUTE, value == 0 && minWidth == 0 ? null : (long) value); + } + + private void assertParsed(DateTimeBuilder dtb, TemporalField field, Long value) { + if (value == null) { + assertEquals(dtb.containsFieldValue(field), false); + } else { + assertEquals(dtb.getLong(field), (long)value); + } + } + + //----------------------------------------------------------------------- + @DataProvider(name="ParseNothing") + Object[][] provider_parseNothing() { + return new Object[][] { + {NANO_OF_SECOND, 3, 6, true, "", 0, 0}, + {NANO_OF_SECOND, 3, 6, true, "A", 0, 0}, + {NANO_OF_SECOND, 3, 6, true, ".", 0, 1}, + {NANO_OF_SECOND, 3, 6, true, ".5", 0, 1}, + {NANO_OF_SECOND, 3, 6, true, ".51", 0, 1}, + {NANO_OF_SECOND, 3, 6, true, ".A23456", 0, 1}, + {NANO_OF_SECOND, 3, 6, true, ".1A3456", 0, 1}, + }; + } + + @Test(dataProvider = "ParseNothing") + public void test_parse_nothing(TemporalField field, int min, int max, boolean decimalPoint, String text, int pos, int expected) { + ParsePosition ppos = new ParsePosition(pos); + DateTimeBuilder dtb = getFormatter(field, min, max, decimalPoint).parseToBuilder(text, ppos); + assertEquals(ppos.getErrorIndex(), expected); + } + + //----------------------------------------------------------------------- + public void test_toString() throws Exception { + assertEquals(getFormatter(NANO_OF_SECOND, 3, 6, true).toString(), "Fraction(NanoOfSecond,3,6,DecimalPoint)"); + } + + public void test_toString_noDecimalPoint() throws Exception { + assertEquals(getFormatter(NANO_OF_SECOND, 3, 6, false).toString(), "Fraction(NanoOfSecond,3,6)"); + } + +} diff --git a/test/java/time/test/java/time/format/TestNumberParser.java b/test/java/time/test/java/time/format/TestNumberParser.java new file mode 100644 index 0000000000000000000000000000000000000000..af4b3b6e94036190f89ef58713dcd7c5f36a0658 --- /dev/null +++ b/test/java/time/test/java/time/format/TestNumberParser.java @@ -0,0 +1,547 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import java.time.format.*; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.text.ParsePosition; +import java.time.format.DateTimeBuilder; +import java.time.temporal.TemporalField; +import java.time.format.DateTimeFormatter; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test NumberPrinterParser. + */ +@Test(groups={"implementation"}) +public class TestNumberParser extends AbstractTestPrinterParser { + + //----------------------------------------------------------------------- + @DataProvider(name="error") + Object[][] data_error() { + return new Object[][] { + {DAY_OF_MONTH, 1, 2, SignStyle.NEVER, "12", -1, IndexOutOfBoundsException.class}, + {DAY_OF_MONTH, 1, 2, SignStyle.NEVER, "12", 3, IndexOutOfBoundsException.class}, + }; + } + + @Test(dataProvider="error") + public void test_parse_error(TemporalField field, int min, int max, SignStyle style, String text, int pos, Class expected) { + try { + getFormatter(field, min, max, style).parseToBuilder(text, new ParsePosition(pos)); + assertTrue(false); + } catch (RuntimeException ex) { + assertTrue(expected.isInstance(ex)); + } + } + + //----------------------------------------------------------------------- + @DataProvider(name="parseData") + Object[][] provider_parseData() { + return new Object[][] { + // normal + {1, 2, SignStyle.NEVER, 0, "12", 0, 2, 12L}, // normal + {1, 2, SignStyle.NEVER, 0, "Xxx12Xxx", 3, 5, 12L}, // parse in middle + {1, 2, SignStyle.NEVER, 0, "99912999", 3, 5, 12L}, // parse in middle + {2, 4, SignStyle.NEVER, 0, "12345", 0, 4, 1234L}, // stops at max width + {2, 4, SignStyle.NEVER, 0, "12-45", 0, 2, 12L}, // stops at dash + {2, 4, SignStyle.NEVER, 0, "123-5", 0, 3, 123L}, // stops at dash + {1, 10, SignStyle.NORMAL, 0, "2147483647", 0, 10, Integer.MAX_VALUE}, + {1, 10, SignStyle.NORMAL, 0, "-2147483648", 0, 11, Integer.MIN_VALUE}, + {1, 10, SignStyle.NORMAL, 0, "2147483648", 0, 10, 2147483648L}, + {1, 10, SignStyle.NORMAL, 0, "-2147483649", 0, 11, -2147483649L}, + {1, 10, SignStyle.NORMAL, 0, "987659876598765", 0, 10, 9876598765L}, + {1, 19, SignStyle.NORMAL, 0, "999999999999999999", 0, 18, 999999999999999999L}, + {1, 19, SignStyle.NORMAL, 0, "-999999999999999999", 0, 19, -999999999999999999L}, + {1, 19, SignStyle.NORMAL, 0, "1000000000000000000", 0, 19, 1000000000000000000L}, + {1, 19, SignStyle.NORMAL, 0, "-1000000000000000000", 0, 20, -1000000000000000000L}, + {1, 19, SignStyle.NORMAL, 0, "000000000000000000", 0, 18, 0L}, + {1, 19, SignStyle.NORMAL, 0, "0000000000000000000", 0, 19, 0L}, + {1, 19, SignStyle.NORMAL, 0, "9223372036854775807", 0, 19, Long.MAX_VALUE}, + {1, 19, SignStyle.NORMAL, 0, "-9223372036854775808", 0, 20, Long.MIN_VALUE}, + {1, 19, SignStyle.NORMAL, 0, "9223372036854775808", 0, 18, 922337203685477580L}, // last digit not parsed + {1, 19, SignStyle.NORMAL, 0, "-9223372036854775809", 0, 19, -922337203685477580L}, // last digit not parsed + // no match + {1, 2, SignStyle.NEVER, 1, "A1", 0, 0, 0}, + {1, 2, SignStyle.NEVER, 1, " 1", 0, 0, 0}, + {1, 2, SignStyle.NEVER, 1, " 1", 1, 1, 0}, + {2, 2, SignStyle.NEVER, 1, "1", 0, 0, 0}, + {2, 2, SignStyle.NEVER, 1, "Xxx1", 0, 0, 0}, + {2, 2, SignStyle.NEVER, 1, "1", 1, 1, 0}, + {2, 2, SignStyle.NEVER, 1, "Xxx1", 4, 4, 0}, + {2, 2, SignStyle.NEVER, 1, "1-2", 0, 0, 0}, + {1, 19, SignStyle.NORMAL, 0, "-000000000000000000", 0, 0, 0}, + {1, 19, SignStyle.NORMAL, 0, "-0000000000000000000", 0, 0, 0}, + // parse reserving space 1 (adjacent-parsing) + {1, 1, SignStyle.NEVER, 1, "12", 0, 1, 1L}, + {1, 19, SignStyle.NEVER, 1, "12", 0, 1, 1L}, + {1, 19, SignStyle.NEVER, 1, "12345", 0, 4, 1234L}, + {1, 19, SignStyle.NEVER, 1, "12345678901", 0, 10, 1234567890L}, + {1, 19, SignStyle.NEVER, 1, "123456789012345678901234567890", 0, 19, 1234567890123456789L}, + {1, 19, SignStyle.NEVER, 1, "1", 0, 1, 1L}, // error from next field + {2, 2, SignStyle.NEVER, 1, "12", 0, 2, 12L}, // error from next field + {2, 19, SignStyle.NEVER, 1, "1", 0, 0, 0}, + // parse reserving space 2 (adjacent-parsing) + {1, 1, SignStyle.NEVER, 2, "123", 0, 1, 1L}, + {1, 19, SignStyle.NEVER, 2, "123", 0, 1, 1L}, + {1, 19, SignStyle.NEVER, 2, "12345", 0, 3, 123L}, + {1, 19, SignStyle.NEVER, 2, "12345678901", 0, 9, 123456789L}, + {1, 19, SignStyle.NEVER, 2, "123456789012345678901234567890", 0, 19, 1234567890123456789L}, + {1, 19, SignStyle.NEVER, 2, "1", 0, 1, 1L}, // error from next field + {1, 19, SignStyle.NEVER, 2, "12", 0, 1, 1L}, // error from next field + {2, 2, SignStyle.NEVER, 2, "12", 0, 2, 12L}, // error from next field + {2, 19, SignStyle.NEVER, 2, "1", 0, 0, 0}, + {2, 19, SignStyle.NEVER, 2, "1AAAAABBBBBCCCCC", 0, 0, 0}, + }; + } + + //----------------------------------------------------------------------- + @Test(dataProvider="parseData") + public void test_parse_fresh(int minWidth, int maxWidth, SignStyle signStyle, int subsequentWidth, String text, int pos, int expectedPos, long expectedValue) { + ParsePosition ppos = new ParsePosition(pos); + DateTimeFormatter dtf = getFormatter(DAY_OF_MONTH, minWidth, maxWidth, signStyle); + if (subsequentWidth > 0) { + // hacky, to reserve space + dtf = builder.appendValue(DAY_OF_YEAR, subsequentWidth).toFormatter(locale).withSymbols(symbols); + } + DateTimeBuilder dtb = dtf.parseToBuilder(text, ppos); + if (ppos.getErrorIndex() != -1) { + assertEquals(ppos.getErrorIndex(), expectedPos); + } else { + assertTrue(subsequentWidth >= 0); + assertEquals(ppos.getIndex(), expectedPos + subsequentWidth); + assertEquals(dtb.getLong(DAY_OF_MONTH), expectedValue); + } + } + + @Test(dataProvider="parseData") + public void test_parse_textField(int minWidth, int maxWidth, SignStyle signStyle, int subsequentWidth, String text, int pos, int expectedPos, long expectedValue) { + ParsePosition ppos = new ParsePosition(pos); + DateTimeFormatter dtf = getFormatter(DAY_OF_WEEK, minWidth, maxWidth, signStyle); + if (subsequentWidth > 0) { + // hacky, to reserve space + dtf = builder.appendValue(DAY_OF_YEAR, subsequentWidth).toFormatter(locale).withSymbols(symbols); + } + DateTimeBuilder dtb = dtf.parseToBuilder(text, ppos); + if (ppos.getErrorIndex() != -1) { + assertEquals(ppos.getErrorIndex(), expectedPos); + } else { + assertTrue(subsequentWidth >= 0); + assertEquals(ppos.getIndex(), expectedPos + subsequentWidth); + assertEquals(dtb.getLong(DAY_OF_WEEK), expectedValue); + } + } + + //----------------------------------------------------------------------- + @DataProvider(name="parseSignsStrict") + Object[][] provider_parseSignsStrict() { + return new Object[][] { + // basics + {"0", 1, 2, SignStyle.NEVER, 1, 0}, + {"1", 1, 2, SignStyle.NEVER, 1, 1}, + {"2", 1, 2, SignStyle.NEVER, 1, 2}, + {"3", 1, 2, SignStyle.NEVER, 1, 3}, + {"4", 1, 2, SignStyle.NEVER, 1, 4}, + {"5", 1, 2, SignStyle.NEVER, 1, 5}, + {"6", 1, 2, SignStyle.NEVER, 1, 6}, + {"7", 1, 2, SignStyle.NEVER, 1, 7}, + {"8", 1, 2, SignStyle.NEVER, 1, 8}, + {"9", 1, 2, SignStyle.NEVER, 1, 9}, + {"10", 1, 2, SignStyle.NEVER, 2, 10}, + {"100", 1, 2, SignStyle.NEVER, 2, 10}, + {"100", 1, 3, SignStyle.NEVER, 3, 100}, + + // never + {"0", 1, 2, SignStyle.NEVER, 1, 0}, + {"5", 1, 2, SignStyle.NEVER, 1, 5}, + {"50", 1, 2, SignStyle.NEVER, 2, 50}, + {"500", 1, 2, SignStyle.NEVER, 2, 50}, + {"-0", 1, 2, SignStyle.NEVER, 0, null}, + {"-5", 1, 2, SignStyle.NEVER, 0, null}, + {"-50", 1, 2, SignStyle.NEVER, 0, null}, + {"-500", 1, 2, SignStyle.NEVER, 0, null}, + {"-AAA", 1, 2, SignStyle.NEVER, 0, null}, + {"+0", 1, 2, SignStyle.NEVER, 0, null}, + {"+5", 1, 2, SignStyle.NEVER, 0, null}, + {"+50", 1, 2, SignStyle.NEVER, 0, null}, + {"+500", 1, 2, SignStyle.NEVER, 0, null}, + {"+AAA", 1, 2, SignStyle.NEVER, 0, null}, + + // not negative + {"0", 1, 2, SignStyle.NOT_NEGATIVE, 1, 0}, + {"5", 1, 2, SignStyle.NOT_NEGATIVE, 1, 5}, + {"50", 1, 2, SignStyle.NOT_NEGATIVE, 2, 50}, + {"500", 1, 2, SignStyle.NOT_NEGATIVE, 2, 50}, + {"-0", 1, 2, SignStyle.NOT_NEGATIVE, 0, null}, + {"-5", 1, 2, SignStyle.NOT_NEGATIVE, 0, null}, + {"-50", 1, 2, SignStyle.NOT_NEGATIVE, 0, null}, + {"-500", 1, 2, SignStyle.NOT_NEGATIVE, 0, null}, + {"-AAA", 1, 2, SignStyle.NOT_NEGATIVE, 0, null}, + {"+0", 1, 2, SignStyle.NOT_NEGATIVE, 0, null}, + {"+5", 1, 2, SignStyle.NOT_NEGATIVE, 0, null}, + {"+50", 1, 2, SignStyle.NOT_NEGATIVE, 0, null}, + {"+500", 1, 2, SignStyle.NOT_NEGATIVE, 0, null}, + {"+AAA", 1, 2, SignStyle.NOT_NEGATIVE, 0, null}, + + // normal + {"0", 1, 2, SignStyle.NORMAL, 1, 0}, + {"5", 1, 2, SignStyle.NORMAL, 1, 5}, + {"50", 1, 2, SignStyle.NORMAL, 2, 50}, + {"500", 1, 2, SignStyle.NORMAL, 2, 50}, + {"-0", 1, 2, SignStyle.NORMAL, 0, null}, + {"-5", 1, 2, SignStyle.NORMAL, 2, -5}, + {"-50", 1, 2, SignStyle.NORMAL, 3, -50}, + {"-500", 1, 2, SignStyle.NORMAL, 3, -50}, + {"-AAA", 1, 2, SignStyle.NORMAL, 1, null}, + {"+0", 1, 2, SignStyle.NORMAL, 0, null}, + {"+5", 1, 2, SignStyle.NORMAL, 0, null}, + {"+50", 1, 2, SignStyle.NORMAL, 0, null}, + {"+500", 1, 2, SignStyle.NORMAL, 0, null}, + {"+AAA", 1, 2, SignStyle.NORMAL, 0, null}, + + // always + {"0", 1, 2, SignStyle.ALWAYS, 0, null}, + {"5", 1, 2, SignStyle.ALWAYS, 0, null}, + {"50", 1, 2, SignStyle.ALWAYS, 0, null}, + {"500", 1, 2, SignStyle.ALWAYS, 0, null}, + {"-0", 1, 2, SignStyle.ALWAYS, 0, null}, + {"-5", 1, 2, SignStyle.ALWAYS, 2, -5}, + {"-50", 1, 2, SignStyle.ALWAYS, 3, -50}, + {"-500", 1, 2, SignStyle.ALWAYS, 3, -50}, + {"-AAA", 1, 2, SignStyle.ALWAYS, 1, null}, + {"+0", 1, 2, SignStyle.ALWAYS, 2, 0}, + {"+5", 1, 2, SignStyle.ALWAYS, 2, 5}, + {"+50", 1, 2, SignStyle.ALWAYS, 3, 50}, + {"+500", 1, 2, SignStyle.ALWAYS, 3, 50}, + {"+AAA", 1, 2, SignStyle.ALWAYS, 1, null}, + + // exceeds pad + {"0", 1, 2, SignStyle.EXCEEDS_PAD, 1, 0}, + {"5", 1, 2, SignStyle.EXCEEDS_PAD, 1, 5}, + {"50", 1, 2, SignStyle.EXCEEDS_PAD, 0, null}, + {"500", 1, 2, SignStyle.EXCEEDS_PAD, 0, null}, + {"-0", 1, 2, SignStyle.EXCEEDS_PAD, 0, null}, + {"-5", 1, 2, SignStyle.EXCEEDS_PAD, 2, -5}, + {"-50", 1, 2, SignStyle.EXCEEDS_PAD, 3, -50}, + {"-500", 1, 2, SignStyle.EXCEEDS_PAD, 3, -50}, + {"-AAA", 1, 2, SignStyle.EXCEEDS_PAD, 1, null}, + {"+0", 1, 2, SignStyle.EXCEEDS_PAD, 0, null}, + {"+5", 1, 2, SignStyle.EXCEEDS_PAD, 0, null}, + {"+50", 1, 2, SignStyle.EXCEEDS_PAD, 3, 50}, + {"+500", 1, 2, SignStyle.EXCEEDS_PAD, 3, 50}, + {"+AAA", 1, 2, SignStyle.EXCEEDS_PAD, 1, null}, + }; + } + + @Test(dataProvider="parseSignsStrict") + public void test_parseSignsStrict(String input, int min, int max, SignStyle style, int parseLen, Integer parseVal) throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter(DAY_OF_MONTH, min, max, style).parseToBuilder(input, pos); + if (pos.getErrorIndex() != -1) { + assertEquals(pos.getErrorIndex(), parseLen); + } else { + assertEquals(pos.getIndex(), parseLen); + assertEquals(dtb.getLong(DAY_OF_MONTH), (long)parseVal); + } + } + + //----------------------------------------------------------------------- + @DataProvider(name="parseSignsLenient") + Object[][] provider_parseSignsLenient() { + return new Object[][] { + // never + {"0", 1, 2, SignStyle.NEVER, 1, 0}, + {"5", 1, 2, SignStyle.NEVER, 1, 5}, + {"50", 1, 2, SignStyle.NEVER, 2, 50}, + {"500", 1, 2, SignStyle.NEVER, 2, 50}, + {"-0", 1, 2, SignStyle.NEVER, 2, 0}, + {"-5", 1, 2, SignStyle.NEVER, 2, -5}, + {"-50", 1, 2, SignStyle.NEVER, 3, -50}, + {"-500", 1, 2, SignStyle.NEVER, 3, -50}, + {"-AAA", 1, 2, SignStyle.NEVER, 1, null}, + {"+0", 1, 2, SignStyle.NEVER, 2, 0}, + {"+5", 1, 2, SignStyle.NEVER, 2, 5}, + {"+50", 1, 2, SignStyle.NEVER, 3, 50}, + {"+500", 1, 2, SignStyle.NEVER, 3, 50}, + {"+AAA", 1, 2, SignStyle.NEVER, 1, null}, + {"50", 2, 2, SignStyle.NEVER, 2, 50}, + {"-50", 2, 2, SignStyle.NEVER, 0, null}, + {"+50", 2, 2, SignStyle.NEVER, 0, null}, + + // not negative + {"0", 1, 2, SignStyle.NOT_NEGATIVE, 1, 0}, + {"5", 1, 2, SignStyle.NOT_NEGATIVE, 1, 5}, + {"50", 1, 2, SignStyle.NOT_NEGATIVE, 2, 50}, + {"500", 1, 2, SignStyle.NOT_NEGATIVE, 2, 50}, + {"-0", 1, 2, SignStyle.NOT_NEGATIVE, 2, 0}, + {"-5", 1, 2, SignStyle.NOT_NEGATIVE, 2, -5}, + {"-50", 1, 2, SignStyle.NOT_NEGATIVE, 3, -50}, + {"-500", 1, 2, SignStyle.NOT_NEGATIVE, 3, -50}, + {"-AAA", 1, 2, SignStyle.NOT_NEGATIVE, 1, null}, + {"+0", 1, 2, SignStyle.NOT_NEGATIVE, 2, 0}, + {"+5", 1, 2, SignStyle.NOT_NEGATIVE, 2, 5}, + {"+50", 1, 2, SignStyle.NOT_NEGATIVE, 3, 50}, + {"+500", 1, 2, SignStyle.NOT_NEGATIVE, 3, 50}, + {"+AAA", 1, 2, SignStyle.NOT_NEGATIVE, 1, null}, + {"50", 2, 2, SignStyle.NOT_NEGATIVE, 2, 50}, + {"-50", 2, 2, SignStyle.NOT_NEGATIVE, 0, null}, + {"+50", 2, 2, SignStyle.NOT_NEGATIVE, 0, null}, + + // normal + {"0", 1, 2, SignStyle.NORMAL, 1, 0}, + {"5", 1, 2, SignStyle.NORMAL, 1, 5}, + {"50", 1, 2, SignStyle.NORMAL, 2, 50}, + {"500", 1, 2, SignStyle.NORMAL, 2, 50}, + {"-0", 1, 2, SignStyle.NORMAL, 2, 0}, + {"-5", 1, 2, SignStyle.NORMAL, 2, -5}, + {"-50", 1, 2, SignStyle.NORMAL, 3, -50}, + {"-500", 1, 2, SignStyle.NORMAL, 3, -50}, + {"-AAA", 1, 2, SignStyle.NORMAL, 1, null}, + {"+0", 1, 2, SignStyle.NORMAL, 2, 0}, + {"+5", 1, 2, SignStyle.NORMAL, 2, 5}, + {"+50", 1, 2, SignStyle.NORMAL, 3, 50}, + {"+500", 1, 2, SignStyle.NORMAL, 3, 50}, + {"+AAA", 1, 2, SignStyle.NORMAL, 1, null}, + {"50", 2, 2, SignStyle.NORMAL, 2, 50}, + {"-50", 2, 2, SignStyle.NORMAL, 3, -50}, + {"+50", 2, 2, SignStyle.NORMAL, 3, 50}, + + // always + {"0", 1, 2, SignStyle.ALWAYS, 1, 0}, + {"5", 1, 2, SignStyle.ALWAYS, 1, 5}, + {"50", 1, 2, SignStyle.ALWAYS, 2, 50}, + {"500", 1, 2, SignStyle.ALWAYS, 2, 50}, + {"-0", 1, 2, SignStyle.ALWAYS, 2, 0}, + {"-5", 1, 2, SignStyle.ALWAYS, 2, -5}, + {"-50", 1, 2, SignStyle.ALWAYS, 3, -50}, + {"-500", 1, 2, SignStyle.ALWAYS, 3, -50}, + {"-AAA", 1, 2, SignStyle.ALWAYS, 1, null}, + {"+0", 1, 2, SignStyle.ALWAYS, 2, 0}, + {"+5", 1, 2, SignStyle.ALWAYS, 2, 5}, + {"+50", 1, 2, SignStyle.ALWAYS, 3, 50}, + {"+500", 1, 2, SignStyle.ALWAYS, 3, 50}, + {"+AAA", 1, 2, SignStyle.ALWAYS, 1, null}, + + // exceeds pad + {"0", 1, 2, SignStyle.EXCEEDS_PAD, 1, 0}, + {"5", 1, 2, SignStyle.EXCEEDS_PAD, 1, 5}, + {"50", 1, 2, SignStyle.EXCEEDS_PAD, 2, 50}, + {"500", 1, 2, SignStyle.EXCEEDS_PAD, 2, 50}, + {"-0", 1, 2, SignStyle.EXCEEDS_PAD, 2, 0}, + {"-5", 1, 2, SignStyle.EXCEEDS_PAD, 2, -5}, + {"-50", 1, 2, SignStyle.EXCEEDS_PAD, 3, -50}, + {"-500", 1, 2, SignStyle.EXCEEDS_PAD, 3, -50}, + {"-AAA", 1, 2, SignStyle.EXCEEDS_PAD, 1, null}, + {"+0", 1, 2, SignStyle.EXCEEDS_PAD, 2, 0}, + {"+5", 1, 2, SignStyle.EXCEEDS_PAD, 2, 5}, + {"+50", 1, 2, SignStyle.EXCEEDS_PAD, 3, 50}, + {"+500", 1, 2, SignStyle.EXCEEDS_PAD, 3, 50}, + {"+AAA", 1, 2, SignStyle.EXCEEDS_PAD, 1, null}, + }; + } + + @Test(dataProvider="parseSignsLenient") + public void test_parseSignsLenient(String input, int min, int max, SignStyle style, int parseLen, Integer parseVal) throws Exception { + setStrict(false); + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter(DAY_OF_MONTH, min, max, style).parseToBuilder(input, pos); + if (pos.getErrorIndex() != -1) { + assertEquals(pos.getErrorIndex(), parseLen); + } else { + assertEquals(pos.getIndex(), parseLen); + assertEquals(dtb.getLong(DAY_OF_MONTH), (long)parseVal); + } + } + + //----------------------------------------------------------------------- + @DataProvider(name="parseDigitsLenient") + Object[][] provider_parseDigitsLenient() { + return new Object[][] { + // never + {"5", 1, 2, SignStyle.NEVER, 1, 5}, + {"5", 2, 2, SignStyle.NEVER, 1, 5}, + {"54", 1, 3, SignStyle.NEVER, 2, 54}, + {"54", 2, 3, SignStyle.NEVER, 2, 54}, + {"54", 3, 3, SignStyle.NEVER, 2, 54}, + {"543", 1, 3, SignStyle.NEVER, 3, 543}, + {"543", 2, 3, SignStyle.NEVER, 3, 543}, + {"543", 3, 3, SignStyle.NEVER, 3, 543}, + {"5432", 1, 3, SignStyle.NEVER, 3, 543}, + {"5432", 2, 3, SignStyle.NEVER, 3, 543}, + {"5432", 3, 3, SignStyle.NEVER, 3, 543}, + {"5AAA", 2, 3, SignStyle.NEVER, 1, 5}, + + // not negative + {"5", 1, 2, SignStyle.NOT_NEGATIVE, 1, 5}, + {"5", 2, 2, SignStyle.NOT_NEGATIVE, 1, 5}, + {"54", 1, 3, SignStyle.NOT_NEGATIVE, 2, 54}, + {"54", 2, 3, SignStyle.NOT_NEGATIVE, 2, 54}, + {"54", 3, 3, SignStyle.NOT_NEGATIVE, 2, 54}, + {"543", 1, 3, SignStyle.NOT_NEGATIVE, 3, 543}, + {"543", 2, 3, SignStyle.NOT_NEGATIVE, 3, 543}, + {"543", 3, 3, SignStyle.NOT_NEGATIVE, 3, 543}, + {"5432", 1, 3, SignStyle.NOT_NEGATIVE, 3, 543}, + {"5432", 2, 3, SignStyle.NOT_NEGATIVE, 3, 543}, + {"5432", 3, 3, SignStyle.NOT_NEGATIVE, 3, 543}, + {"5AAA", 2, 3, SignStyle.NOT_NEGATIVE, 1, 5}, + + // normal + {"5", 1, 2, SignStyle.NORMAL, 1, 5}, + {"5", 2, 2, SignStyle.NORMAL, 1, 5}, + {"54", 1, 3, SignStyle.NORMAL, 2, 54}, + {"54", 2, 3, SignStyle.NORMAL, 2, 54}, + {"54", 3, 3, SignStyle.NORMAL, 2, 54}, + {"543", 1, 3, SignStyle.NORMAL, 3, 543}, + {"543", 2, 3, SignStyle.NORMAL, 3, 543}, + {"543", 3, 3, SignStyle.NORMAL, 3, 543}, + {"5432", 1, 3, SignStyle.NORMAL, 3, 543}, + {"5432", 2, 3, SignStyle.NORMAL, 3, 543}, + {"5432", 3, 3, SignStyle.NORMAL, 3, 543}, + {"5AAA", 2, 3, SignStyle.NORMAL, 1, 5}, + + // always + {"5", 1, 2, SignStyle.ALWAYS, 1, 5}, + {"5", 2, 2, SignStyle.ALWAYS, 1, 5}, + {"54", 1, 3, SignStyle.ALWAYS, 2, 54}, + {"54", 2, 3, SignStyle.ALWAYS, 2, 54}, + {"54", 3, 3, SignStyle.ALWAYS, 2, 54}, + {"543", 1, 3, SignStyle.ALWAYS, 3, 543}, + {"543", 2, 3, SignStyle.ALWAYS, 3, 543}, + {"543", 3, 3, SignStyle.ALWAYS, 3, 543}, + {"5432", 1, 3, SignStyle.ALWAYS, 3, 543}, + {"5432", 2, 3, SignStyle.ALWAYS, 3, 543}, + {"5432", 3, 3, SignStyle.ALWAYS, 3, 543}, + {"5AAA", 2, 3, SignStyle.ALWAYS, 1, 5}, + + // exceeds pad + {"5", 1, 2, SignStyle.EXCEEDS_PAD, 1, 5}, + {"5", 2, 2, SignStyle.EXCEEDS_PAD, 1, 5}, + {"54", 1, 3, SignStyle.EXCEEDS_PAD, 2, 54}, + {"54", 2, 3, SignStyle.EXCEEDS_PAD, 2, 54}, + {"54", 3, 3, SignStyle.EXCEEDS_PAD, 2, 54}, + {"543", 1, 3, SignStyle.EXCEEDS_PAD, 3, 543}, + {"543", 2, 3, SignStyle.EXCEEDS_PAD, 3, 543}, + {"543", 3, 3, SignStyle.EXCEEDS_PAD, 3, 543}, + {"5432", 1, 3, SignStyle.EXCEEDS_PAD, 3, 543}, + {"5432", 2, 3, SignStyle.EXCEEDS_PAD, 3, 543}, + {"5432", 3, 3, SignStyle.EXCEEDS_PAD, 3, 543}, + {"5AAA", 2, 3, SignStyle.EXCEEDS_PAD, 1, 5}, + }; + } + + @Test(dataProvider="parseDigitsLenient") + public void test_parseDigitsLenient(String input, int min, int max, SignStyle style, int parseLen, Integer parseVal) throws Exception { + setStrict(false); + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter(DAY_OF_MONTH, min, max, style).parseToBuilder(input, pos); + if (pos.getErrorIndex() != -1) { + assertEquals(pos.getErrorIndex(), parseLen); + } else { + assertEquals(pos.getIndex(), parseLen); + assertEquals(dtb.getLong(DAY_OF_MONTH), (long)parseVal); + } + } + + //----------------------------------------------------------------------- + @DataProvider(name="parseDigitsAdjacentLenient") + Object[][] provider_parseDigitsAdjacentLenient() { + return new Object[][] { + // never + {"5", 1, null, null}, + {"54", 1, null, null}, + + {"543", 3, 5, 43}, + {"543A", 3, 5, 43}, + + {"5432", 4, 54, 32}, + {"5432A", 4, 54, 32}, + + {"54321", 4, 54, 32}, + {"54321A", 4, 54, 32}, + }; + } + + @Test(dataProvider="parseDigitsAdjacentLenient") + public void test_parseDigitsAdjacentLenient(String input, int parseLen, Integer parseMonth, Integer parsedDay) throws Exception { + setStrict(false); + ParsePosition pos = new ParsePosition(0); + DateTimeFormatter f = builder + .appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL) + .appendValue(DAY_OF_MONTH, 2).toFormatter(locale).withSymbols(symbols); + DateTimeBuilder dtb = f.parseToBuilder(input, pos); + if (pos.getErrorIndex() != -1) { + assertEquals(pos.getErrorIndex(), parseLen); + } else { + assertEquals(pos.getIndex(), parseLen); + assertEquals(dtb.getLong(MONTH_OF_YEAR), (long) parseMonth); + assertEquals(dtb.getLong(DAY_OF_MONTH), (long) parsedDay); + } + } + +} diff --git a/test/java/time/test/java/time/format/TestNumberPrinter.java b/test/java/time/test/java/time/format/TestNumberPrinter.java new file mode 100644 index 0000000000000000000000000000000000000000..80c05b8eb9fc4654992139285274e815acbf5e79 --- /dev/null +++ b/test/java/time/test/java/time/format/TestNumberPrinter.java @@ -0,0 +1,283 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import java.time.format.*; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +import java.time.DateTimeException; +import java.time.LocalDate; +import test.java.time.temporal.MockFieldValue; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test SimpleNumberPrinterParser. + */ +@Test +public class TestNumberPrinter extends AbstractTestPrinterParser { + + //----------------------------------------------------------------------- + @Test(expectedExceptions=DateTimeException.class) + public void test_print_emptyCalendrical() throws Exception { + getFormatter(DAY_OF_MONTH, 1, 2, SignStyle.NEVER).printTo(EMPTY_DTA, buf); + } + + public void test_print_append() throws Exception { + buf.append("EXISTING"); + getFormatter(DAY_OF_MONTH, 1, 2, SignStyle.NEVER).printTo(LocalDate.of(2012, 1, 3), buf); + assertEquals(buf.toString(), "EXISTING3"); + } + + //----------------------------------------------------------------------- + @DataProvider(name="Pad") + Object[][] provider_pad() { + return new Object[][] { + {1, 1, -10, null}, + {1, 1, -9, "9"}, + {1, 1, -1, "1"}, + {1, 1, 0, "0"}, + {1, 1, 3, "3"}, + {1, 1, 9, "9"}, + {1, 1, 10, null}, + + {1, 2, -100, null}, + {1, 2, -99, "99"}, + {1, 2, -10, "10"}, + {1, 2, -9, "9"}, + {1, 2, -1, "1"}, + {1, 2, 0, "0"}, + {1, 2, 3, "3"}, + {1, 2, 9, "9"}, + {1, 2, 10, "10"}, + {1, 2, 99, "99"}, + {1, 2, 100, null}, + + {2, 2, -100, null}, + {2, 2, -99, "99"}, + {2, 2, -10, "10"}, + {2, 2, -9, "09"}, + {2, 2, -1, "01"}, + {2, 2, 0, "00"}, + {2, 2, 3, "03"}, + {2, 2, 9, "09"}, + {2, 2, 10, "10"}, + {2, 2, 99, "99"}, + {2, 2, 100, null}, + + {1, 3, -1000, null}, + {1, 3, -999, "999"}, + {1, 3, -100, "100"}, + {1, 3, -99, "99"}, + {1, 3, -10, "10"}, + {1, 3, -9, "9"}, + {1, 3, -1, "1"}, + {1, 3, 0, "0"}, + {1, 3, 3, "3"}, + {1, 3, 9, "9"}, + {1, 3, 10, "10"}, + {1, 3, 99, "99"}, + {1, 3, 100, "100"}, + {1, 3, 999, "999"}, + {1, 3, 1000, null}, + + {2, 3, -1000, null}, + {2, 3, -999, "999"}, + {2, 3, -100, "100"}, + {2, 3, -99, "99"}, + {2, 3, -10, "10"}, + {2, 3, -9, "09"}, + {2, 3, -1, "01"}, + {2, 3, 0, "00"}, + {2, 3, 3, "03"}, + {2, 3, 9, "09"}, + {2, 3, 10, "10"}, + {2, 3, 99, "99"}, + {2, 3, 100, "100"}, + {2, 3, 999, "999"}, + {2, 3, 1000, null}, + + {3, 3, -1000, null}, + {3, 3, -999, "999"}, + {3, 3, -100, "100"}, + {3, 3, -99, "099"}, + {3, 3, -10, "010"}, + {3, 3, -9, "009"}, + {3, 3, -1, "001"}, + {3, 3, 0, "000"}, + {3, 3, 3, "003"}, + {3, 3, 9, "009"}, + {3, 3, 10, "010"}, + {3, 3, 99, "099"}, + {3, 3, 100, "100"}, + {3, 3, 999, "999"}, + {3, 3, 1000, null}, + + {1, 10, Integer.MAX_VALUE - 1, "2147483646"}, + {1, 10, Integer.MAX_VALUE, "2147483647"}, + {1, 10, Integer.MIN_VALUE + 1, "2147483647"}, + {1, 10, Integer.MIN_VALUE, "2147483648"}, + }; + } + + @Test(dataProvider="Pad") + public void test_pad_NOT_NEGATIVE(int minPad, int maxPad, long value, String result) throws Exception { + try { + getFormatter(DAY_OF_MONTH, minPad, maxPad, SignStyle.NOT_NEGATIVE).printTo(new MockFieldValue(DAY_OF_MONTH, value), buf); + if (result == null || value < 0) { + fail("Expected exception"); + } + assertEquals(buf.toString(), result); + } catch (DateTimePrintException ex) { + if (result == null || value < 0) { + assertEquals(ex.getMessage().contains(DAY_OF_MONTH.getName()), true); + } else { + throw ex; + } + } + } + + @Test(dataProvider="Pad") + public void test_pad_NEVER(int minPad, int maxPad, long value, String result) throws Exception { + try { + getFormatter(DAY_OF_MONTH, minPad, maxPad, SignStyle.NEVER).printTo(new MockFieldValue(DAY_OF_MONTH, value), buf); + if (result == null) { + fail("Expected exception"); + } + assertEquals(buf.toString(), result); + } catch (DateTimePrintException ex) { + if (result != null) { + throw ex; + } + assertEquals(ex.getMessage().contains(DAY_OF_MONTH.getName()), true); + } + } + + @Test(dataProvider="Pad") + public void test_pad_NORMAL(int minPad, int maxPad, long value, String result) throws Exception { + try { + getFormatter(DAY_OF_MONTH, minPad, maxPad, SignStyle.NORMAL).printTo(new MockFieldValue(DAY_OF_MONTH, value), buf); + if (result == null) { + fail("Expected exception"); + } + assertEquals(buf.toString(), (value < 0 ? "-" + result : result)); + } catch (DateTimePrintException ex) { + if (result != null) { + throw ex; + } + assertEquals(ex.getMessage().contains(DAY_OF_MONTH.getName()), true); + } + } + + @Test(dataProvider="Pad") + public void test_pad_ALWAYS(int minPad, int maxPad, long value, String result) throws Exception { + try { + getFormatter(DAY_OF_MONTH, minPad, maxPad, SignStyle.ALWAYS).printTo(new MockFieldValue(DAY_OF_MONTH, value), buf); + if (result == null) { + fail("Expected exception"); + } + assertEquals(buf.toString(), (value < 0 ? "-" + result : "+" + result)); + } catch (DateTimePrintException ex) { + if (result != null) { + throw ex; + } + assertEquals(ex.getMessage().contains(DAY_OF_MONTH.getName()), true); + } + } + + @Test(dataProvider="Pad") + public void test_pad_EXCEEDS_PAD(int minPad, int maxPad, long value, String result) throws Exception { + try { + getFormatter(DAY_OF_MONTH, minPad, maxPad, SignStyle.EXCEEDS_PAD).printTo(new MockFieldValue(DAY_OF_MONTH, value), buf); + if (result == null) { + fail("Expected exception"); + return; // unreachable + } + if (result.length() > minPad || value < 0) { + result = (value < 0 ? "-" + result : "+" + result); + } + assertEquals(buf.toString(), result); + } catch (DateTimePrintException ex) { + if (result != null) { + throw ex; + } + assertEquals(ex.getMessage().contains(DAY_OF_MONTH.getName()), true); + } + } + + //----------------------------------------------------------------------- + public void test_toString1() throws Exception { + assertEquals(getFormatter(HOUR_OF_DAY, 1, 19, SignStyle.NORMAL).toString(), "Value(HourOfDay)"); + } + + public void test_toString2() throws Exception { + assertEquals(getFormatter(HOUR_OF_DAY, 2, 2, SignStyle.NOT_NEGATIVE).toString(), "Value(HourOfDay,2)"); + } + + public void test_toString3() throws Exception { + assertEquals(getFormatter(HOUR_OF_DAY, 1, 2, SignStyle.NOT_NEGATIVE).toString(), "Value(HourOfDay,1,2,NOT_NEGATIVE)"); + } + +} diff --git a/test/java/time/test/java/time/format/TestPadParserDecorator.java b/test/java/time/test/java/time/format/TestPadParserDecorator.java new file mode 100644 index 0000000000000000000000000000000000000000..08a9e24544d1c645a433d34ddfb2d09028d9caac --- /dev/null +++ b/test/java/time/test/java/time/format/TestPadParserDecorator.java @@ -0,0 +1,140 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import java.time.format.*; + +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static org.testng.Assert.assertEquals; + +import java.text.ParsePosition; +import java.time.format.DateTimeBuilder; + +import org.testng.annotations.Test; + +/** + * Test PadPrinterParserDecorator. + */ +@Test(groups={"implementation"}) +public class TestPadParserDecorator extends AbstractTestPrinterParser { + + //----------------------------------------------------------------------- + @Test(expectedExceptions=IndexOutOfBoundsException.class) + public void test_parse_negativePosition() throws Exception { + builder.padNext(3, '-').appendLiteral('Z'); + getFormatter().parseToBuilder("--Z", new ParsePosition(-1)); + } + + @Test(expectedExceptions=IndexOutOfBoundsException.class) + public void test_parse_offEndPosition() throws Exception { + builder.padNext(3, '-').appendLiteral('Z'); + getFormatter().parseToBuilder("--Z", new ParsePosition(4)); + } + + //----------------------------------------------------------------------- + public void test_parse() throws Exception { + ParsePosition pos = new ParsePosition(0); + builder.padNext(3, '-').appendValue(MONTH_OF_YEAR, 1, 3, SignStyle.NEVER); + DateTimeBuilder dtb = getFormatter().parseToBuilder("--2", pos); + assertEquals(pos.getIndex(), 3); + assertEquals(dtb.getFieldValueMap().size(), 1); + assertEquals(dtb.getLong(MONTH_OF_YEAR), 2L); + } + + public void test_parse_noReadBeyond() throws Exception { + ParsePosition pos = new ParsePosition(0); + builder.padNext(3, '-').appendValue(MONTH_OF_YEAR, 1, 3, SignStyle.NEVER); + DateTimeBuilder dtb = getFormatter().parseToBuilder("--22", pos); + assertEquals(pos.getIndex(), 3); + assertEquals(dtb.getFieldValueMap().size(), 1); + assertEquals(dtb.getLong(MONTH_OF_YEAR), 2L); + } + + public void test_parse_textLessThanPadWidth() throws Exception { + ParsePosition pos = new ParsePosition(0); + builder.padNext(3, '-').appendValue(MONTH_OF_YEAR, 1, 3, SignStyle.NEVER); + DateTimeBuilder dtb = getFormatter().parseToBuilder("-1", pos); + assertEquals(pos.getErrorIndex(), 0); + } + + public void test_parse_decoratedErrorPassedBack() throws Exception { + ParsePosition pos = new ParsePosition(0); + builder.padNext(3, '-').appendValue(MONTH_OF_YEAR, 1, 3, SignStyle.NEVER); + DateTimeBuilder dtb = getFormatter().parseToBuilder("--A", pos); + assertEquals(pos.getErrorIndex(), 2); + } + + public void test_parse_decoratedDidNotParseToPadWidth() throws Exception { + ParsePosition pos = new ParsePosition(0); + builder.padNext(3, '-').appendValue(MONTH_OF_YEAR, 1, 3, SignStyle.NEVER); + DateTimeBuilder dtb = getFormatter().parseToBuilder("-1X", pos); + assertEquals(pos.getErrorIndex(), 0); + } + + //----------------------------------------------------------------------- + public void test_parse_decoratedStartsWithPad() throws Exception { + ParsePosition pos = new ParsePosition(0); + builder.padNext(8, '-').appendLiteral("-HELLO-"); + DateTimeBuilder dtb = getFormatter().parseToBuilder("--HELLO-", pos); + assertEquals(pos.getIndex(), 8); + assertEquals(dtb.getFieldValueMap().size(), 0); + } + +} diff --git a/test/java/time/test/java/time/format/TestPadPrinterDecorator.java b/test/java/time/test/java/time/format/TestPadPrinterDecorator.java new file mode 100644 index 0000000000000000000000000000000000000000..d6302aee4d74958c8d19ea252895d79f056f02f3 --- /dev/null +++ b/test/java/time/test/java/time/format/TestPadPrinterDecorator.java @@ -0,0 +1,138 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import java.time.format.*; + +import static org.testng.Assert.assertEquals; + +import java.time.LocalDate; + +import org.testng.annotations.Test; + +/** + * Test PadPrinterDecorator. + */ +@Test(groups={"implementation"}) +public class TestPadPrinterDecorator extends AbstractTestPrinterParser { + + //----------------------------------------------------------------------- + public void test_print_emptyCalendrical() throws Exception { + builder.padNext(3, '-').appendLiteral('Z'); + getFormatter().printTo(EMPTY_DTA, buf); + assertEquals(buf.toString(), "--Z"); + } + + public void test_print_fullDateTime() throws Exception { + builder.padNext(3, '-').appendLiteral('Z'); + getFormatter().printTo(LocalDate.of(2008, 12, 3), buf); + assertEquals(buf.toString(), "--Z"); + } + + public void test_print_append() throws Exception { + buf.append("EXISTING"); + builder.padNext(3, '-').appendLiteral('Z'); + getFormatter().printTo(EMPTY_DTA, buf); + assertEquals(buf.toString(), "EXISTING--Z"); + } + + //----------------------------------------------------------------------- + public void test_print_noPadRequiredSingle() throws Exception { + builder.padNext(1, '-').appendLiteral('Z'); + getFormatter().printTo(EMPTY_DTA, buf); + assertEquals(buf.toString(), "Z"); + } + + public void test_print_padRequiredSingle() throws Exception { + builder.padNext(5, '-').appendLiteral('Z'); + getFormatter().printTo(EMPTY_DTA, buf); + assertEquals(buf.toString(), "----Z"); + } + + public void test_print_noPadRequiredMultiple() throws Exception { + builder.padNext(4, '-').appendLiteral("WXYZ"); + getFormatter().printTo(EMPTY_DTA, buf); + assertEquals(buf.toString(), "WXYZ"); + } + + public void test_print_padRequiredMultiple() throws Exception { + builder.padNext(5, '-').appendLiteral("WXYZ"); + getFormatter().printTo(EMPTY_DTA, buf); + assertEquals(buf.toString(), "-WXYZ"); + } + + @Test(expectedExceptions=DateTimePrintException.class) + public void test_print_overPad() throws Exception { + builder.padNext(3, '-').appendLiteral("WXYZ"); + getFormatter().printTo(EMPTY_DTA, buf); + } + + //----------------------------------------------------------------------- + public void test_toString1() throws Exception { + builder.padNext(5, ' ').appendLiteral('Y'); + assertEquals(getFormatter().toString(), "Pad('Y',5)"); + } + + public void test_toString2() throws Exception { + builder.padNext(5, '-').appendLiteral('Y'); + assertEquals(getFormatter().toString(), "Pad('Y',5,'-')"); + } + +} diff --git a/test/java/time/test/java/time/format/TestReducedParser.java b/test/java/time/test/java/time/format/TestReducedParser.java new file mode 100644 index 0000000000000000000000000000000000000000..bcd383039ff5f9a10e4242f7f7825eb0606f97ee --- /dev/null +++ b/test/java/time/test/java/time/format/TestReducedParser.java @@ -0,0 +1,198 @@ +/* + * 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. + * + * 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) 2010-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 test.java.time.format; + +import java.time.format.*; + +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.text.ParsePosition; +import java.time.format.DateTimeBuilder; +import java.time.temporal.TemporalField; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test ReducedPrinterParser. + */ +@Test(groups={"implementation"}) +public class TestReducedParser extends AbstractTestPrinterParser { + + private DateTimeFormatter getFormatter0(TemporalField field, int width, int baseValue) { + return builder.appendValueReduced(field, width, baseValue).toFormatter(locale).withSymbols(symbols); + } + + //----------------------------------------------------------------------- + @DataProvider(name="error") + Object[][] data_error() { + return new Object[][] { + {YEAR, 2, 2010, "12", -1, IndexOutOfBoundsException.class}, + {YEAR, 2, 2010, "12", 3, IndexOutOfBoundsException.class}, + }; + } + + @Test(dataProvider="error") + public void test_parse_error(TemporalField field, int width, int baseValue, String text, int pos, Class expected) { + try { + getFormatter0(field, width, baseValue).parseToBuilder(text, new ParsePosition(pos)); + } catch (RuntimeException ex) { + assertTrue(expected.isInstance(ex)); + } + } + + //----------------------------------------------------------------------- + public void test_parse_fieldRangeIgnored() throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter0(DAY_OF_YEAR, 3, 10).parseToBuilder("456", pos); + assertEquals(pos.getIndex(), 3); + assertParsed(dtb, DAY_OF_YEAR, 456L); // parsed dayOfYear=456 + } + + //----------------------------------------------------------------------- + @DataProvider(name="Parse") + Object[][] provider_parse() { + return new Object[][] { + // negative zero + {YEAR, 1, 2010, "-0", 0, 0, null}, + + // general + {YEAR, 2, 2010, "Xxx12Xxx", 3, 5, 2012}, + {YEAR, 2, 2010, "12345", 0, 2, 2012}, + {YEAR, 2, 2010, "12-45", 0, 2, 2012}, + + // insufficient digits + {YEAR, 2, 2010, "0", 0, 0, null}, + {YEAR, 2, 2010, "1", 0, 0, null}, + {YEAR, 2, 2010, "1", 1, 1, null}, + {YEAR, 2, 2010, "1-2", 0, 0, null}, + {YEAR, 2, 2010, "9", 0, 0, null}, + + // other junk + {YEAR, 2, 2010, "A0", 0, 0, null}, + {YEAR, 2, 2010, "0A", 0, 0, null}, + {YEAR, 2, 2010, " 1", 0, 0, null}, + {YEAR, 2, 2010, "-1", 0, 0, null}, + {YEAR, 2, 2010, "-10", 0, 0, null}, + + // parse OK 1 + {YEAR, 1, 2010, "0", 0, 1, 2010}, + {YEAR, 1, 2010, "9", 0, 1, 2019}, + {YEAR, 1, 2010, "10", 0, 1, 2011}, + + {YEAR, 1, 2005, "0", 0, 1, 2010}, + {YEAR, 1, 2005, "4", 0, 1, 2014}, + {YEAR, 1, 2005, "5", 0, 1, 2005}, + {YEAR, 1, 2005, "9", 0, 1, 2009}, + {YEAR, 1, 2005, "10", 0, 1, 2011}, + + // parse OK 2 + {YEAR, 2, 2010, "00", 0, 2, 2100}, + {YEAR, 2, 2010, "09", 0, 2, 2109}, + {YEAR, 2, 2010, "10", 0, 2, 2010}, + {YEAR, 2, 2010, "99", 0, 2, 2099}, + {YEAR, 2, 2010, "100", 0, 2, 2010}, + + // parse OK 2 + {YEAR, 2, -2005, "05", 0, 2, -2005}, + {YEAR, 2, -2005, "00", 0, 2, -2000}, + {YEAR, 2, -2005, "99", 0, 2, -1999}, + {YEAR, 2, -2005, "06", 0, 2, -1906}, + {YEAR, 2, -2005, "100", 0, 2, -1910}, + }; + } + + @Test(dataProvider="Parse") + public void test_parse(TemporalField field, int width, int baseValue, String input, int pos, int parseLen, Integer parseVal) { + ParsePosition ppos = new ParsePosition(pos); + DateTimeBuilder dtb = getFormatter0(field, width, baseValue).parseToBuilder(input, ppos); + if (ppos.getErrorIndex() != -1) { + assertEquals(ppos.getErrorIndex(), parseLen); + } else { + assertEquals(ppos.getIndex(), parseLen); + assertParsed(dtb, YEAR, parseVal != null ? (long) parseVal : null); + } + } + + @Test(dataProvider="Parse") + public void test_parseLenient(TemporalField field, int width, int baseValue, String input, int pos, int parseLen, Integer parseVal) { + setStrict(false); + ParsePosition ppos = new ParsePosition(pos); + DateTimeBuilder dtb = getFormatter0(field, width, baseValue).parseToBuilder(input, ppos); + if (ppos.getErrorIndex() != -1) { + assertEquals(ppos.getErrorIndex(), parseLen); + } else { + assertEquals(ppos.getIndex(), parseLen); + assertParsed(dtb, YEAR, parseVal != null ? (long) parseVal : null); + } + } + + private void assertParsed(DateTimeBuilder dtb, TemporalField field, Long value) { + if (value == null) { + assertEquals(dtb, null); + } else { + assertEquals(dtb.getLong(field), (long)value); + } + } + +} diff --git a/test/java/time/test/java/time/format/TestReducedPrinter.java b/test/java/time/test/java/time/format/TestReducedPrinter.java new file mode 100644 index 0000000000000000000000000000000000000000..698496dc57e6a4225de3944f463465745df17a9f --- /dev/null +++ b/test/java/time/test/java/time/format/TestReducedPrinter.java @@ -0,0 +1,181 @@ +/* + * 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. + * + * 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) 2010-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 test.java.time.format; + +import java.time.format.*; + +import static java.time.temporal.ChronoField.YEAR; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.temporal.TemporalField; +import test.java.time.temporal.MockFieldValue; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test ReducedPrinterParser. + */ +@Test(groups={"implementation"}) +public class TestReducedPrinter extends AbstractTestPrinterParser { + + private DateTimeFormatter getFormatter0(TemporalField field, int width, int baseValue) { + return builder.appendValueReduced(field, width, baseValue).toFormatter(locale).withSymbols(symbols); + } + + //----------------------------------------------------------------------- + @Test(expectedExceptions=DateTimeException.class) + public void test_print_emptyCalendrical() throws Exception { + getFormatter0(YEAR, 2, 2010).printTo(EMPTY_DTA, buf); + } + + //----------------------------------------------------------------------- + public void test_print_append() throws Exception { + buf.append("EXISTING"); + getFormatter0(YEAR, 2, 2010).printTo(LocalDate.of(2012, 1, 1), buf); + assertEquals(buf.toString(), "EXISTING12"); + } + + //----------------------------------------------------------------------- + @DataProvider(name="Pivot") + Object[][] provider_pivot() { + return new Object[][] { + {1, 2010, 2010, "0"}, + {1, 2010, 2011, "1"}, + {1, 2010, 2012, "2"}, + {1, 2010, 2013, "3"}, + {1, 2010, 2014, "4"}, + {1, 2010, 2015, "5"}, + {1, 2010, 2016, "6"}, + {1, 2010, 2017, "7"}, + {1, 2010, 2018, "8"}, + {1, 2010, 2019, "9"}, + {1, 2010, 2009, "9"}, + {1, 2010, 2020, "0"}, + + {2, 2010, 2010, "10"}, + {2, 2010, 2011, "11"}, + {2, 2010, 2021, "21"}, + {2, 2010, 2099, "99"}, + {2, 2010, 2100, "00"}, + {2, 2010, 2109, "09"}, + {2, 2010, 2009, "09"}, + {2, 2010, 2110, "10"}, + + {2, 2005, 2005, "05"}, + {2, 2005, 2099, "99"}, + {2, 2005, 2100, "00"}, + {2, 2005, 2104, "04"}, + {2, 2005, 2004, "04"}, + {2, 2005, 2105, "05"}, + + {3, 2005, 2005, "005"}, + {3, 2005, 2099, "099"}, + {3, 2005, 2100, "100"}, + {3, 2005, 2999, "999"}, + {3, 2005, 3000, "000"}, + {3, 2005, 3004, "004"}, + {3, 2005, 2004, "004"}, + {3, 2005, 3005, "005"}, + + {9, 2005, 2005, "000002005"}, + {9, 2005, 2099, "000002099"}, + {9, 2005, 2100, "000002100"}, + {9, 2005, 999999999, "999999999"}, + {9, 2005, 1000000000, "000000000"}, + {9, 2005, 1000002004, "000002004"}, + {9, 2005, 2004, "000002004"}, + {9, 2005, 1000002005, "000002005"}, + + {2, -2005, -2005, "05"}, + {2, -2005, -2000, "00"}, + {2, -2005, -1999, "99"}, + {2, -2005, -1904, "04"}, + {2, -2005, -2006, "06"}, + {2, -2005, -1905, "05"}, + }; + } + + @Test(dataProvider="Pivot") + public void test_pivot(int width, int baseValue, int value, String result) throws Exception { + try { + getFormatter0(YEAR, width, baseValue).printTo(new MockFieldValue(YEAR, value), buf); + if (result == null) { + fail("Expected exception"); + } + assertEquals(buf.toString(), result); + } catch (DateTimePrintException ex) { + if (result == null || value < 0) { + assertEquals(ex.getMessage().contains(YEAR.getName()), true); + } else { + throw ex; + } + } + } + + //----------------------------------------------------------------------- + public void test_toString() throws Exception { + assertEquals(getFormatter0(YEAR, 2, 2005).toString(), "ReducedValue(Year,2,2005)"); + } + +} diff --git a/test/java/time/test/java/time/format/TestSettingsParser.java b/test/java/time/test/java/time/format/TestSettingsParser.java new file mode 100644 index 0000000000000000000000000000000000000000..4b639aa56d48f04427edde8b4db9294307ab609c --- /dev/null +++ b/test/java/time/test/java/time/format/TestSettingsParser.java @@ -0,0 +1,147 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import java.time.format.*; + +import static org.testng.Assert.assertEquals; + +import java.text.ParsePosition; +import java.time.ZoneOffset; + +import org.testng.annotations.Test; + +/** + * Test SettingsParser. + */ +@Test(groups={"implementation"}) +public class TestSettingsParser extends AbstractTestPrinterParser { + + //----------------------------------------------------------------------- + public void test_print_sensitive() throws Exception { + setCaseSensitive(true); + getFormatter().printTo(dta, buf); + assertEquals(buf.toString(), ""); + } + + public void test_print_strict() throws Exception { + setStrict(true); + getFormatter().printTo(dta, buf); + assertEquals(buf.toString(), ""); + } + + /* + public void test_print_nulls() throws Exception { + setCaseSensitive(true); + getFormatter().printTo(null, null); + } + */ + + //----------------------------------------------------------------------- + public void test_parse_changeStyle_sensitive() throws Exception { + setCaseSensitive(true); + ParsePosition pos = new ParsePosition(0); + getFormatter().parseToBuilder("a", pos); + assertEquals(pos.getIndex(), 0); + } + + public void test_parse_changeStyle_insensitive() throws Exception { + setCaseSensitive(false); + ParsePosition pos = new ParsePosition(0); + getFormatter().parseToBuilder("a", pos); + assertEquals(pos.getIndex(), 0); + } + + public void test_parse_changeStyle_strict() throws Exception { + setStrict(true); + ParsePosition pos = new ParsePosition(0); + getFormatter().parseToBuilder("a", pos); + assertEquals(pos.getIndex(), 0); + } + + public void test_parse_changeStyle_lenient() throws Exception { + setStrict(false); + ParsePosition pos = new ParsePosition(0); + getFormatter().parseToBuilder("a", pos); + assertEquals(pos.getIndex(), 0); + } + + //----------------------------------------------------------------------- + public void test_toString_sensitive() throws Exception { + setCaseSensitive(true); + assertEquals(getFormatter().toString(), "ParseCaseSensitive(true)"); + } + + public void test_toString_insensitive() throws Exception { + setCaseSensitive(false); + assertEquals(getFormatter().toString(), "ParseCaseSensitive(false)"); + } + + public void test_toString_strict() throws Exception { + setStrict(true); + assertEquals(getFormatter().toString(), "ParseStrict(true)"); + } + + public void test_toString_lenient() throws Exception { + setStrict(false); + assertEquals(getFormatter().toString(), "ParseStrict(false)"); + } + +} diff --git a/test/java/time/test/java/time/format/TestStringLiteralParser.java b/test/java/time/test/java/time/format/TestStringLiteralParser.java new file mode 100644 index 0000000000000000000000000000000000000000..bd67eab39f397c2b00bdc543350fc3515baf2d6b --- /dev/null +++ b/test/java/time/test/java/time/format/TestStringLiteralParser.java @@ -0,0 +1,137 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.time.format.DateTimeBuilder; +import java.text.ParsePosition; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test StringLiteralPrinterParser. + */ +@Test(groups={"implementation"}) +public class TestStringLiteralParser extends AbstractTestPrinterParser { + + @DataProvider(name="success") + Object[][] data_success() { + return new Object[][] { + // match + {"hello", true, "hello", 0, 5}, + {"hello", true, "helloOTHER", 0, 5}, + {"hello", true, "OTHERhelloOTHER", 5, 10}, + {"hello", true, "OTHERhello", 5, 10}, + + // no match + {"hello", true, "", 0, 0}, + {"hello", true, "a", 1, 1}, + {"hello", true, "HELLO", 0, 0}, + {"hello", true, "hlloo", 0, 0}, + {"hello", true, "OTHERhllooOTHER", 5, 5}, + {"hello", true, "OTHERhlloo", 5, 5}, + {"hello", true, "h", 0, 0}, + {"hello", true, "OTHERh", 5, 5}, + + // case insensitive + {"hello", false, "hello", 0, 5}, + {"hello", false, "HELLO", 0, 5}, + {"hello", false, "HelLo", 0, 5}, + {"hello", false, "HelLO", 0, 5}, + }; + } + + @Test(dataProvider="success") + public void test_parse_success(String s, boolean caseSensitive, String text, int pos, int expectedPos) { + setCaseSensitive(caseSensitive); + ParsePosition ppos = new ParsePosition(pos); + DateTimeBuilder result = + getFormatter(s).parseToBuilder(text, ppos); + if (ppos.getErrorIndex() != -1) { + assertEquals(ppos.getIndex(), expectedPos); + } else { + assertEquals(ppos.getIndex(), expectedPos); + assertEquals(result.getCalendricalList().size(), 0); + } + } + + //----------------------------------------------------------------------- + @DataProvider(name="error") + Object[][] data_error() { + return new Object[][] { + {"hello", "hello", -1, IndexOutOfBoundsException.class}, + {"hello", "hello", 6, IndexOutOfBoundsException.class}, + }; + } + + @Test(dataProvider="error") + public void test_parse_error(String s, String text, int pos, Class expected) { + try { + DateTimeBuilder result = + getFormatter(s).parseToBuilder(text, new ParsePosition(pos)); + assertTrue(false); + } catch (RuntimeException ex) { + assertTrue(expected.isInstance(ex)); + } + } +} diff --git a/test/java/time/test/java/time/format/TestStringLiteralPrinter.java b/test/java/time/test/java/time/format/TestStringLiteralPrinter.java new file mode 100644 index 0000000000000000000000000000000000000000..df018701b3c0136eefc01becf235e61940fac9c8 --- /dev/null +++ b/test/java/time/test/java/time/format/TestStringLiteralPrinter.java @@ -0,0 +1,104 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import java.time.format.*; + +import static org.testng.Assert.assertEquals; + +import org.testng.annotations.Test; + +/** + * Test StringLiteralPrinterParser. + */ +@Test(groups={"implementation"}) +public class TestStringLiteralPrinter extends AbstractTestPrinterParser { + + //----------------------------------------------------------------------- + public void test_print_emptyCalendrical() throws Exception { + buf.append("EXISTING"); + getFormatter("hello").printTo(EMPTY_DTA, buf); + assertEquals(buf.toString(), "EXISTINGhello"); + } + + public void test_print_dateTime() throws Exception { + buf.append("EXISTING"); + getFormatter("hello").printTo(dta, buf); + assertEquals(buf.toString(), "EXISTINGhello"); + } + + + + + public void test_print_emptyAppendable() throws Exception { + getFormatter("hello").printTo(dta, buf); + assertEquals(buf.toString(), "hello"); + } + + //----------------------------------------------------------------------- + public void test_toString() throws Exception { + assertEquals(getFormatter("hello").toString(), "'hello'"); + } + + public void test_toString_apos() throws Exception { + assertEquals(getFormatter("o'clock").toString(), "'o''clock'"); + } + +} diff --git a/test/java/time/test/java/time/format/TestTextParser.java b/test/java/time/test/java/time/format/TestTextParser.java new file mode 100644 index 0000000000000000000000000000000000000000..7961b27ac8b12804b7789f5da0f1140c1fda668a --- /dev/null +++ b/test/java/time/test/java/time/format/TestTextParser.java @@ -0,0 +1,341 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import java.time.format.*; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.text.ParsePosition; +import java.util.Locale; +import java.time.format.DateTimeBuilder; +import java.time.temporal.TemporalField; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test TextPrinterParser. + */ +@Test(groups={"implementation"}) +public class TestTextParser extends AbstractTestPrinterParser { + + //----------------------------------------------------------------------- + @DataProvider(name="error") + Object[][] data_error() { + return new Object[][] { + {DAY_OF_WEEK, TextStyle.FULL, "Monday", -1, IndexOutOfBoundsException.class}, + {DAY_OF_WEEK, TextStyle.FULL, "Monday", 7, IndexOutOfBoundsException.class}, + }; + } + + @Test(dataProvider="error") + public void test_parse_error(TemporalField field, TextStyle style, String text, int pos, Class expected) { + try { + getFormatter(field, style).parseToBuilder(text, new ParsePosition(pos)); + } catch (RuntimeException ex) { + assertTrue(expected.isInstance(ex)); + } + } + + //----------------------------------------------------------------------- + public void test_parse_midStr() throws Exception { + ParsePosition pos = new ParsePosition(3); + assertEquals(getFormatter(DAY_OF_WEEK, TextStyle.FULL) + .parseToBuilder("XxxMondayXxx", pos) + .getLong(DAY_OF_WEEK), 1L); + assertEquals(pos.getIndex(), 9); + } + + public void test_parse_remainderIgnored() throws Exception { + ParsePosition pos = new ParsePosition(0); + assertEquals(getFormatter(DAY_OF_WEEK, TextStyle.SHORT) + .parseToBuilder("Wednesday", pos) + .getLong(DAY_OF_WEEK), 3L); + assertEquals(pos.getIndex(), 3); + } + + //----------------------------------------------------------------------- + public void test_parse_noMatch1() throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = + getFormatter(DAY_OF_WEEK, TextStyle.FULL).parseToBuilder("Munday", pos); + assertEquals(pos.getErrorIndex(), 0); + } + + public void test_parse_noMatch2() throws Exception { + ParsePosition pos = new ParsePosition(3); + DateTimeBuilder dtb = + getFormatter(DAY_OF_WEEK, TextStyle.FULL).parseToBuilder("Monday", pos); + assertEquals(pos.getErrorIndex(), 3); + } + + public void test_parse_noMatch_atEnd() throws Exception { + ParsePosition pos = new ParsePosition(6); + DateTimeBuilder dtb = + getFormatter(DAY_OF_WEEK, TextStyle.FULL).parseToBuilder("Monday", pos); + assertEquals(pos.getErrorIndex(), 6); + } + + //----------------------------------------------------------------------- + @DataProvider(name="parseText") + Object[][] provider_text() { + return new Object[][] { + {DAY_OF_WEEK, TextStyle.FULL, 1, "Monday"}, + {DAY_OF_WEEK, TextStyle.FULL, 2, "Tuesday"}, + {DAY_OF_WEEK, TextStyle.FULL, 3, "Wednesday"}, + {DAY_OF_WEEK, TextStyle.FULL, 4, "Thursday"}, + {DAY_OF_WEEK, TextStyle.FULL, 5, "Friday"}, + {DAY_OF_WEEK, TextStyle.FULL, 6, "Saturday"}, + {DAY_OF_WEEK, TextStyle.FULL, 7, "Sunday"}, + + {DAY_OF_WEEK, TextStyle.SHORT, 1, "Mon"}, + {DAY_OF_WEEK, TextStyle.SHORT, 2, "Tue"}, + {DAY_OF_WEEK, TextStyle.SHORT, 3, "Wed"}, + {DAY_OF_WEEK, TextStyle.SHORT, 4, "Thu"}, + {DAY_OF_WEEK, TextStyle.SHORT, 5, "Fri"}, + {DAY_OF_WEEK, TextStyle.SHORT, 6, "Sat"}, + {DAY_OF_WEEK, TextStyle.SHORT, 7, "Sun"}, + + {MONTH_OF_YEAR, TextStyle.FULL, 1, "January"}, + {MONTH_OF_YEAR, TextStyle.FULL, 12, "December"}, + + {MONTH_OF_YEAR, TextStyle.SHORT, 1, "Jan"}, + {MONTH_OF_YEAR, TextStyle.SHORT, 12, "Dec"}, + }; + } + + @DataProvider(name="parseNumber") + Object[][] provider_number() { + return new Object[][] { + {DAY_OF_MONTH, TextStyle.FULL, 1, "1"}, + {DAY_OF_MONTH, TextStyle.FULL, 2, "2"}, + {DAY_OF_MONTH, TextStyle.FULL, 30, "30"}, + {DAY_OF_MONTH, TextStyle.FULL, 31, "31"}, + + {DAY_OF_MONTH, TextStyle.SHORT, 1, "1"}, + {DAY_OF_MONTH, TextStyle.SHORT, 2, "2"}, + {DAY_OF_MONTH, TextStyle.SHORT, 30, "30"}, + {DAY_OF_MONTH, TextStyle.SHORT, 31, "31"}, + }; + } + + @Test(dataProvider="parseText") + public void test_parseText(TemporalField field, TextStyle style, int value, String input) throws Exception { + ParsePosition pos = new ParsePosition(0); + assertEquals(getFormatter(field, style).parseToBuilder(input, pos).getLong(field), (long) value); + assertEquals(pos.getIndex(), input.length()); + } + + @Test(dataProvider="parseNumber") + public void test_parseNumber(TemporalField field, TextStyle style, int value, String input) throws Exception { + ParsePosition pos = new ParsePosition(0); + assertEquals(getFormatter(field, style).parseToBuilder(input, pos).getLong(field), (long) value); + assertEquals(pos.getIndex(), input.length()); + } + + //----------------------------------------------------------------------- + @Test(dataProvider="parseText") + public void test_parse_strict_caseSensitive_parseUpper(TemporalField field, TextStyle style, int value, String input) throws Exception { + setCaseSensitive(true); + ParsePosition pos = new ParsePosition(0); + getFormatter(field, style).parseToBuilder(input.toUpperCase(), pos); + assertEquals(pos.getErrorIndex(), 0); + } + + @Test(dataProvider="parseText") + public void test_parse_strict_caseInsensitive_parseUpper(TemporalField field, TextStyle style, int value, String input) throws Exception { + setCaseSensitive(false); + ParsePosition pos = new ParsePosition(0); + assertEquals(getFormatter(field, style).parseToBuilder(input.toUpperCase(), pos).getLong(field), (long) value); + assertEquals(pos.getIndex(), input.length()); + } + + //----------------------------------------------------------------------- + @Test(dataProvider="parseText") + public void test_parse_strict_caseSensitive_parseLower(TemporalField field, TextStyle style, int value, String input) throws Exception { + setCaseSensitive(true); + ParsePosition pos = new ParsePosition(0); + getFormatter(field, style).parseToBuilder(input.toLowerCase(), pos); + assertEquals(pos.getErrorIndex(), 0); + } + + @Test(dataProvider="parseText") + public void test_parse_strict_caseInsensitive_parseLower(TemporalField field, TextStyle style, int value, String input) throws Exception { + setCaseSensitive(false); + ParsePosition pos = new ParsePosition(0); + assertEquals(getFormatter(field, style).parseToBuilder(input.toLowerCase(), pos).getLong(field), (long) value); + assertEquals(pos.getIndex(), input.length()); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + public void test_parse_full_strict_full_match() throws Exception { + setStrict(true); + ParsePosition pos = new ParsePosition(0); + assertEquals(getFormatter(MONTH_OF_YEAR, TextStyle.FULL).parseToBuilder("January", pos).getLong(MONTH_OF_YEAR), 1L); + assertEquals(pos.getIndex(), 7); + } + + public void test_parse_full_strict_short_noMatch() throws Exception { + setStrict(true); + ParsePosition pos = new ParsePosition(0); + getFormatter(MONTH_OF_YEAR, TextStyle.FULL).parseToBuilder("Janua", pos); + assertEquals(pos.getErrorIndex(), 0); + } + + public void test_parse_full_strict_number_noMatch() throws Exception { + setStrict(true); + ParsePosition pos = new ParsePosition(0); + getFormatter(MONTH_OF_YEAR, TextStyle.FULL).parseToBuilder("1", pos); + assertEquals(pos.getErrorIndex(), 0); + } + + //----------------------------------------------------------------------- + public void test_parse_short_strict_full_match() throws Exception { + setStrict(true); + ParsePosition pos = new ParsePosition(0); + assertEquals(getFormatter(MONTH_OF_YEAR, TextStyle.SHORT).parseToBuilder("January", pos).getLong(MONTH_OF_YEAR), 1L); + assertEquals(pos.getIndex(), 3); + } + + public void test_parse_short_strict_short_match() throws Exception { + setStrict(true); + ParsePosition pos = new ParsePosition(0); + assertEquals(getFormatter(MONTH_OF_YEAR, TextStyle.SHORT).parseToBuilder("Janua", pos).getLong(MONTH_OF_YEAR), 1L); + assertEquals(pos.getIndex(), 3); + } + + public void test_parse_short_strict_number_noMatch() throws Exception { + setStrict(true); + ParsePosition pos = new ParsePosition(0); + getFormatter(MONTH_OF_YEAR, TextStyle.SHORT).parseToBuilder("1", pos); + assertEquals(pos.getErrorIndex(), 0); + } + + //----------------------------------------------------------------------- + public void test_parse_french_short_strict_full_noMatch() throws Exception { + setStrict(true); + ParsePosition pos = new ParsePosition(0); + getFormatter(MONTH_OF_YEAR, TextStyle.SHORT).withLocale(Locale.FRENCH) + .parseToBuilder("janvier", pos); + assertEquals(pos.getErrorIndex(), 0); + } + + public void test_parse_french_short_strict_short_match() throws Exception { + setStrict(true); + ParsePosition pos = new ParsePosition(0); + assertEquals(getFormatter(MONTH_OF_YEAR, TextStyle.SHORT).withLocale(Locale.FRENCH) + .parseToBuilder("janv.", pos) + .getLong(MONTH_OF_YEAR), + 1L); + assertEquals(pos.getIndex(), 5); + } + + //----------------------------------------------------------------------- + public void test_parse_full_lenient_full_match() throws Exception { + setStrict(false); + ParsePosition pos = new ParsePosition(0); + assertEquals(getFormatter(MONTH_OF_YEAR, TextStyle.FULL).parseToBuilder("January.", pos).getLong(MONTH_OF_YEAR), 1L); + assertEquals(pos.getIndex(), 7); + } + + public void test_parse_full_lenient_short_match() throws Exception { + setStrict(false); + ParsePosition pos = new ParsePosition(0); + assertEquals(getFormatter(MONTH_OF_YEAR, TextStyle.FULL).parseToBuilder("Janua", pos).getLong(MONTH_OF_YEAR), 1L); + assertEquals(pos.getIndex(), 3); + } + + public void test_parse_full_lenient_number_match() throws Exception { + setStrict(false); + ParsePosition pos = new ParsePosition(0); + assertEquals(getFormatter(MONTH_OF_YEAR, TextStyle.FULL).parseToBuilder("1", pos).getLong(MONTH_OF_YEAR), 1L); + assertEquals(pos.getIndex(), 1); + } + + //----------------------------------------------------------------------- + public void test_parse_short_lenient_full_match() throws Exception { + setStrict(false); + ParsePosition pos = new ParsePosition(0); + assertEquals(getFormatter(MONTH_OF_YEAR, TextStyle.SHORT).parseToBuilder("January", pos).getLong(MONTH_OF_YEAR), 1L); + assertEquals(pos.getIndex(), 7); + } + + public void test_parse_short_lenient_short_match() throws Exception { + setStrict(false); + ParsePosition pos = new ParsePosition(0); + assertEquals(getFormatter(MONTH_OF_YEAR, TextStyle.SHORT).parseToBuilder("Janua", pos).getLong(MONTH_OF_YEAR), 1L); + assertEquals(pos.getIndex(), 3); + } + + public void test_parse_short_lenient_number_match() throws Exception { + setStrict(false); + ParsePosition pos = new ParsePosition(0); + assertEquals(getFormatter(MONTH_OF_YEAR, TextStyle.SHORT).parseToBuilder("1", pos).getLong(MONTH_OF_YEAR), 1L); + assertEquals(pos.getIndex(), 1); + } + +} diff --git a/test/java/time/test/java/time/format/TestTextPrinter.java b/test/java/time/test/java/time/format/TestTextPrinter.java new file mode 100644 index 0000000000000000000000000000000000000000..cc93f461a78dc5cd403e640a415cb2b51a1cab8e --- /dev/null +++ b/test/java/time/test/java/time/format/TestTextPrinter.java @@ -0,0 +1,195 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import java.time.format.*; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static org.testng.Assert.assertEquals; + +import java.util.Locale; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.temporal.TemporalField; +import test.java.time.temporal.MockFieldValue; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test TextPrinterParser. + */ +@Test(groups={"implementation"}) +public class TestTextPrinter extends AbstractTestPrinterParser { + + //----------------------------------------------------------------------- + @Test(expectedExceptions=DateTimeException.class) + public void test_print_emptyCalendrical() throws Exception { + getFormatter(DAY_OF_WEEK, TextStyle.FULL).printTo(EMPTY_DTA, buf); + } + + public void test_print_append() throws Exception { + buf.append("EXISTING"); + getFormatter(DAY_OF_WEEK, TextStyle.FULL).printTo(LocalDate.of(2012, 4, 18), buf); + assertEquals(buf.toString(), "EXISTINGWednesday"); + } + + //----------------------------------------------------------------------- + @DataProvider(name="print") + Object[][] provider_dow() { + return new Object[][] { + {DAY_OF_WEEK, TextStyle.FULL, 1, "Monday"}, + {DAY_OF_WEEK, TextStyle.FULL, 2, "Tuesday"}, + {DAY_OF_WEEK, TextStyle.FULL, 3, "Wednesday"}, + {DAY_OF_WEEK, TextStyle.FULL, 4, "Thursday"}, + {DAY_OF_WEEK, TextStyle.FULL, 5, "Friday"}, + {DAY_OF_WEEK, TextStyle.FULL, 6, "Saturday"}, + {DAY_OF_WEEK, TextStyle.FULL, 7, "Sunday"}, + + {DAY_OF_WEEK, TextStyle.SHORT, 1, "Mon"}, + {DAY_OF_WEEK, TextStyle.SHORT, 2, "Tue"}, + {DAY_OF_WEEK, TextStyle.SHORT, 3, "Wed"}, + {DAY_OF_WEEK, TextStyle.SHORT, 4, "Thu"}, + {DAY_OF_WEEK, TextStyle.SHORT, 5, "Fri"}, + {DAY_OF_WEEK, TextStyle.SHORT, 6, "Sat"}, + {DAY_OF_WEEK, TextStyle.SHORT, 7, "Sun"}, + + {DAY_OF_WEEK, TextStyle.NARROW, 1, "M"}, + {DAY_OF_WEEK, TextStyle.NARROW, 2, "T"}, + {DAY_OF_WEEK, TextStyle.NARROW, 3, "W"}, + {DAY_OF_WEEK, TextStyle.NARROW, 4, "T"}, + {DAY_OF_WEEK, TextStyle.NARROW, 5, "F"}, + {DAY_OF_WEEK, TextStyle.NARROW, 6, "S"}, + {DAY_OF_WEEK, TextStyle.NARROW, 7, "S"}, + + {DAY_OF_MONTH, TextStyle.FULL, 1, "1"}, + {DAY_OF_MONTH, TextStyle.FULL, 2, "2"}, + {DAY_OF_MONTH, TextStyle.FULL, 3, "3"}, + {DAY_OF_MONTH, TextStyle.FULL, 28, "28"}, + {DAY_OF_MONTH, TextStyle.FULL, 29, "29"}, + {DAY_OF_MONTH, TextStyle.FULL, 30, "30"}, + {DAY_OF_MONTH, TextStyle.FULL, 31, "31"}, + + {DAY_OF_MONTH, TextStyle.SHORT, 1, "1"}, + {DAY_OF_MONTH, TextStyle.SHORT, 2, "2"}, + {DAY_OF_MONTH, TextStyle.SHORT, 3, "3"}, + {DAY_OF_MONTH, TextStyle.SHORT, 28, "28"}, + {DAY_OF_MONTH, TextStyle.SHORT, 29, "29"}, + {DAY_OF_MONTH, TextStyle.SHORT, 30, "30"}, + {DAY_OF_MONTH, TextStyle.SHORT, 31, "31"}, + + {MONTH_OF_YEAR, TextStyle.FULL, 1, "January"}, + {MONTH_OF_YEAR, TextStyle.FULL, 2, "February"}, + {MONTH_OF_YEAR, TextStyle.FULL, 3, "March"}, + {MONTH_OF_YEAR, TextStyle.FULL, 4, "April"}, + {MONTH_OF_YEAR, TextStyle.FULL, 5, "May"}, + {MONTH_OF_YEAR, TextStyle.FULL, 6, "June"}, + {MONTH_OF_YEAR, TextStyle.FULL, 7, "July"}, + {MONTH_OF_YEAR, TextStyle.FULL, 8, "August"}, + {MONTH_OF_YEAR, TextStyle.FULL, 9, "September"}, + {MONTH_OF_YEAR, TextStyle.FULL, 10, "October"}, + {MONTH_OF_YEAR, TextStyle.FULL, 11, "November"}, + {MONTH_OF_YEAR, TextStyle.FULL, 12, "December"}, + + {MONTH_OF_YEAR, TextStyle.SHORT, 1, "Jan"}, + {MONTH_OF_YEAR, TextStyle.SHORT, 2, "Feb"}, + {MONTH_OF_YEAR, TextStyle.SHORT, 3, "Mar"}, + {MONTH_OF_YEAR, TextStyle.SHORT, 4, "Apr"}, + {MONTH_OF_YEAR, TextStyle.SHORT, 5, "May"}, + {MONTH_OF_YEAR, TextStyle.SHORT, 6, "Jun"}, + {MONTH_OF_YEAR, TextStyle.SHORT, 7, "Jul"}, + {MONTH_OF_YEAR, TextStyle.SHORT, 8, "Aug"}, + {MONTH_OF_YEAR, TextStyle.SHORT, 9, "Sep"}, + {MONTH_OF_YEAR, TextStyle.SHORT, 10, "Oct"}, + {MONTH_OF_YEAR, TextStyle.SHORT, 11, "Nov"}, + {MONTH_OF_YEAR, TextStyle.SHORT, 12, "Dec"}, + }; + } + + @Test(dataProvider="print") + public void test_print(TemporalField field, TextStyle style, int value, String expected) throws Exception { + getFormatter(field, style).printTo(new MockFieldValue(field, value), buf); + assertEquals(buf.toString(), expected); + } + + //----------------------------------------------------------------------- + public void test_print_french_long() throws Exception { + getFormatter(MONTH_OF_YEAR, TextStyle.FULL).withLocale(Locale.FRENCH).printTo(LocalDate.of(2012, 1, 1), buf); + assertEquals(buf.toString(), "janvier"); + } + + public void test_print_french_short() throws Exception { + getFormatter(MONTH_OF_YEAR, TextStyle.SHORT).withLocale(Locale.FRENCH).printTo(LocalDate.of(2012, 1, 1), buf); + assertEquals(buf.toString(), "janv."); + } + + //----------------------------------------------------------------------- + public void test_toString1() throws Exception { + assertEquals(getFormatter(MONTH_OF_YEAR, TextStyle.FULL).toString(), "Text(MonthOfYear)"); + } + + public void test_toString2() throws Exception { + assertEquals(getFormatter(MONTH_OF_YEAR, TextStyle.SHORT).toString(), "Text(MonthOfYear,SHORT)"); + } + +} diff --git a/test/java/time/test/java/time/format/TestZoneIdParser.java b/test/java/time/test/java/time/format/TestZoneIdParser.java new file mode 100644 index 0000000000000000000000000000000000000000..99ee93bb52ee1ea19f1eb7b05e53cd412ff420ca --- /dev/null +++ b/test/java/time/test/java/time/format/TestZoneIdParser.java @@ -0,0 +1,245 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.util.Set; + +import java.text.ParsePosition; +import java.time.ZoneId; +import java.time.format.DateTimeBuilder; +import java.time.format.DateTimeFormatter; +import java.time.format.TextStyle; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.Queries; +import java.time.zone.ZoneRulesProvider; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test ZonePrinterParser. + */ +@Test(groups={"implementation"}) +public class TestZoneIdParser extends AbstractTestPrinterParser { + + private static final String AMERICA_DENVER = "America/Denver"; + private static final ZoneId TIME_ZONE_DENVER = ZoneId.of(AMERICA_DENVER); + + private DateTimeFormatter getFormatter0(TextStyle style) { + if (style == null) + return builder.appendZoneId().toFormatter(locale).withSymbols(symbols); + return builder.appendZoneText(style).toFormatter(locale).withSymbols(symbols); + } + + //----------------------------------------------------------------------- + @DataProvider(name="error") + Object[][] data_error() { + return new Object[][] { + {null, "hello", -1, IndexOutOfBoundsException.class}, + {null, "hello", 6, IndexOutOfBoundsException.class}, + }; + } + + @Test(dataProvider="error") + public void test_parse_error(TextStyle style, String text, int pos, Class expected) { + try { + getFormatter0(style).parseToBuilder(text, new ParsePosition(pos)); + assertTrue(false); + } catch (RuntimeException ex) { + assertTrue(expected.isInstance(ex)); + } + } + + //----------------------------------------------------------------------- + public void test_parse_exactMatch_Denver() throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter0(null).parseToBuilder(AMERICA_DENVER, pos); + assertEquals(pos.getIndex(), AMERICA_DENVER.length()); + assertParsed(dtb, TIME_ZONE_DENVER); + } + + public void test_parse_startStringMatch_Denver() throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter0(null).parseToBuilder(AMERICA_DENVER + "OTHER", pos); + assertEquals(pos.getIndex(), AMERICA_DENVER.length()); + assertParsed(dtb, TIME_ZONE_DENVER); + } + + public void test_parse_midStringMatch_Denver() throws Exception { + ParsePosition pos = new ParsePosition(5); + DateTimeBuilder dtb = getFormatter0(null).parseToBuilder("OTHER" + AMERICA_DENVER + "OTHER", pos); + assertEquals(pos.getIndex(), 5 + AMERICA_DENVER.length()); + assertParsed(dtb, TIME_ZONE_DENVER); + } + + public void test_parse_endStringMatch_Denver() throws Exception { + ParsePosition pos = new ParsePosition(5); + DateTimeBuilder dtb = getFormatter0(null).parseToBuilder("OTHER" + AMERICA_DENVER, pos); + assertEquals(pos.getIndex(), 5 + AMERICA_DENVER.length()); + assertParsed(dtb, TIME_ZONE_DENVER); + } + + public void test_parse_partialMatch() throws Exception { + ParsePosition pos = new ParsePosition(5); + DateTimeBuilder dtb = getFormatter0(null).parseToBuilder("OTHERAmerica/Bogusville", pos); + assertEquals(pos.getErrorIndex(), 5); // TBD: -6 ? + assertEquals(dtb, null); + } + + //----------------------------------------------------------------------- + @DataProvider(name="zones") + Object[][] populateTestData() { + Set ids = ZoneRulesProvider.getAvailableZoneIds(); + Object[][] rtnval = new Object[ids.size()][]; + int i = 0; + for (String id : ids) { + rtnval[i++] = new Object[] { id, ZoneId.of(id) }; + } + return rtnval; + } + + @Test(dataProvider="zones") + public void test_parse_exactMatch(String parse, ZoneId expected) throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter0(null).parseToBuilder(parse, pos); + assertEquals(pos.getIndex(), parse.length()); + assertParsed(dtb, expected); + } + + @Test(dataProvider="zones") + public void test_parse_startMatch(String parse, ZoneId expected) throws Exception { + String append = " OTHER"; + parse += append; + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter0(null).parseToBuilder(parse, pos); + assertEquals(pos.getIndex(), parse.length() - append.length()); + assertParsed(dtb, expected); + } + + //----------------------------------------------------------------------- + public void test_parse_caseInsensitive() throws Exception { + DateTimeFormatter fmt = new DateTimeFormatterBuilder().appendZoneId().toFormatter(); + DateTimeFormatter fmtCI = new DateTimeFormatterBuilder().parseCaseInsensitive() + .appendZoneId() + .toFormatter(); + for (String zidStr : ZoneRulesProvider.getAvailableZoneIds()) { + ZoneId zid = ZoneId.of(zidStr); + assertEquals(fmt.parse(zidStr, Queries.zoneId()), zid); + assertEquals(fmtCI.parse(zidStr.toLowerCase(), Queries.zoneId()), zid); + assertEquals(fmtCI.parse(zidStr.toUpperCase(), Queries.zoneId()), zid); + ParsePosition pos = new ParsePosition(5); + assertEquals(fmtCI.parseToBuilder("OTHER" + zidStr.toLowerCase() + "OTHER", pos) + .query(Queries.zoneId()), zid); + assertEquals(pos.getIndex(), zidStr.length() + 5); + pos = new ParsePosition(5); + assertEquals(fmtCI.parseToBuilder("OTHER" + zidStr.toUpperCase() + "OTHER", pos) + .query(Queries.zoneId()), zid); + assertEquals(pos.getIndex(), zidStr.length() + 5); + } + } + + //----------------------------------------------------------------------- + /* + public void test_parse_endStringMatch_utc() throws Exception { + ParsePosition pos = new ParsePosition(5); + DateTimeBuilder dtb = getFormatter0(null).parseToBuilder("OTHERUTC", pos); + assertEquals(pos.getIndex(), 8); + assertParsed(dtb, ZoneOffset.UTC); + } + + public void test_parse_endStringMatch_utc_plus1() throws Exception { + ParsePosition pos = new ParsePosition(5); + DateTimeBuilder dtb = getFormatter0(null).parseToBuilder("OTHERUTC+01:00", pos); + assertEquals(pos.getIndex(), 14); + assertParsed(dtb, ZoneId.of("UTC+01:00")); + } + + //----------------------------------------------------------------------- + public void test_parse_midStringMatch_utc() throws Exception { + ParsePosition pos = new ParsePosition(5); + DateTimeBuilder dtb = getFormatter0(null).parseToBuilder("OTHERUTCOTHER", pos); + assertEquals(pos.getIndex(), 8); + assertParsed(dtb, ZoneOffset.UTC); + } + + public void test_parse_midStringMatch_utc_plus1() throws Exception { + ParsePosition pos = new ParsePosition(5); + DateTimeBuilder dtb = getFormatter0(null).parseToBuilder("OTHERUTC+01:00OTHER", pos); + assertEquals(pos.getIndex(), 14); + assertParsed(dtb, ZoneId.of("UTC+01:00")); + } + */ + //----------------------------------------------------------------------- + public void test_toString_id() { + assertEquals(getFormatter0(null).toString(), "ZoneId()"); + } + + public void test_toString_text() { + assertEquals(getFormatter0(TextStyle.FULL).toString(), "ZoneText(FULL)"); + } + + private void assertParsed(DateTimeBuilder dtb, ZoneId expectedZone) { + assertEquals(dtb.query(ZoneId::from), expectedZone); + } + +} diff --git a/test/java/time/test/java/time/format/TestZoneOffsetParser.java b/test/java/time/test/java/time/format/TestZoneOffsetParser.java new file mode 100644 index 0000000000000000000000000000000000000000..300411cb2eb4c7888ef9a815374c8a7d20a3fd03 --- /dev/null +++ b/test/java/time/test/java/time/format/TestZoneOffsetParser.java @@ -0,0 +1,444 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import static java.time.temporal.ChronoField.OFFSET_SECONDS; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.text.ParsePosition; +import java.time.ZoneOffset; +import java.time.format.DateTimeBuilder; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test ZoneOffsetPrinterParser. + */ +@Test(groups={"implementation"}) +public class TestZoneOffsetParser extends AbstractTestPrinterParser { + + //----------------------------------------------------------------------- + @DataProvider(name="error") + Object[][] data_error() { + return new Object[][] { + {"+HH:MM:ss", "Z", "hello", -1, IndexOutOfBoundsException.class}, + {"+HH:MM:ss", "Z", "hello", 6, IndexOutOfBoundsException.class}, + }; + } + + @Test(dataProvider="error") + public void test_parse_error(String pattern, String noOffsetText, String text, int pos, Class expected) { + try { + getFormatter(pattern, noOffsetText).parseToBuilder(text, new ParsePosition(pos)); + } catch (RuntimeException ex) { + assertTrue(expected.isInstance(ex)); + } + } + + //----------------------------------------------------------------------- + public void test_parse_exactMatch_UTC() throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter("+HH:MM:ss", "Z").parseToBuilder("Z", pos); + assertEquals(pos.getIndex(), 1); + assertParsed(dtb, ZoneOffset.UTC); + } + + public void test_parse_startStringMatch_UTC() throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter("+HH:MM:ss", "Z").parseToBuilder("ZOTHER", pos); + assertEquals(pos.getIndex(), 1); + assertParsed(dtb, ZoneOffset.UTC); + } + + public void test_parse_midStringMatch_UTC() throws Exception { + ParsePosition pos = new ParsePosition(5); + DateTimeBuilder dtb = getFormatter("+HH:MM:ss", "Z").parseToBuilder("OTHERZOTHER", pos); + assertEquals(pos.getIndex(), 6); + assertParsed(dtb, ZoneOffset.UTC); + } + + public void test_parse_endStringMatch_UTC() throws Exception { + ParsePosition pos = new ParsePosition(5); + DateTimeBuilder dtb = getFormatter("+HH:MM:ss", "Z").parseToBuilder("OTHERZ", pos); + assertEquals(pos.getIndex(), 6); + assertParsed(dtb, ZoneOffset.UTC); + } + + //----------------------------------------------------------------------- + public void test_parse_exactMatch_UTC_EmptyUTC() throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter("+HH:MM:ss", "").parseToBuilder("", pos); + assertEquals(pos.getIndex(), 0); + assertParsed(dtb, ZoneOffset.UTC); + } + + public void test_parse_startStringMatch_UTC_EmptyUTC() throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter("+HH:MM:ss", "").parseToBuilder("OTHER", pos); + assertEquals(pos.getIndex(), 0); + assertParsed(dtb, ZoneOffset.UTC); + } + + public void test_parse_midStringMatch_UTC_EmptyUTC() throws Exception { + ParsePosition pos = new ParsePosition(5); + DateTimeBuilder dtb = getFormatter("+HH:MM:ss", "").parseToBuilder("OTHEROTHER", pos); + assertEquals(pos.getIndex(), 5); + assertParsed(dtb, ZoneOffset.UTC); + } + + public void test_parse_endStringMatch_UTC_EmptyUTC() throws Exception { + ParsePosition pos = new ParsePosition(5); + DateTimeBuilder dtb = getFormatter("+HH:MM:ss", "").parseToBuilder("OTHER", pos); + assertEquals(pos.getIndex(), 5); + assertParsed(dtb, ZoneOffset.UTC); + } + + //----------------------------------------------------------------------- + @DataProvider(name="offsets") + Object[][] provider_offsets() { + return new Object[][] { + {"+HH", "+00", ZoneOffset.UTC}, + {"+HH", "-00", ZoneOffset.UTC}, + {"+HH", "+01", ZoneOffset.ofHours(1)}, + {"+HH", "-01", ZoneOffset.ofHours(-1)}, + + {"+HHMM", "+0000", ZoneOffset.UTC}, + {"+HHMM", "-0000", ZoneOffset.UTC}, + {"+HHMM", "+0102", ZoneOffset.ofHoursMinutes(1, 2)}, + {"+HHMM", "-0102", ZoneOffset.ofHoursMinutes(-1, -2)}, + + {"+HH:MM", "+00:00", ZoneOffset.UTC}, + {"+HH:MM", "-00:00", ZoneOffset.UTC}, + {"+HH:MM", "+01:02", ZoneOffset.ofHoursMinutes(1, 2)}, + {"+HH:MM", "-01:02", ZoneOffset.ofHoursMinutes(-1, -2)}, + + {"+HHMMss", "+0000", ZoneOffset.UTC}, + {"+HHMMss", "-0000", ZoneOffset.UTC}, + {"+HHMMss", "+0100", ZoneOffset.ofHoursMinutesSeconds(1, 0, 0)}, + {"+HHMMss", "+0159", ZoneOffset.ofHoursMinutesSeconds(1, 59, 0)}, + {"+HHMMss", "+0200", ZoneOffset.ofHoursMinutesSeconds(2, 0, 0)}, + {"+HHMMss", "+1800", ZoneOffset.ofHoursMinutesSeconds(18, 0, 0)}, + {"+HHMMss", "+010215", ZoneOffset.ofHoursMinutesSeconds(1, 2, 15)}, + {"+HHMMss", "-0100", ZoneOffset.ofHoursMinutesSeconds(-1, 0, 0)}, + {"+HHMMss", "-0200", ZoneOffset.ofHoursMinutesSeconds(-2, 0, 0)}, + {"+HHMMss", "-1800", ZoneOffset.ofHoursMinutesSeconds(-18, 0, 0)}, + + {"+HHMMss", "+000000", ZoneOffset.UTC}, + {"+HHMMss", "-000000", ZoneOffset.UTC}, + {"+HHMMss", "+010000", ZoneOffset.ofHoursMinutesSeconds(1, 0, 0)}, + {"+HHMMss", "+010203", ZoneOffset.ofHoursMinutesSeconds(1, 2, 3)}, + {"+HHMMss", "+015959", ZoneOffset.ofHoursMinutesSeconds(1, 59, 59)}, + {"+HHMMss", "+020000", ZoneOffset.ofHoursMinutesSeconds(2, 0, 0)}, + {"+HHMMss", "+180000", ZoneOffset.ofHoursMinutesSeconds(18, 0, 0)}, + {"+HHMMss", "-010000", ZoneOffset.ofHoursMinutesSeconds(-1, 0, 0)}, + {"+HHMMss", "-020000", ZoneOffset.ofHoursMinutesSeconds(-2, 0, 0)}, + {"+HHMMss", "-180000", ZoneOffset.ofHoursMinutesSeconds(-18, 0, 0)}, + + {"+HH:MM:ss", "+00:00", ZoneOffset.UTC}, + {"+HH:MM:ss", "-00:00", ZoneOffset.UTC}, + {"+HH:MM:ss", "+01:00", ZoneOffset.ofHoursMinutesSeconds(1, 0, 0)}, + {"+HH:MM:ss", "+01:02", ZoneOffset.ofHoursMinutesSeconds(1, 2, 0)}, + {"+HH:MM:ss", "+01:59", ZoneOffset.ofHoursMinutesSeconds(1, 59, 0)}, + {"+HH:MM:ss", "+02:00", ZoneOffset.ofHoursMinutesSeconds(2, 0, 0)}, + {"+HH:MM:ss", "+18:00", ZoneOffset.ofHoursMinutesSeconds(18, 0, 0)}, + {"+HH:MM:ss", "+01:02:15", ZoneOffset.ofHoursMinutesSeconds(1, 2, 15)}, + {"+HH:MM:ss", "-01:00", ZoneOffset.ofHoursMinutesSeconds(-1, 0, 0)}, + {"+HH:MM:ss", "-02:00", ZoneOffset.ofHoursMinutesSeconds(-2, 0, 0)}, + {"+HH:MM:ss", "-18:00", ZoneOffset.ofHoursMinutesSeconds(-18, 0, 0)}, + + {"+HH:MM:ss", "+00:00:00", ZoneOffset.UTC}, + {"+HH:MM:ss", "-00:00:00", ZoneOffset.UTC}, + {"+HH:MM:ss", "+01:00:00", ZoneOffset.ofHoursMinutesSeconds(1, 0, 0)}, + {"+HH:MM:ss", "+01:02:03", ZoneOffset.ofHoursMinutesSeconds(1, 2, 3)}, + {"+HH:MM:ss", "+01:59:59", ZoneOffset.ofHoursMinutesSeconds(1, 59, 59)}, + {"+HH:MM:ss", "+02:00:00", ZoneOffset.ofHoursMinutesSeconds(2, 0, 0)}, + {"+HH:MM:ss", "+18:00:00", ZoneOffset.ofHoursMinutesSeconds(18, 0, 0)}, + {"+HH:MM:ss", "-01:00:00", ZoneOffset.ofHoursMinutesSeconds(-1, 0, 0)}, + {"+HH:MM:ss", "-02:00:00", ZoneOffset.ofHoursMinutesSeconds(-2, 0, 0)}, + {"+HH:MM:ss", "-18:00:00", ZoneOffset.ofHoursMinutesSeconds(-18, 0, 0)}, + + {"+HHMMSS", "+000000", ZoneOffset.UTC}, + {"+HHMMSS", "-000000", ZoneOffset.UTC}, + {"+HHMMSS", "+010203", ZoneOffset.ofHoursMinutesSeconds(1, 2, 3)}, + {"+HHMMSS", "-010203", ZoneOffset.ofHoursMinutesSeconds(-1, -2, -3)}, + + {"+HH:MM:SS", "+00:00:00", ZoneOffset.UTC}, + {"+HH:MM:SS", "-00:00:00", ZoneOffset.UTC}, + {"+HH:MM:SS", "+01:02:03", ZoneOffset.ofHoursMinutesSeconds(1, 2, 3)}, + {"+HH:MM:SS", "-01:02:03", ZoneOffset.ofHoursMinutesSeconds(-1, -2, -3)}, + }; + } + + @Test(dataProvider="offsets") + public void test_parse_exactMatch(String pattern, String parse, ZoneOffset expected) throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter(pattern, "Z").parseToBuilder(parse, pos); + assertEquals(pos.getIndex(), parse.length()); + assertParsed(dtb, expected); + } + + @Test(dataProvider="offsets") + public void test_parse_startStringMatch(String pattern, String parse, ZoneOffset expected) throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter(pattern, "Z").parseToBuilder(parse + ":OTHER", pos); + assertEquals(pos.getIndex(), parse.length()); + assertParsed(dtb, expected); + } + + @Test(dataProvider="offsets") + public void test_parse_midStringMatch(String pattern, String parse, ZoneOffset expected) throws Exception { + ParsePosition pos = new ParsePosition(5); + DateTimeBuilder dtb = getFormatter(pattern, "Z").parseToBuilder("OTHER" + parse + ":OTHER", pos); + assertEquals(pos.getIndex(), parse.length() + 5); + assertParsed(dtb, expected); + } + + @Test(dataProvider="offsets") + public void test_parse_endStringMatch(String pattern, String parse, ZoneOffset expected) throws Exception { + ParsePosition pos = new ParsePosition(5); + DateTimeBuilder dtb = getFormatter(pattern, "Z").parseToBuilder("OTHER" + parse, pos); + assertEquals(pos.getIndex(), parse.length() + 5); + assertParsed(dtb, expected); + } + + @Test(dataProvider="offsets") + public void test_parse_exactMatch_EmptyUTC(String pattern, String parse, ZoneOffset expected) throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter(pattern, "").parseToBuilder(parse, pos); + assertEquals(pos.getIndex(), parse.length()); + assertParsed(dtb, expected); + } + + @Test(dataProvider="offsets") + public void test_parse_startStringMatch_EmptyUTC(String pattern, String parse, ZoneOffset expected) throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter(pattern, "").parseToBuilder(parse + ":OTHER", pos); + assertEquals(pos.getIndex(), parse.length()); + assertParsed(dtb, expected); + } + + @Test(dataProvider="offsets") + public void test_parse_midStringMatch_EmptyUTC(String pattern, String parse, ZoneOffset expected) throws Exception { + ParsePosition pos = new ParsePosition(5); + DateTimeBuilder dtb = getFormatter(pattern, "").parseToBuilder("OTHER" + parse + ":OTHER", pos); + assertEquals(pos.getIndex(), parse.length() + 5); + assertParsed(dtb, expected); + } + + @Test(dataProvider="offsets") + public void test_parse_endStringMatch_EmptyUTC(String pattern, String parse, ZoneOffset expected) throws Exception { + ParsePosition pos = new ParsePosition(5); + DateTimeBuilder dtb = getFormatter(pattern, "").parseToBuilder("OTHER" + parse, pos); + assertEquals(pos.getIndex(), parse.length() + 5); + assertParsed(dtb, expected); + } + + //----------------------------------------------------------------------- + @DataProvider(name="bigOffsets") + Object[][] provider_bigOffsets() { + return new Object[][] { + {"+HH", "+59", 59 * 3600}, + {"+HH", "-19", -(19 * 3600)}, + + {"+HHMM", "+1801", 18 * 3600 + 1 * 60}, + {"+HHMM", "-1801", -(18 * 3600 + 1 * 60)}, + + {"+HH:MM", "+18:01", 18 * 3600 + 1 * 60}, + {"+HH:MM", "-18:01", -(18 * 3600 + 1 * 60)}, + + {"+HHMMss", "+180103", 18 * 3600 + 1 * 60 + 3}, + {"+HHMMss", "-180103", -(18 * 3600 + 1 * 60 + 3)}, + + {"+HH:MM:ss", "+18:01:03", 18 * 3600 + 1 * 60 + 3}, + {"+HH:MM:ss", "-18:01:03", -(18 * 3600 + 1 * 60 + 3)}, + + {"+HHMMSS", "+180103", 18 * 3600 + 1 * 60 + 3}, + {"+HHMMSS", "-180103", -(18 * 3600 + 1 * 60 + 3)}, + + {"+HH:MM:SS", "+18:01:03", 18 * 3600 + 1 * 60 + 3}, + {"+HH:MM:SS", "-18:01:03", -(18 * 3600 + 1 * 60 + 3)}, + }; + } + + @Test(dataProvider="bigOffsets") + public void test_parse_bigOffsets(String pattern, String parse, long offsetSecs) throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter(pattern, "").parseToBuilder(parse, pos); + assertEquals(pos.getIndex(), parse.length()); + assertEquals(dtb.getLong(OFFSET_SECONDS), offsetSecs); + } + + //----------------------------------------------------------------------- + @DataProvider(name="badOffsets") + Object[][] provider_badOffsets() { + return new Object[][] { + {"+HH", "+1", 0}, + {"+HH", "-1", 0}, + {"+HH", "01", 0}, + {"+HH", "01", 0}, + {"+HH", "+AA", 0}, + + {"+HHMM", "+1", 0}, + {"+HHMM", "+01", 0}, + {"+HHMM", "+001", 0}, + {"+HHMM", "0102", 0}, + {"+HHMM", "+01:02", 0}, + {"+HHMM", "+AAAA", 0}, + + {"+HH:MM", "+1", 0}, + {"+HH:MM", "+01", 0}, + {"+HH:MM", "+0:01", 0}, + {"+HH:MM", "+00:1", 0}, + {"+HH:MM", "+0:1", 0}, + {"+HH:MM", "+:", 0}, + {"+HH:MM", "01:02", 0}, + {"+HH:MM", "+0102", 0}, + {"+HH:MM", "+AA:AA", 0}, + + {"+HHMMss", "+1", 0}, + {"+HHMMss", "+01", 0}, + {"+HHMMss", "+001", 0}, + {"+HHMMss", "0102", 0}, + {"+HHMMss", "+01:02", 0}, + {"+HHMMss", "+AAAA", 0}, + + {"+HH:MM:ss", "+1", 0}, + {"+HH:MM:ss", "+01", 0}, + {"+HH:MM:ss", "+0:01", 0}, + {"+HH:MM:ss", "+00:1", 0}, + {"+HH:MM:ss", "+0:1", 0}, + {"+HH:MM:ss", "+:", 0}, + {"+HH:MM:ss", "01:02", 0}, + {"+HH:MM:ss", "+0102", 0}, + {"+HH:MM:ss", "+AA:AA", 0}, + + {"+HHMMSS", "+1", 0}, + {"+HHMMSS", "+01", 0}, + {"+HHMMSS", "+001", 0}, + {"+HHMMSS", "0102", 0}, + {"+HHMMSS", "+01:02", 0}, + {"+HHMMSS", "+AAAA", 0}, + + {"+HH:MM:SS", "+1", 0}, + {"+HH:MM:SS", "+01", 0}, + {"+HH:MM:SS", "+0:01", 0}, + {"+HH:MM:SS", "+00:1", 0}, + {"+HH:MM:SS", "+0:1", 0}, + {"+HH:MM:SS", "+:", 0}, + {"+HH:MM:SS", "01:02", 0}, + {"+HH:MM:SS", "+0102", 0}, + {"+HH:MM:SS", "+AA:AA", 0}, + }; + } + + @Test(dataProvider="badOffsets") + public void test_parse_invalid(String pattern, String parse, int expectedPosition) throws Exception { + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter(pattern, "Z").parseToBuilder(parse, pos); + assertEquals(pos.getErrorIndex(), expectedPosition); + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + public void test_parse_caseSensitiveUTC_matchedCase() throws Exception { + setCaseSensitive(true); + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter("+HH:MM:ss", "Z").parseToBuilder("Z", pos); + assertEquals(pos.getIndex(), 1); + assertParsed(dtb, ZoneOffset.UTC); + } + + public void test_parse_caseSensitiveUTC_unmatchedCase() throws Exception { + setCaseSensitive(true); + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter("+HH:MM:ss", "Z").parseToBuilder("z", pos); + assertEquals(pos.getErrorIndex(), 0); + assertEquals(dtb, null); + } + + public void test_parse_caseInsensitiveUTC_matchedCase() throws Exception { + setCaseSensitive(false); + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter("+HH:MM:ss", "Z").parseToBuilder("Z", pos); + assertEquals(pos.getIndex(), 1); + assertParsed(dtb, ZoneOffset.UTC); + } + + public void test_parse_caseInsensitiveUTC_unmatchedCase() throws Exception { + setCaseSensitive(false); + ParsePosition pos = new ParsePosition(0); + DateTimeBuilder dtb = getFormatter("+HH:MM:ss", "Z").parseToBuilder("z", pos); + assertEquals(pos.getIndex(), 1); + assertParsed(dtb, ZoneOffset.UTC); + } + + private void assertParsed(DateTimeBuilder dtb, ZoneOffset expectedOffset) { + if (expectedOffset == null) { + assertEquals(dtb, null); + } else { + assertEquals(dtb.getFieldValueMap().size(), 1); + assertEquals(dtb.getLong(OFFSET_SECONDS), (long) expectedOffset.getTotalSeconds()); + } + } + +} diff --git a/test/java/time/test/java/time/format/TestZoneOffsetPrinter.java b/test/java/time/test/java/time/format/TestZoneOffsetPrinter.java new file mode 100644 index 0000000000000000000000000000000000000000..e4250900b05dbc68f64a33ba0d187b97e80a702b --- /dev/null +++ b/test/java/time/test/java/time/format/TestZoneOffsetPrinter.java @@ -0,0 +1,175 @@ +/* + * 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. + * + * 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 test.java.time.format; + +import static java.time.temporal.ChronoField.OFFSET_SECONDS; +import static org.testng.Assert.assertEquals; + +import java.time.DateTimeException; +import java.time.ZoneOffset; +import java.time.format.DateTimeBuilder; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test ZoneOffsetPrinterParser. + */ +@Test(groups={"implementation"}) +public class TestZoneOffsetPrinter extends AbstractTestPrinterParser { + + private static final ZoneOffset OFFSET_0130 = ZoneOffset.of("+01:30"); + + //----------------------------------------------------------------------- + @DataProvider(name="offsets") + Object[][] provider_offsets() { + return new Object[][] { + {"+HH", "NO-OFFSET", ZoneOffset.UTC}, + {"+HH", "+01", ZoneOffset.ofHours(1)}, + {"+HH", "-01", ZoneOffset.ofHours(-1)}, + + {"+HHMM", "NO-OFFSET", ZoneOffset.UTC}, + {"+HHMM", "+0102", ZoneOffset.ofHoursMinutes(1, 2)}, + {"+HHMM", "-0102", ZoneOffset.ofHoursMinutes(-1, -2)}, + + {"+HH:MM", "NO-OFFSET", ZoneOffset.UTC}, + {"+HH:MM", "+01:02", ZoneOffset.ofHoursMinutes(1, 2)}, + {"+HH:MM", "-01:02", ZoneOffset.ofHoursMinutes(-1, -2)}, + + {"+HHMMss", "NO-OFFSET", ZoneOffset.UTC}, + {"+HHMMss", "+0100", ZoneOffset.ofHoursMinutesSeconds(1, 0, 0)}, + {"+HHMMss", "+0102", ZoneOffset.ofHoursMinutesSeconds(1, 2, 0)}, + {"+HHMMss", "+0159", ZoneOffset.ofHoursMinutesSeconds(1, 59, 0)}, + {"+HHMMss", "+0200", ZoneOffset.ofHoursMinutesSeconds(2, 0, 0)}, + {"+HHMMss", "+1800", ZoneOffset.ofHoursMinutesSeconds(18, 0, 0)}, + {"+HHMMss", "+010215", ZoneOffset.ofHoursMinutesSeconds(1, 2, 15)}, + {"+HHMMss", "-0100", ZoneOffset.ofHoursMinutesSeconds(-1, 0, 0)}, + {"+HHMMss", "-0200", ZoneOffset.ofHoursMinutesSeconds(-2, 0, 0)}, + {"+HHMMss", "-1800", ZoneOffset.ofHoursMinutesSeconds(-18, 0, 0)}, + + {"+HHMMss", "NO-OFFSET", ZoneOffset.UTC}, + {"+HHMMss", "+0100", ZoneOffset.ofHoursMinutesSeconds(1, 0, 0)}, + {"+HHMMss", "+010203", ZoneOffset.ofHoursMinutesSeconds(1, 2, 3)}, + {"+HHMMss", "+015959", ZoneOffset.ofHoursMinutesSeconds(1, 59, 59)}, + {"+HHMMss", "+0200", ZoneOffset.ofHoursMinutesSeconds(2, 0, 0)}, + {"+HHMMss", "+1800", ZoneOffset.ofHoursMinutesSeconds(18, 0, 0)}, + {"+HHMMss", "-0100", ZoneOffset.ofHoursMinutesSeconds(-1, 0, 0)}, + {"+HHMMss", "-0200", ZoneOffset.ofHoursMinutesSeconds(-2, 0, 0)}, + {"+HHMMss", "-1800", ZoneOffset.ofHoursMinutesSeconds(-18, 0, 0)}, + + {"+HH:MM:ss", "NO-OFFSET", ZoneOffset.UTC}, + {"+HH:MM:ss", "+01:00", ZoneOffset.ofHoursMinutesSeconds(1, 0, 0)}, + {"+HH:MM:ss", "+01:02", ZoneOffset.ofHoursMinutesSeconds(1, 2, 0)}, + {"+HH:MM:ss", "+01:59", ZoneOffset.ofHoursMinutesSeconds(1, 59, 0)}, + {"+HH:MM:ss", "+02:00", ZoneOffset.ofHoursMinutesSeconds(2, 0, 0)}, + {"+HH:MM:ss", "+18:00", ZoneOffset.ofHoursMinutesSeconds(18, 0, 0)}, + {"+HH:MM:ss", "+01:02:15", ZoneOffset.ofHoursMinutesSeconds(1, 2, 15)}, + {"+HH:MM:ss", "-01:00", ZoneOffset.ofHoursMinutesSeconds(-1, 0, 0)}, + {"+HH:MM:ss", "-02:00", ZoneOffset.ofHoursMinutesSeconds(-2, 0, 0)}, + {"+HH:MM:ss", "-18:00", ZoneOffset.ofHoursMinutesSeconds(-18, 0, 0)}, + + {"+HH:MM:ss", "NO-OFFSET", ZoneOffset.UTC}, + {"+HH:MM:ss", "+01:00", ZoneOffset.ofHoursMinutesSeconds(1, 0, 0)}, + {"+HH:MM:ss", "+01:02:03", ZoneOffset.ofHoursMinutesSeconds(1, 2, 3)}, + {"+HH:MM:ss", "+01:59:59", ZoneOffset.ofHoursMinutesSeconds(1, 59, 59)}, + {"+HH:MM:ss", "+02:00", ZoneOffset.ofHoursMinutesSeconds(2, 0, 0)}, + {"+HH:MM:ss", "+18:00", ZoneOffset.ofHoursMinutesSeconds(18, 0, 0)}, + {"+HH:MM:ss", "-01:00", ZoneOffset.ofHoursMinutesSeconds(-1, 0, 0)}, + {"+HH:MM:ss", "-02:00", ZoneOffset.ofHoursMinutesSeconds(-2, 0, 0)}, + {"+HH:MM:ss", "-18:00", ZoneOffset.ofHoursMinutesSeconds(-18, 0, 0)}, + + {"+HHMMSS", "NO-OFFSET", ZoneOffset.UTC}, + {"+HHMMSS", "+010203", ZoneOffset.ofHoursMinutesSeconds(1, 2, 3)}, + {"+HHMMSS", "-010203", ZoneOffset.ofHoursMinutesSeconds(-1, -2, -3)}, + {"+HHMMSS", "+010200", ZoneOffset.ofHoursMinutesSeconds(1, 2, 0)}, + {"+HHMMSS", "-010200", ZoneOffset.ofHoursMinutesSeconds(-1, -2, 0)}, + + {"+HH:MM:SS", "NO-OFFSET", ZoneOffset.UTC}, + {"+HH:MM:SS", "+01:02:03", ZoneOffset.ofHoursMinutesSeconds(1, 2, 3)}, + {"+HH:MM:SS", "-01:02:03", ZoneOffset.ofHoursMinutesSeconds(-1, -2, -3)}, + {"+HH:MM:SS", "+01:02:00", ZoneOffset.ofHoursMinutesSeconds(1, 2, 0)}, + {"+HH:MM:SS", "-01:02:00", ZoneOffset.ofHoursMinutesSeconds(-1, -2, 0)}, + }; + } + + @Test(dataProvider="offsets") + public void test_print(String pattern, String expected, ZoneOffset offset) throws Exception { + buf.append("EXISTING"); + getFormatter(pattern, "NO-OFFSET").printTo(new DateTimeBuilder(OFFSET_SECONDS, offset.getTotalSeconds()), buf); + assertEquals(buf.toString(), "EXISTING" + expected); + } + + @Test(dataProvider="offsets") + public void test_toString(String pattern, String expected, ZoneOffset offset) throws Exception { + assertEquals(getFormatter(pattern, "NO-OFFSET").toString(), "Offset('NO-OFFSET'," + pattern + ")"); + } + + //----------------------------------------------------------------------- + @Test(expectedExceptions=DateTimeException.class) + public void test_print_emptyCalendrical() throws Exception { + getFormatter("+HH:MM:ss", "Z").printTo(EMPTY_DTA, buf); + } + + public void test_print_emptyAppendable() throws Exception { + getFormatter("+HH:MM:ss", "Z").printTo(new DateTimeBuilder(OFFSET_SECONDS, OFFSET_0130.getTotalSeconds()), buf); + assertEquals(buf.toString(), "+01:30"); + } + +} diff --git a/test/java/time/test/java/time/format/TestZoneTextPrinterParser.java b/test/java/time/test/java/time/format/TestZoneTextPrinterParser.java new file mode 100644 index 0000000000000000000000000000000000000000..0f58634b655a54c30b017524182ebda68989b505 --- /dev/null +++ b/test/java/time/test/java/time/format/TestZoneTextPrinterParser.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2012, 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. + * + * 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 test.java.time.format; + +import java.util.Date; +import java.util.Locale; +import java.util.Random; +import java.util.Set; +import java.util.TimeZone; + +import java.time.ZonedDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoField; +import java.time.format.DateTimeFormatSymbols; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.TextStyle; +import java.time.zone.ZoneRulesProvider; + +import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; + +/** + * Test ZoneTextPrinterParser + */ +@Test(groups={"implementation"}) +public class TestZoneTextPrinterParser extends AbstractTestPrinterParser { + + protected static DateTimeFormatter getFormatter(Locale locale, TextStyle style) { + return new DateTimeFormatterBuilder().appendZoneText(style) + .toFormatter(locale) + .withSymbols(DateTimeFormatSymbols.of(locale)); + } + + public void test_printText() { + Random r = new Random(); + int N = 50; + Locale[] locales = Locale.getAvailableLocales(); + Set zids = ZoneRulesProvider.getAvailableZoneIds(); + ZonedDateTime zdt = ZonedDateTime.now(); + + //System.out.printf("locale==%d, timezone=%d%n", locales.length, zids.size()); + while (N-- > 0) { + zdt = zdt.withDayOfYear(r.nextInt(365) + 1) + .with(ChronoField.SECOND_OF_DAY, r.nextInt(86400)); + for (String zid : zids) { + zdt = zdt.withZoneSameLocal(ZoneId.of(zid)); + TimeZone tz = TimeZone.getTimeZone(zid); + boolean isDST = tz.inDaylightTime(new Date(zdt.toInstant().toEpochMilli())); + for (Locale locale : locales) { + printText(locale, zdt, TextStyle.FULL, + tz.getDisplayName(isDST, TimeZone.LONG, locale)); + printText(locale, zdt, TextStyle.SHORT, + tz.getDisplayName(isDST, TimeZone.SHORT, locale)); + } + } + } + } + + private void printText(Locale locale, ZonedDateTime zdt, TextStyle style, String expected) { + String result = getFormatter(locale, style).print(zdt); + if (!result.equals(expected)) { + if (result.equals("FooLocation") || // from rules provider test if same vm + result.startsWith("Etc/GMT") || result.equals("ROC")) { // TBD: match jdk behavior? + return; + } + System.out.println("----------------"); + System.out.printf("tdz[%s]%n", zdt.toString()); + System.out.printf("[%-4s, %5s] :[%s]%n", locale.toString(), style.toString(),result); + System.out.printf("%4s, %5s :[%s]%n", "", "", expected); + } + assertEquals(result, expected); + } +} diff --git a/test/java/time/test/java/time/temporal/MockFieldNoValue.java b/test/java/time/test/java/time/temporal/MockFieldNoValue.java new file mode 100644 index 0000000000000000000000000000000000000000..255735db006fd803cf2d68cb48faa9dccaa55c35 --- /dev/null +++ b/test/java/time/test/java/time/temporal/MockFieldNoValue.java @@ -0,0 +1,124 @@ +/* + * 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. + * + * 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 test.java.time.temporal; + +import java.time.format.DateTimeBuilder; +import java.time.temporal.*; + +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.WEEKS; + +import java.time.DateTimeException; + +/** + * Mock TemporalField that returns null. + */ +public enum MockFieldNoValue implements TemporalField { + + INSTANCE; + + @Override + public String getName() { + return null; + } + + @Override + public TemporalUnit getBaseUnit() { + return WEEKS; + } + + @Override + public TemporalUnit getRangeUnit() { + return MONTHS; + } + + @Override + public ValueRange range() { + return ValueRange.of(1, 20); + } + + //----------------------------------------------------------------------- + @Override + public boolean doIsSupported(TemporalAccessor temporal) { + return true; + } + + @Override + public ValueRange doRange(TemporalAccessor temporal) { + return ValueRange.of(1, 20); + } + + @Override + public long doGet(TemporalAccessor temporal) { + throw new DateTimeException("Mock"); + } + + @Override + public R doWith(R temporal, long newValue) { + throw new DateTimeException("Mock"); + } + + //----------------------------------------------------------------------- + @Override + public boolean resolve(DateTimeBuilder dateTimeBuilder, long value) { + return false; + } + +} diff --git a/test/java/time/test/java/time/temporal/MockFieldValue.java b/test/java/time/test/java/time/temporal/MockFieldValue.java new file mode 100644 index 0000000000000000000000000000000000000000..7f478a9a539434ecdf83fe2983f6ba6184cd187a --- /dev/null +++ b/test/java/time/test/java/time/temporal/MockFieldValue.java @@ -0,0 +1,104 @@ +/* + * 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. + * + * 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) 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 test.java.time.temporal; + +import java.time.temporal.*; + +import java.time.DateTimeException; +import java.time.temporal.TemporalAccessor; + +/** + * Mock simple date-time with one field-value. + */ +public final class MockFieldValue implements TemporalAccessor { + + private final TemporalField field; + private final long value; + + public MockFieldValue(TemporalField field, long value) { + this.field = field; + this.value = value; + } + + @Override + public boolean isSupported(TemporalField field) { + return field != null && field.equals(this.field); + } + + @Override + public ValueRange range(TemporalField field) { + if (field instanceof ChronoField) { + if (isSupported(field)) { + return field.range(); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.doRange(this); + } + + @Override + public long getLong(TemporalField field) { + if (this.field.equals(field)) { + return value; + } + throw new DateTimeException("Unsupported field: " + field); + } + +} diff --git a/test/java/time/test/java/time/temporal/TestChronoUnit.java b/test/java/time/test/java/time/temporal/TestChronoUnit.java new file mode 100644 index 0000000000000000000000000000000000000000..b282cda2f85c72ebf6b84e4067d4b1f5b86f0c86 --- /dev/null +++ b/test/java/time/test/java/time/temporal/TestChronoUnit.java @@ -0,0 +1,318 @@ +/* + * 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 test.java.time.temporal; + +import static java.time.Month.AUGUST; +import static java.time.Month.FEBRUARY; +import static java.time.Month.JULY; +import static java.time.Month.JUNE; +import static java.time.Month.MARCH; +import static java.time.Month.OCTOBER; +import static java.time.Month.SEPTEMBER; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.WEEKS; +import static java.time.temporal.ChronoUnit.YEARS; +import static org.testng.Assert.assertEquals; + +import java.time.LocalDate; +import java.time.Month; +import java.time.ZoneOffset; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test. + */ +@Test +public class TestChronoUnit { + + //----------------------------------------------------------------------- + @DataProvider(name = "yearsBetween") + Object[][] data_yearsBetween() { + return new Object[][] { + {date(1939, SEPTEMBER, 2), date(1939, SEPTEMBER, 1), 0}, + {date(1939, SEPTEMBER, 2), date(1939, SEPTEMBER, 2), 0}, + {date(1939, SEPTEMBER, 2), date(1939, SEPTEMBER, 3), 0}, + + {date(1939, SEPTEMBER, 2), date(1940, SEPTEMBER, 1), 0}, + {date(1939, SEPTEMBER, 2), date(1940, SEPTEMBER, 2), 1}, + {date(1939, SEPTEMBER, 2), date(1940, SEPTEMBER, 3), 1}, + + {date(1939, SEPTEMBER, 2), date(1938, SEPTEMBER, 1), -1}, + {date(1939, SEPTEMBER, 2), date(1938, SEPTEMBER, 2), -1}, + {date(1939, SEPTEMBER, 2), date(1938, SEPTEMBER, 3), 0}, + + {date(1939, SEPTEMBER, 2), date(1945, SEPTEMBER, 3), 6}, + {date(1939, SEPTEMBER, 2), date(1945, OCTOBER, 3), 6}, + {date(1939, SEPTEMBER, 2), date(1945, AUGUST, 3), 5}, + }; + } + + @Test(dataProvider = "yearsBetween") + public void test_yearsBetween(LocalDate start, LocalDate end, long expected) { + assertEquals(YEARS.between(start, end).getAmount(), expected); + assertEquals(YEARS.between(start, end).getUnit(), YEARS); + } + + @Test(dataProvider = "yearsBetween") + public void test_yearsBetweenReversed(LocalDate start, LocalDate end, long expected) { + assertEquals(YEARS.between(end, start).getAmount(), -expected); + assertEquals(YEARS.between(end, start).getUnit(), YEARS); + } + + @Test(dataProvider = "yearsBetween") + public void test_yearsBetween_LocalDateTimeSameTime(LocalDate start, LocalDate end, long expected) { + assertEquals(YEARS.between(start.atTime(12, 30), end.atTime(12, 30)).getAmount(), expected); + } + + @Test(dataProvider = "yearsBetween") + public void test_yearsBetween_LocalDateTimeLaterTime(LocalDate start, LocalDate end, long expected) { + assertEquals(YEARS.between(start.atTime(12, 30), end.atTime(12, 31)).getAmount(), expected); + } + + @Test(dataProvider = "yearsBetween") + public void test_yearsBetween_ZonedDateSameOffset(LocalDate start, LocalDate end, long expected) { + assertEquals(YEARS.between(start.atStartOfDay(ZoneOffset.ofHours(2)), end.atStartOfDay(ZoneOffset.ofHours(2))).getAmount(), expected); + } + + @Test(dataProvider = "yearsBetween") + public void test_yearsBetween_ZonedDateLaterOffset(LocalDate start, LocalDate end, long expected) { + // +01:00 is later than +02:00 + assertEquals(YEARS.between(start.atStartOfDay(ZoneOffset.ofHours(2)), end.atStartOfDay(ZoneOffset.ofHours(1))).getAmount(), expected); + } + + //----------------------------------------------------------------------- + @DataProvider(name = "monthsBetween") + Object[][] data_monthsBetween() { + return new Object[][] { + {date(2012, JULY, 2), date(2012, JULY, 1), 0}, + {date(2012, JULY, 2), date(2012, JULY, 2), 0}, + {date(2012, JULY, 2), date(2012, JULY, 3), 0}, + + {date(2012, JULY, 2), date(2012, AUGUST, 1), 0}, + {date(2012, JULY, 2), date(2012, AUGUST, 2), 1}, + {date(2012, JULY, 2), date(2012, AUGUST, 3), 1}, + + {date(2012, JULY, 2), date(2012, SEPTEMBER, 1), 1}, + {date(2012, JULY, 2), date(2012, SEPTEMBER, 2), 2}, + {date(2012, JULY, 2), date(2012, SEPTEMBER, 3), 2}, + + {date(2012, JULY, 2), date(2012, JUNE, 1), -1}, + {date(2012, JULY, 2), date(2012, JUNE, 2), -1}, + {date(2012, JULY, 2), date(2012, JUNE, 3), 0}, + + {date(2012, FEBRUARY, 27), date(2012, MARCH, 26), 0}, + {date(2012, FEBRUARY, 27), date(2012, MARCH, 27), 1}, + {date(2012, FEBRUARY, 27), date(2012, MARCH, 28), 1}, + + {date(2012, FEBRUARY, 28), date(2012, MARCH, 27), 0}, + {date(2012, FEBRUARY, 28), date(2012, MARCH, 28), 1}, + {date(2012, FEBRUARY, 28), date(2012, MARCH, 29), 1}, + + {date(2012, FEBRUARY, 29), date(2012, MARCH, 28), 0}, + {date(2012, FEBRUARY, 29), date(2012, MARCH, 29), 1}, + {date(2012, FEBRUARY, 29), date(2012, MARCH, 30), 1}, + }; + } + + @Test(dataProvider = "monthsBetween") + public void test_monthsBetween(LocalDate start, LocalDate end, long expected) { + assertEquals(MONTHS.between(start, end).getAmount(), expected); + assertEquals(MONTHS.between(start, end).getUnit(), MONTHS); + } + + @Test(dataProvider = "monthsBetween") + public void test_monthsBetweenReversed(LocalDate start, LocalDate end, long expected) { + assertEquals(MONTHS.between(end, start).getAmount(), -expected); + assertEquals(MONTHS.between(end, start).getUnit(), MONTHS); + } + + @Test(dataProvider = "monthsBetween") + public void test_monthsBetween_LocalDateTimeSameTime(LocalDate start, LocalDate end, long expected) { + assertEquals(MONTHS.between(start.atTime(12, 30), end.atTime(12, 30)).getAmount(), expected); + } + + @Test(dataProvider = "monthsBetween") + public void test_monthsBetween_LocalDateTimeLaterTime(LocalDate start, LocalDate end, long expected) { + assertEquals(MONTHS.between(start.atTime(12, 30), end.atTime(12, 31)).getAmount(), expected); + } + + @Test(dataProvider = "monthsBetween") + public void test_monthsBetween_ZonedDateSameOffset(LocalDate start, LocalDate end, long expected) { + assertEquals(MONTHS.between(start.atStartOfDay(ZoneOffset.ofHours(2)), end.atStartOfDay(ZoneOffset.ofHours(2))).getAmount(), expected); + } + + @Test(dataProvider = "monthsBetween") + public void test_monthsBetween_ZonedDateLaterOffset(LocalDate start, LocalDate end, long expected) { + // +01:00 is later than +02:00 + assertEquals(MONTHS.between(start.atStartOfDay(ZoneOffset.ofHours(2)), end.atStartOfDay(ZoneOffset.ofHours(1))).getAmount(), expected); + } + + //----------------------------------------------------------------------- + @DataProvider(name = "weeksBetween") + Object[][] data_weeksBetween() { + return new Object[][] { + {date(2012, JULY, 2), date(2012, JUNE, 25), -1}, + {date(2012, JULY, 2), date(2012, JUNE, 26), 0}, + {date(2012, JULY, 2), date(2012, JULY, 2), 0}, + {date(2012, JULY, 2), date(2012, JULY, 8), 0}, + {date(2012, JULY, 2), date(2012, JULY, 9), 1}, + + {date(2012, FEBRUARY, 28), date(2012, FEBRUARY, 21), -1}, + {date(2012, FEBRUARY, 28), date(2012, FEBRUARY, 22), 0}, + {date(2012, FEBRUARY, 28), date(2012, FEBRUARY, 28), 0}, + {date(2012, FEBRUARY, 28), date(2012, FEBRUARY, 29), 0}, + {date(2012, FEBRUARY, 28), date(2012, MARCH, 1), 0}, + {date(2012, FEBRUARY, 28), date(2012, MARCH, 5), 0}, + {date(2012, FEBRUARY, 28), date(2012, MARCH, 6), 1}, + + {date(2012, FEBRUARY, 29), date(2012, FEBRUARY, 22), -1}, + {date(2012, FEBRUARY, 29), date(2012, FEBRUARY, 23), 0}, + {date(2012, FEBRUARY, 29), date(2012, FEBRUARY, 28), 0}, + {date(2012, FEBRUARY, 29), date(2012, FEBRUARY, 29), 0}, + {date(2012, FEBRUARY, 29), date(2012, MARCH, 1), 0}, + {date(2012, FEBRUARY, 29), date(2012, MARCH, 6), 0}, + {date(2012, FEBRUARY, 29), date(2012, MARCH, 7), 1}, + }; + } + + @Test(dataProvider = "weeksBetween") + public void test_weeksBetween(LocalDate start, LocalDate end, long expected) { + assertEquals(WEEKS.between(start, end).getAmount(), expected); + assertEquals(WEEKS.between(start, end).getUnit(), WEEKS); + } + + @Test(dataProvider = "weeksBetween") + public void test_weeksBetweenReversed(LocalDate start, LocalDate end, long expected) { + assertEquals(WEEKS.between(end, start).getAmount(), -expected); + assertEquals(WEEKS.between(end, start).getUnit(), WEEKS); + } + + //----------------------------------------------------------------------- + @DataProvider(name = "daysBetween") + Object[][] data_daysBetween() { + return new Object[][] { + {date(2012, JULY, 2), date(2012, JULY, 1), -1}, + {date(2012, JULY, 2), date(2012, JULY, 2), 0}, + {date(2012, JULY, 2), date(2012, JULY, 3), 1}, + + {date(2012, FEBRUARY, 28), date(2012, FEBRUARY, 27), -1}, + {date(2012, FEBRUARY, 28), date(2012, FEBRUARY, 28), 0}, + {date(2012, FEBRUARY, 28), date(2012, FEBRUARY, 29), 1}, + {date(2012, FEBRUARY, 28), date(2012, MARCH, 1), 2}, + + {date(2012, FEBRUARY, 29), date(2012, FEBRUARY, 27), -2}, + {date(2012, FEBRUARY, 29), date(2012, FEBRUARY, 28), -1}, + {date(2012, FEBRUARY, 29), date(2012, FEBRUARY, 29), 0}, + {date(2012, FEBRUARY, 29), date(2012, MARCH, 1), 1}, + + {date(2012, MARCH, 1), date(2012, FEBRUARY, 27), -3}, + {date(2012, MARCH, 1), date(2012, FEBRUARY, 28), -2}, + {date(2012, MARCH, 1), date(2012, FEBRUARY, 29), -1}, + {date(2012, MARCH, 1), date(2012, MARCH, 1), 0}, + {date(2012, MARCH, 1), date(2012, MARCH, 2), 1}, + + {date(2012, MARCH, 1), date(2013, FEBRUARY, 28), 364}, + {date(2012, MARCH, 1), date(2013, MARCH, 1), 365}, + + {date(2011, MARCH, 1), date(2012, FEBRUARY, 28), 364}, + {date(2011, MARCH, 1), date(2012, FEBRUARY, 29), 365}, + {date(2011, MARCH, 1), date(2012, MARCH, 1), 366}, + }; + } + + @Test(dataProvider = "daysBetween") + public void test_daysBetween(LocalDate start, LocalDate end, long expected) { + assertEquals(DAYS.between(start, end).getAmount(), expected); + assertEquals(DAYS.between(start, end).getUnit(), DAYS); + } + + @Test(dataProvider = "daysBetween") + public void test_daysBetweenReversed(LocalDate start, LocalDate end, long expected) { + assertEquals(DAYS.between(end, start).getAmount(), -expected); + assertEquals(DAYS.between(end, start).getUnit(), DAYS); + } + + @Test(dataProvider = "daysBetween") + public void test_daysBetween_LocalDateTimeSameTime(LocalDate start, LocalDate end, long expected) { + assertEquals(DAYS.between(start.atTime(12, 30), end.atTime(12, 30)).getAmount(), expected); + } + + @Test(dataProvider = "daysBetween") + public void test_daysBetween_LocalDateTimeLaterTime(LocalDate start, LocalDate end, long expected) { + assertEquals(DAYS.between(start.atTime(12, 30), end.atTime(12, 31)).getAmount(), expected); + } + + @Test(dataProvider = "daysBetween") + public void test_daysBetween_ZonedDateSameOffset(LocalDate start, LocalDate end, long expected) { + assertEquals(DAYS.between(start.atStartOfDay(ZoneOffset.ofHours(2)), end.atStartOfDay(ZoneOffset.ofHours(2))).getAmount(), expected); + } + + @Test(dataProvider = "daysBetween") + public void test_daysBetween_ZonedDateLaterOffset(LocalDate start, LocalDate end, long expected) { + // +01:00 is later than +02:00 + assertEquals(DAYS.between(start.atStartOfDay(ZoneOffset.ofHours(2)), end.atStartOfDay(ZoneOffset.ofHours(1))).getAmount(), expected); + } + + //----------------------------------------------------------------------- + private static LocalDate date(int year, Month month, int dom) { + return LocalDate.of(year, month, dom); + } + +} diff --git a/test/java/time/test/java/time/temporal/TestDateTimeAdjusters.java b/test/java/time/test/java/time/temporal/TestDateTimeAdjusters.java new file mode 100644 index 0000000000000000000000000000000000000000..bdade55f5729e83a29712755414ab6f368ea41db --- /dev/null +++ b/test/java/time/test/java/time/temporal/TestDateTimeAdjusters.java @@ -0,0 +1,112 @@ +/* + * 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. + * + * 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 test.java.time.temporal; + +import java.time.temporal.*; + +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.util.Collections; + +import org.testng.annotations.Test; + +/** + * Test Adjusters. + */ +@Test(groups={"implementation"}) +public class TestDateTimeAdjusters { + + @SuppressWarnings("rawtypes") + public void test_constructor() throws Exception { + for (Constructor constructor : Adjusters.class.getDeclaredConstructors()) { + assertTrue(Modifier.isPrivate(constructor.getModifiers())); + constructor.setAccessible(true); + constructor.newInstance(Collections.nCopies(constructor.getParameterTypes().length, null).toArray()); + } + } + + public void factory_firstDayOfMonthSame() { + assertSame(Adjusters.firstDayOfMonth(), Adjusters.firstDayOfMonth()); + } + + public void factory_lastDayOfMonthSame() { + assertSame(Adjusters.lastDayOfMonth(), Adjusters.lastDayOfMonth()); + } + + public void factory_firstDayOfNextMonthSame() { + assertSame(Adjusters.firstDayOfNextMonth(), Adjusters.firstDayOfNextMonth()); + } + + public void factory_firstDayOfYearSame() { + assertSame(Adjusters.firstDayOfYear(), Adjusters.firstDayOfYear()); + } + + public void factory_lastDayOfYearSame() { + assertSame(Adjusters.lastDayOfYear(), Adjusters.lastDayOfYear()); + } + + public void factory_firstDayOfNextYearSame() { + assertSame(Adjusters.firstDayOfNextYear(), Adjusters.firstDayOfNextYear()); + } + +} diff --git a/test/java/time/test/java/time/temporal/TestDateTimeBuilderCombinations.java b/test/java/time/test/java/time/temporal/TestDateTimeBuilderCombinations.java new file mode 100644 index 0000000000000000000000000000000000000000..328ead5e4e227a614c5f38fcaa069a58968a2baf --- /dev/null +++ b/test/java/time/test/java/time/temporal/TestDateTimeBuilderCombinations.java @@ -0,0 +1,171 @@ +/* + * 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. + * + * 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 test.java.time.temporal; + +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; +import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.DAY_OF_WEEK; +import static java.time.temporal.ChronoField.DAY_OF_YEAR; +import static java.time.temporal.ChronoField.EPOCH_DAY; +import static java.time.temporal.ChronoField.EPOCH_MONTH; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR; +import static org.testng.Assert.assertEquals; + +import java.time.LocalDate; +import java.time.format.DateTimeBuilder; +import java.time.temporal.TemporalField; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test. + */ +public class TestDateTimeBuilderCombinations { + + @DataProvider(name = "combine") + Object[][] data_combine() { + return new Object[][] { + {YEAR, 2012, MONTH_OF_YEAR, 6, DAY_OF_MONTH, 3, null, null, LocalDate.class, LocalDate.of(2012, 6, 3)}, + {EPOCH_MONTH, (2012 - 1970) * 12 + 6 - 1, DAY_OF_MONTH, 3, null, null, null, null, LocalDate.class, LocalDate.of(2012, 6, 3)}, + {YEAR, 2012, ALIGNED_WEEK_OF_YEAR, 6, DAY_OF_WEEK, 3, null, null, LocalDate.class, LocalDate.of(2012, 2, 8)}, + {YEAR, 2012, DAY_OF_YEAR, 155, null, null, null, null, LocalDate.class, LocalDate.of(2012, 6, 3)}, +// {ERA, 1, YEAR_OF_ERA, 2012, DAY_OF_YEAR, 155, null, null, LocalDate.class, LocalDate.of(2012, 6, 3)}, + {YEAR, 2012, MONTH_OF_YEAR, 6, null, null, null, null, LocalDate.class, null}, + {EPOCH_DAY, 12, null, null, null, null, null, null, LocalDate.class, LocalDate.of(1970, 1, 13)}, + }; + } + + @Test(dataProvider = "combine") + public void test_derive(TemporalField field1, Number value1, TemporalField field2, Number value2, + TemporalField field3, Number value3, TemporalField field4, Number value4, Class query, Object expectedVal) { + DateTimeBuilder builder = new DateTimeBuilder(field1, value1.longValue()); + if (field2 != null) { + builder.addFieldValue(field2, value2.longValue()); + } + if (field3 != null) { + builder.addFieldValue(field3, value3.longValue()); + } + if (field4 != null) { + builder.addFieldValue(field4, value4.longValue()); + } + builder.resolve(); + assertEquals(builder.extract((Class) query), expectedVal); + } + + //----------------------------------------------------------------------- + @DataProvider(name = "normalized") + Object[][] data_normalized() { + return new Object[][] { + {YEAR, 2127, null, null, null, null, YEAR, 2127}, + {MONTH_OF_YEAR, 12, null, null, null, null, MONTH_OF_YEAR, 12}, + {DAY_OF_YEAR, 127, null, null, null, null, DAY_OF_YEAR, 127}, + {DAY_OF_MONTH, 23, null, null, null, null, DAY_OF_MONTH, 23}, + {DAY_OF_WEEK, 127, null, null, null, null, DAY_OF_WEEK, 127L}, + {ALIGNED_WEEK_OF_YEAR, 23, null, null, null, null, ALIGNED_WEEK_OF_YEAR, 23}, + {ALIGNED_DAY_OF_WEEK_IN_YEAR, 4, null, null, null, null, ALIGNED_DAY_OF_WEEK_IN_YEAR, 4L}, + {ALIGNED_WEEK_OF_MONTH, 4, null, null, null, null, ALIGNED_WEEK_OF_MONTH, 4}, + {ALIGNED_DAY_OF_WEEK_IN_MONTH, 3, null, null, null, null, ALIGNED_DAY_OF_WEEK_IN_MONTH, 3}, + {EPOCH_MONTH, 15, null, null, null, null, EPOCH_MONTH, null}, + {EPOCH_MONTH, 15, null, null, null, null, YEAR, 1971}, + {EPOCH_MONTH, 15, null, null, null, null, MONTH_OF_YEAR, 4}, + }; + } + + @Test(dataProvider = "normalized") + public void test_normalized(TemporalField field1, Number value1, TemporalField field2, Number value2, + TemporalField field3, Number value3, TemporalField query, Number expectedVal) { + DateTimeBuilder builder = new DateTimeBuilder(field1, value1.longValue()); + if (field2 != null) { + builder.addFieldValue(field2, value2.longValue()); + } + if (field3 != null) { + builder.addFieldValue(field3, value3.longValue()); + } + builder.resolve(); + if (expectedVal != null) { + assertEquals(builder.getLong(query), expectedVal.longValue()); + } else { + assertEquals(builder.containsFieldValue(query), false); + } + } + + //----------------------------------------------------------------------- + // TODO: maybe reinstate +// public void test_split() { +// DateTimeBuilder builder = new DateTimeBuilder(); +// builder.addCalendrical(LocalDateTime.of(2012, 6, 30, 12, 30)); +// builder.addCalendrical(ZoneOffset.ofHours(2)); +// builder.resolve(); +// assertEquals(builder.build(LocalDate.class), LocalDate.of(2012, 6, 30)); +// assertEquals(builder.build(LocalTime.class), LocalTime.of(12, 30)); +// assertEquals(builder.build(ZoneOffset.class), ZoneOffset.ofHours(2)); +// +// assertEquals(builder.build(LocalDateTime.class), LocalDateTime.of(2012, 6, 30, 12, 30)); +// assertEquals(builder.build(OffsetDate.class), OffsetDate.of(LocalDate.of(2012, 6, 30), ZoneOffset.ofHours(2))); +// assertEquals(builder.build(OffsetTime.class), OffsetTime.of(LocalTime.of(12, 30), ZoneOffset.ofHours(2))); +//// assertEquals(builder.build(OffsetDateTime.class), OffsetDateTime.of(2012, 6, 30, 12, 30, ZoneOffset.ofHours(2))); +// } + +} diff --git a/test/java/time/test/java/time/temporal/TestDateTimeValueRange.java b/test/java/time/test/java/time/temporal/TestDateTimeValueRange.java new file mode 100644 index 0000000000000000000000000000000000000000..d7397d41e64f42941cc3e998a414f71694bd392f --- /dev/null +++ b/test/java/time/test/java/time/temporal/TestDateTimeValueRange.java @@ -0,0 +1,274 @@ +/* + * 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. + * + * 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 test.java.time.temporal; + +import static org.testng.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import java.time.temporal.ValueRange; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import test.java.time.AbstractTest; + +/** + * Test. + */ +@Test +public class TestDateTimeValueRange extends AbstractTest { + + //----------------------------------------------------------------------- + // Basics + //----------------------------------------------------------------------- + @Test + public void test_immutable() { + assertImmutable(ValueRange.class); + } + + //----------------------------------------------------------------------- + // Serialization + //----------------------------------------------------------------------- + public void test_serialization() throws Exception { + Object obj = ValueRange.of(1, 2, 3, 4); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(obj); + oos.close(); + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())); + assertEquals(ois.readObject(), obj); + } + + //----------------------------------------------------------------------- + // of(long,long) + //----------------------------------------------------------------------- + public void test_of_longlong() { + ValueRange test = ValueRange.of(1, 12); + assertEquals(test.getMinimum(), 1); + assertEquals(test.getLargestMinimum(), 1); + assertEquals(test.getSmallestMaximum(), 12); + assertEquals(test.getMaximum(), 12); + assertEquals(test.isFixed(), true); + assertEquals(test.isIntValue(), true); + } + + public void test_of_longlong_big() { + ValueRange test = ValueRange.of(1, 123456789012345L); + assertEquals(test.getMinimum(), 1); + assertEquals(test.getLargestMinimum(), 1); + assertEquals(test.getSmallestMaximum(), 123456789012345L); + assertEquals(test.getMaximum(), 123456789012345L); + assertEquals(test.isFixed(), true); + assertEquals(test.isIntValue(), false); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void test_of_longlong_minGtMax() { + ValueRange.of(12, 1); + } + + //----------------------------------------------------------------------- + // of(long,long,long) + //----------------------------------------------------------------------- + public void test_of_longlonglong() { + ValueRange test = ValueRange.of(1, 28, 31); + assertEquals(test.getMinimum(), 1); + assertEquals(test.getLargestMinimum(), 1); + assertEquals(test.getSmallestMaximum(), 28); + assertEquals(test.getMaximum(), 31); + assertEquals(test.isFixed(), false); + assertEquals(test.isIntValue(), true); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void test_of_longlonglong_minGtMax() { + ValueRange.of(12, 1, 2); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void test_of_longlonglong_smallestmaxminGtMax() { + ValueRange.of(1, 31, 28); + } + + //----------------------------------------------------------------------- + // of(long,long,long,long) + //----------------------------------------------------------------------- + @DataProvider(name="valid") + Object[][] data_valid() { + return new Object[][] { + {1, 1, 1, 1}, + {1, 1, 1, 2}, + {1, 1, 2, 2}, + {1, 2, 3, 4}, + {1, 1, 28, 31}, + {1, 3, 31, 31}, + {-5, -4, -3, -2}, + {-5, -4, 3, 4}, + {1, 20, 10, 31}, + }; + } + + @Test(dataProvider="valid") + public void test_of_longlonglonglong(long sMin, long lMin, long sMax, long lMax) { + ValueRange test = ValueRange.of(sMin, lMin, sMax, lMax); + assertEquals(test.getMinimum(), sMin); + assertEquals(test.getLargestMinimum(), lMin); + assertEquals(test.getSmallestMaximum(), sMax); + assertEquals(test.getMaximum(), lMax); + assertEquals(test.isFixed(), sMin == lMin && sMax == lMax); + assertEquals(test.isIntValue(), true); + } + + @DataProvider(name="invalid") + Object[][] data_invalid() { + return new Object[][] { + {1, 2, 31, 28}, + {1, 31, 2, 28}, + {31, 2, 1, 28}, + {31, 2, 3, 28}, + + {2, 1, 28, 31}, + {2, 1, 31, 28}, + {12, 13, 1, 2}, + }; + } + + @Test(dataProvider="invalid", expectedExceptions=IllegalArgumentException.class) + public void test_of_longlonglonglong_invalid(long sMin, long lMin, long sMax, long lMax) { + ValueRange.of(sMin, lMin, sMax, lMax); + } + + //----------------------------------------------------------------------- + // isValidValue(long) + //----------------------------------------------------------------------- + public void test_isValidValue_long() { + ValueRange test = ValueRange.of(1, 28, 31); + assertEquals(test.isValidValue(0), false); + assertEquals(test.isValidValue(1), true); + assertEquals(test.isValidValue(2), true); + assertEquals(test.isValidValue(30), true); + assertEquals(test.isValidValue(31), true); + assertEquals(test.isValidValue(32), false); + } + + //----------------------------------------------------------------------- + // isValidIntValue(long) + //----------------------------------------------------------------------- + public void test_isValidValue_long_int() { + ValueRange test = ValueRange.of(1, 28, 31); + assertEquals(test.isValidValue(0), false); + assertEquals(test.isValidValue(1), true); + assertEquals(test.isValidValue(31), true); + assertEquals(test.isValidValue(32), false); + } + + public void test_isValidValue_long_long() { + ValueRange test = ValueRange.of(1, 28, Integer.MAX_VALUE + 1L); + assertEquals(test.isValidIntValue(0), false); + assertEquals(test.isValidIntValue(1), false); + assertEquals(test.isValidIntValue(31), false); + assertEquals(test.isValidIntValue(32), false); + } + + //----------------------------------------------------------------------- + // equals() / hashCode() + //----------------------------------------------------------------------- + public void test_equals1() { + ValueRange a = ValueRange.of(1, 2, 3, 4); + ValueRange b = ValueRange.of(1, 2, 3, 4); + assertEquals(a.equals(a), true); + assertEquals(a.equals(b), true); + assertEquals(b.equals(a), true); + assertEquals(b.equals(b), true); + assertEquals(a.hashCode() == b.hashCode(), true); + } + + public void test_equals2() { + ValueRange a = ValueRange.of(1, 2, 3, 4); + assertEquals(a.equals(ValueRange.of(0, 2, 3, 4)), false); + assertEquals(a.equals(ValueRange.of(1, 3, 3, 4)), false); + assertEquals(a.equals(ValueRange.of(1, 2, 4, 4)), false); + assertEquals(a.equals(ValueRange.of(1, 2, 3, 5)), false); + } + + public void test_equals_otherType() { + ValueRange a = ValueRange.of(1, 12); + assertEquals(a.equals("Rubbish"), false); + } + + public void test_equals_null() { + ValueRange a = ValueRange.of(1, 12); + assertEquals(a.equals(null), false); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + public void test_toString() { + assertEquals(ValueRange.of(1, 1, 4, 4).toString(), "1 - 4"); + assertEquals(ValueRange.of(1, 1, 3, 4).toString(), "1 - 3/4"); + assertEquals(ValueRange.of(1, 2, 3, 4).toString(), "1/2 - 3/4"); + assertEquals(ValueRange.of(1, 2, 4, 4).toString(), "1/2 - 4"); + } + +} diff --git a/test/java/time/test/java/time/temporal/TestISOChronoImpl.java b/test/java/time/test/java/time/temporal/TestISOChronoImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..9fc0155320220c5f9937574897822e4b849715b0 --- /dev/null +++ b/test/java/time/test/java/time/temporal/TestISOChronoImpl.java @@ -0,0 +1,181 @@ +/* + * 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) 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 test.java.time.temporal; + +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.YEAR; +import static java.time.temporal.ChronoField.YEAR_OF_ERA; + +import static org.testng.Assert.assertEquals; + +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.time.temporal.ISOChrono; +import java.time.temporal.WeekFields; +import java.time.temporal.ChronoLocalDate; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test. + */ +@Test +public class TestISOChronoImpl { + + @DataProvider(name = "RangeVersusCalendar") + Object[][] provider_rangeVersusCalendar() { + return new Object[][]{ + {LocalDate.of(1900, 1, 4), LocalDate.of(2100, 1, 8)}, + //{LocalDate.of(1583, 1, 1), LocalDate.of(2100, 1, 1)}, + }; + } + + //----------------------------------------------------------------------- + // Verify ISO Calendar matches java.util.Calendar for range + //----------------------------------------------------------------------- + @Test(groups = {"implementation"}, dataProvider = "RangeVersusCalendar") + public void test_ISOChrono_vsCalendar(LocalDate isoStartDate, LocalDate isoEndDate) { + GregorianCalendar cal = new GregorianCalendar(); + assertEquals(cal.getCalendarType(), "gregory", "Unexpected calendar type"); + ChronoLocalDate isoDate = ISOChrono.INSTANCE.date(isoStartDate); + + cal.setTimeZone(TimeZone.getTimeZone("GMT+00")); + cal.set(Calendar.YEAR, isoDate.get(YEAR)); + cal.set(Calendar.MONTH, isoDate.get(MONTH_OF_YEAR) - 1); + cal.set(Calendar.DAY_OF_MONTH, isoDate.get(DAY_OF_MONTH)); + + while (isoDate.isBefore(isoEndDate)) { + assertEquals(isoDate.get(DAY_OF_MONTH), cal.get(Calendar.DAY_OF_MONTH), "Day mismatch in " + isoDate + "; cal: " + cal); + assertEquals(isoDate.get(MONTH_OF_YEAR), cal.get(Calendar.MONTH) + 1, "Month mismatch in " + isoDate); + assertEquals(isoDate.get(YEAR_OF_ERA), cal.get(Calendar.YEAR), "Year mismatch in " + isoDate); + + isoDate = isoDate.plus(1, ChronoUnit.DAYS); + cal.add(Calendar.DAY_OF_MONTH, 1); + } + } + + //----------------------------------------------------------------------- + // Verify ISO Calendar matches java.util.Calendar + // DayOfWeek, WeekOfMonth, WeekOfYear for range + //----------------------------------------------------------------------- + @Test(groups = {"implementation"}, dataProvider = "RangeVersusCalendar") + public void test_DayOfWeek_ISOChrono_vsCalendar(LocalDate isoStartDate, LocalDate isoEndDate) { + GregorianCalendar cal = new GregorianCalendar(); + assertEquals(cal.getCalendarType(), "gregory", "Unexpected calendar type"); + ChronoLocalDate isoDate = ISOChrono.INSTANCE.date(isoStartDate); + + for (DayOfWeek firstDayOfWeek : DayOfWeek.values()) { + for (int minDays = 1; minDays <= 7; minDays++) { + WeekFields weekDef = WeekFields.of(firstDayOfWeek, minDays); + cal.setFirstDayOfWeek(Math.floorMod(firstDayOfWeek.getValue(), 7) + 1); + cal.setMinimalDaysInFirstWeek(minDays); + + cal.setTimeZone(TimeZone.getTimeZone("GMT+00")); + cal.set(Calendar.YEAR, isoDate.get(YEAR)); + cal.set(Calendar.MONTH, isoDate.get(MONTH_OF_YEAR) - 1); + cal.set(Calendar.DAY_OF_MONTH, isoDate.get(DAY_OF_MONTH)); + + while (isoDate.isBefore(isoEndDate)) { + assertEquals(isoDate.get(DAY_OF_MONTH), cal.get(Calendar.DAY_OF_MONTH), "Day mismatch in " + isoDate + "; cal: " + cal); + assertEquals(isoDate.get(MONTH_OF_YEAR), cal.get(Calendar.MONTH) + 1, "Month mismatch in " + isoDate); + assertEquals(isoDate.get(YEAR_OF_ERA), cal.get(Calendar.YEAR), "Year mismatch in " + isoDate); + int jDOW = Math.floorMod(cal.get(Calendar.DAY_OF_WEEK) - 2, 7) + 1; + int isoDOW = isoDate.get(weekDef.dayOfWeek()); + if (jDOW != isoDOW) { + System.err.printf(" DOW vs Calendar jdow: %s, isoDate(DOW): %s, isoDate: %s, WeekDef: %s%n", jDOW, isoDOW, isoDate, weekDef); + } + assertEquals(jDOW, isoDOW, "Calendar DayOfWeek does not match ISO DayOfWeek"); + + int jweekOfMonth = cal.get(Calendar.WEEK_OF_MONTH); + int isoWeekOfMonth = isoDate.get(weekDef.weekOfMonth()); + if (jweekOfMonth != isoWeekOfMonth) { + System.err.printf(" WeekOfMonth jWeekOfMonth: %s, isoWeekOfMonth: %s, isoDate: %s, %s%n", + jweekOfMonth, isoWeekOfMonth, isoDate, weekDef); + } + assertEquals(jweekOfMonth, isoWeekOfMonth, "Calendar WeekOfMonth does not match ISO WeekOfMonth"); + + int jweekOfYear = cal.get(Calendar.WEEK_OF_YEAR); + int isoWeekOfYear = isoDate.get(weekDef.weekOfYear()); + if (jweekOfYear != isoWeekOfYear) { + // TBD: Issue #186 Remove misleading output pending resolution + // System.err.printf(" Mismatch WeekOfYear jweekOfYear: %s, isoWeekOfYear: %s, isoDate: %s, WeekDef: %s%n", jweekOfYear, isoWeekOfYear, isoDate, weekDef); + } + //assertEquals(jweekOfYear, isoWeekOfYear, "Calendar WeekOfYear does not match ISO WeekOfYear"); + + isoDate = isoDate.plus(1, ChronoUnit.DAYS); + cal.add(Calendar.DAY_OF_MONTH, 1); + } + } + } + } + + /** + * Return the ISO Day of Week from a java.util.Calendr DAY_OF_WEEK. + * @param the java.util.Calendar day of week (1=Sunday, 7=Saturday) + * @return the ISO DayOfWeek + */ + private DayOfWeek toISOfromCalendarDOW(int i) { + return DayOfWeek.of(Math.floorMod(i - 2, 7) + 1); + } +} diff --git a/test/java/time/test/java/time/temporal/TestJapaneseChronoImpl.java b/test/java/time/test/java/time/temporal/TestJapaneseChronoImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..cadd1a378c64c6baff915d76837f96dffb2834f8 --- /dev/null +++ b/test/java/time/test/java/time/temporal/TestJapaneseChronoImpl.java @@ -0,0 +1,121 @@ +/* + * 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) 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 test.java.time.temporal; + +import static org.testng.Assert.assertEquals; + +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.temporal.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoLocalDate; +import java.time.temporal.ChronoUnit; +import java.time.calendar.JapaneseChrono; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test. + */ +@Test +public class TestJapaneseChronoImpl { + + /** + * Range of years to check consistency with java.util.Calendar + */ + @DataProvider(name="RangeVersusCalendar") + Object[][] provider_rangeVersusCalendar() { + return new Object[][] { + {LocalDate.of(1868, 1, 1), LocalDate.of(2100, 1, 1)}, + }; + } + + //----------------------------------------------------------------------- + // Verify Japanese Calendar matches java.util.Calendar for range + //----------------------------------------------------------------------- + @Test(groups={"implementation"}, dataProvider="RangeVersusCalendar") + public void test_JapaneseChrono_vsCalendar(LocalDate isoStartDate, LocalDate isoEndDate) { + Locale locale = Locale.forLanguageTag("ja-JP-u-ca-japanese"); + assertEquals(locale.toString(), "ja_JP_#u-ca-japanese", "Unexpected locale"); + + Calendar cal = java.util.Calendar.getInstance(locale); + assertEquals(cal.getCalendarType(), "japanese", "Unexpected calendar type"); + + ChronoLocalDate jDate = JapaneseChrono.INSTANCE.date(isoStartDate); + + // Convert to millis and set Japanese Calendar to that start date (at GMT) + OffsetDateTime jodt = OffsetDateTime.of(isoStartDate, LocalTime.MIN, ZoneOffset.UTC); + long millis = jodt.toInstant().toEpochMilli(); + cal.setTimeZone(TimeZone.getTimeZone("GMT+00")); + cal.setTimeInMillis(millis); + + while (jDate.isBefore(isoEndDate)) { + assertEquals(jDate.get(ChronoField.DAY_OF_MONTH), cal.get(Calendar.DAY_OF_MONTH), "Day mismatch in " + jDate + "; cal: " + cal); + assertEquals(jDate.get(ChronoField.MONTH_OF_YEAR), cal.get(Calendar.MONTH) + 1, "Month mismatch in " + jDate); + assertEquals(jDate.get(ChronoField.YEAR_OF_ERA), cal.get(Calendar.YEAR), "Year mismatch in " + jDate); + + jDate = jDate.plus(1, ChronoUnit.DAYS); + cal.add(Calendar.DAY_OF_MONTH, 1); + } + } + +} diff --git a/test/java/time/test/java/time/temporal/TestMonthDay.java b/test/java/time/test/java/time/temporal/TestMonthDay.java new file mode 100644 index 0000000000000000000000000000000000000000..002d4f7e286ff82b3a74be55ace55530170b437d --- /dev/null +++ b/test/java/time/test/java/time/temporal/TestMonthDay.java @@ -0,0 +1,148 @@ +/* + * 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. + * + * 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 test.java.time.temporal; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.time.LocalDate; +import java.time.Month; +import java.time.temporal.MonthDay; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import test.java.time.AbstractTest; + +/** + * Test MonthDay. + */ +@Test +public class TestMonthDay extends AbstractTest { + + private MonthDay TEST_07_15; + + @BeforeMethod(groups={"tck","implementation"}) + public void setUp() { + TEST_07_15 = MonthDay.of(7, 15); + } + + //----------------------------------------------------------------------- + @Test + public void test_immutable() { + assertImmutable(MonthDay.class); + } + + //----------------------------------------------------------------------- + void check(MonthDay test, int m, int d) { + assertEquals(test.getMonth().getValue(), m); + assertEquals(test.getDayOfMonth(), d); + } + + @Test(groups={"implementation"}) + public void test_with_Month_noChangeSame() { + MonthDay test = MonthDay.of(6, 30); + assertSame(test.with(Month.JUNE), test); + } + + @Test(groups={"implementation"}) + public void test_withMonth_int_noChangeSame() { + MonthDay test = MonthDay.of(6, 30); + assertSame(test.withMonth(6), test); + } + @Test(groups={"implementation"}) + public void test_withDayOfMonth_noChangeSame() { + MonthDay test = MonthDay.of(6, 30); + assertSame(test.withDayOfMonth(30), test); + } + + @Test(groups={"implementation"}) + public void test_adjustDate_same() { + MonthDay test = MonthDay.of(6, 30); + LocalDate date = LocalDate.of(2007, 6, 30); + assertSame(test.adjustInto(date), date); + } + + void doTest_comparisons_MonthDay(MonthDay... localDates) { + for (int i = 0; i < localDates.length; i++) { + MonthDay a = localDates[i]; + for (int j = 0; j < localDates.length; j++) { + MonthDay b = localDates[j]; + if (i < j) { + assertTrue(a.compareTo(b) < 0, a + " <=> " + b); + assertEquals(a.isBefore(b), true, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else if (i > j) { + assertTrue(a.compareTo(b) > 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), true, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else { + assertEquals(a.compareTo(b), 0, a + " <=> " + b); + assertEquals(a.isBefore(b), false, a + " <=> " + b); + assertEquals(a.isAfter(b), false, a + " <=> " + b); + assertEquals(a.equals(b), true, a + " <=> " + b); + } + } + } + } + +} diff --git a/test/java/time/test/java/time/temporal/TestOffsetDate.java b/test/java/time/test/java/time/temporal/TestOffsetDate.java new file mode 100644 index 0000000000000000000000000000000000000000..5ba00329b8fbea5a278809d874b86ec884bdc45d --- /dev/null +++ b/test/java/time/test/java/time/temporal/TestOffsetDate.java @@ -0,0 +1,78 @@ +/* + * 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. + * + * 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 test.java.time.temporal; + +import java.time.temporal.OffsetDate; + +import org.testng.annotations.Test; +import test.java.time.AbstractTest; + +/** + * Test OffsetDate. + */ +@Test +public class TestOffsetDate extends AbstractTest { + + @Test + public void test_immutable() { + assertImmutable(OffsetDate.class); + } + +} diff --git a/test/java/time/test/java/time/temporal/TestOffsetDateTime.java b/test/java/time/test/java/time/temporal/TestOffsetDateTime.java new file mode 100644 index 0000000000000000000000000000000000000000..9ee46725c8a8f4faa88537cffd04d687be7bd919 --- /dev/null +++ b/test/java/time/test/java/time/temporal/TestOffsetDateTime.java @@ -0,0 +1,325 @@ +/* + * 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. + * + * 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 test.java.time.temporal; + +import static org.testng.Assert.assertSame; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.time.temporal.OffsetDateTime; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import test.java.time.AbstractTest; +import test.java.time.MockSimplePeriod; + +/** + * Test OffsetDateTime. + */ +@Test +public class TestOffsetDateTime extends AbstractTest { + + private static final ZoneOffset OFFSET_PONE = ZoneOffset.ofHours(1); + private static final ZoneOffset OFFSET_PTWO = ZoneOffset.ofHours(2); + private OffsetDateTime TEST_2008_6_30_11_30_59_000000500; + + @BeforeMethod(groups={"tck","implementation"}) + public void setUp() { + TEST_2008_6_30_11_30_59_000000500 = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59, 500), OFFSET_PONE); + } + + @Test + public void test_immutable() { + assertImmutable(OffsetDateTime.class); + } + + //----------------------------------------------------------------------- + // basics + //----------------------------------------------------------------------- + @DataProvider(name="sampleTimes") + Object[][] provider_sampleTimes() { + return new Object[][] { + {2008, 6, 30, 11, 30, 20, 500, OFFSET_PONE}, + {2008, 6, 30, 11, 0, 0, 0, OFFSET_PONE}, + {2008, 6, 30, 23, 59, 59, 999999999, OFFSET_PONE}, + {-1, 1, 1, 0, 0, 0, 0, OFFSET_PONE}, + }; + } + + @Test(dataProvider="sampleTimes", groups={"implementation"}) + public void test_get_same(int y, int o, int d, int h, int m, int s, int n, ZoneOffset offset) { + LocalDate localDate = LocalDate.of(y, o, d); + LocalTime localTime = LocalTime.of(h, m, s, n); + LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime); + OffsetDateTime a = OffsetDateTime.of(localDateTime, offset); + + assertSame(a.getOffset(), offset); + assertSame(a.getDate(), localDate); + assertSame(a.getTime(), localTime); + assertSame(a.getDateTime(), localDateTime); + } + + //----------------------------------------------------------------------- + // withOffsetSameLocal() + //----------------------------------------------------------------------- + @Test(groups={"implementation"}) + public void test_withOffsetSameLocal() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withOffsetSameLocal(OFFSET_PTWO); + assertSame(test.getDateTime(), base.getDateTime()); + assertSame(test.getOffset(), OFFSET_PTWO); + } + + @Test(groups={"implementation"}) + public void test_withOffsetSameLocal_noChange() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withOffsetSameLocal(OFFSET_PONE); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_withOffsetSameInstant_noChange() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withOffsetSameInstant(OFFSET_PONE); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_withYear_noChange() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withYear(2008); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_withMonth_noChange() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withMonth(6); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_withDayOfMonth_noChange() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withDayOfMonth(30); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_withDayOfYear_noChange() { + OffsetDateTime t = TEST_2008_6_30_11_30_59_000000500.withDayOfYear(31 + 29 + 31 + 30 + 31 + 30); + assertSame(t, TEST_2008_6_30_11_30_59_000000500); + } + + @Test(groups={"implementation"}) + public void test_withHour_noChange() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withHour(11); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_withMinute_noChange() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withMinute(30); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_withSecond_noChange() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.withSecond(59); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_withNanoOfSecond_noChange() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59, 1), OFFSET_PONE); + OffsetDateTime test = base.withNano(1); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_plus_Period_zero() { + OffsetDateTime t = TEST_2008_6_30_11_30_59_000000500.plus(MockSimplePeriod.ZERO_DAYS); + assertSame(t, TEST_2008_6_30_11_30_59_000000500); + } + + @Test(groups={"implementation"}) + public void test_plusYears_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.plusYears(0); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_plusMonths_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.plusMonths(0); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_plusWeeks_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.plusWeeks(0); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_plusDays_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.plusDays(0); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_plusHours_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.plusHours(0); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_plusMinutes_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.plusMinutes(0); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_plusSeconds_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.plusSeconds(0); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_plusNanos_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.plusNanos(0); + } + + @Test(groups={"implementation"}) + public void test_minus_Period_zero() { + OffsetDateTime t = TEST_2008_6_30_11_30_59_000000500.minus(MockSimplePeriod.ZERO_DAYS); + assertSame(t, TEST_2008_6_30_11_30_59_000000500); + } + + @Test(groups={"implementation"}) + public void test_minusYears_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2007, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.minusYears(0); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_minusMonths_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.minusMonths(0); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_minusWeeks_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.minusWeeks(0); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_minusDays_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.minusDays(0); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_minusHours_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.minusHours(0); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_minusMinutes_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.minusMinutes(0); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_minusSeconds_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.minusSeconds(0); + assertSame(test, base); + } + + @Test(groups={"implementation"}) + public void test_minusNanos_zero() { + OffsetDateTime base = OffsetDateTime.of(LocalDate.of(2008, 6, 30), LocalTime.of(11, 30, 59), OFFSET_PONE); + OffsetDateTime test = base.minusNanos(0); + assertSame(test, base); + } + +} diff --git a/test/java/time/test/java/time/temporal/TestOffsetDateTime_instants.java b/test/java/time/test/java/time/temporal/TestOffsetDateTime_instants.java new file mode 100644 index 0000000000000000000000000000000000000000..7922c37589f19dd9bad3ff1c6884e67883e0c86b --- /dev/null +++ b/test/java/time/test/java/time/temporal/TestOffsetDateTime_instants.java @@ -0,0 +1,347 @@ +/* + * 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. + * + * 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 test.java.time.temporal; + +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneOffset; + +import java.time.temporal.OffsetDateTime; +import java.time.temporal.Year; + +import static org.testng.Assert.assertEquals; + +import org.testng.annotations.Test; + +/** + * Test OffsetDate creation. + */ +@Test +public class TestOffsetDateTime_instants { + + private static final ZoneOffset OFFSET_PONE = ZoneOffset.ofHours(1); + private static final ZoneOffset OFFSET_MAX = ZoneOffset.ofHours(18); + private static final ZoneOffset OFFSET_MIN = ZoneOffset.ofHours(-18); + + //----------------------------------------------------------------------- + @Test(expectedExceptions=NullPointerException.class) + public void factory_ofInstant_nullInstant() { + OffsetDateTime.ofInstant((Instant) null, OFFSET_PONE); + } + + @Test(expectedExceptions=NullPointerException.class) + public void factory_ofInstant_nullOffset() { + Instant instant = Instant.ofEpochSecond(0L); + OffsetDateTime.ofInstant(instant, (ZoneOffset) null); + } + + public void factory_ofInstant_allSecsInDay() { + for (int i = 0; i < (24 * 60 * 60); i++) { + Instant instant = Instant.ofEpochSecond(i); + OffsetDateTime test = OffsetDateTime.ofInstant(instant, OFFSET_PONE); + assertEquals(test.getYear(), 1970); + assertEquals(test.getMonth(), Month.JANUARY); + assertEquals(test.getDayOfMonth(), 1 + (i >= 23 * 60 * 60 ? 1 : 0)); + assertEquals(test.getHour(), ((i / (60 * 60)) + 1) % 24); + assertEquals(test.getMinute(), (i / 60) % 60); + assertEquals(test.getSecond(), i % 60); + } + } + + public void factory_ofInstant_allDaysInCycle() { + // sanity check using different algorithm + OffsetDateTime expected = OffsetDateTime.of(LocalDate.of(1970, 1, 1), LocalTime.of(0, 0, 0, 0), ZoneOffset.UTC); + for (long i = 0; i < 146097; i++) { + Instant instant = Instant.ofEpochSecond(i * 24L * 60L * 60L); + OffsetDateTime test = OffsetDateTime.ofInstant(instant, ZoneOffset.UTC); + assertEquals(test, expected); + expected = expected.plusDays(1); + } + } + + public void factory_ofInstant_history() { + doTest_factory_ofInstant_all(-2820, 2820); + } + + //----------------------------------------------------------------------- + public void factory_ofInstant_minYear() { + doTest_factory_ofInstant_all(Year.MIN_VALUE, Year.MIN_VALUE + 420); + } + + @Test(expectedExceptions=DateTimeException.class) + public void factory_ofInstant_tooLow() { + long days_0000_to_1970 = (146097 * 5) - (30 * 365 + 7); + int year = Year.MIN_VALUE - 1; + long days = (year * 365L + (year / 4 - year / 100 + year / 400)) - days_0000_to_1970; + Instant instant = Instant.ofEpochSecond(days * 24L * 60L * 60L); + OffsetDateTime.ofInstant(instant, ZoneOffset.UTC); + } + + public void factory_ofInstant_maxYear() { + doTest_factory_ofInstant_all(Year.MAX_VALUE - 420, Year.MAX_VALUE); + } + + @Test(expectedExceptions=DateTimeException.class) + public void factory_ofInstant_tooBig() { + long days_0000_to_1970 = (146097 * 5) - (30 * 365 + 7); + long year = Year.MAX_VALUE + 1L; + long days = (year * 365L + (year / 4 - year / 100 + year / 400)) - days_0000_to_1970; + Instant instant = Instant.ofEpochSecond(days * 24L * 60L * 60L); + OffsetDateTime.ofInstant(instant, ZoneOffset.UTC); + } + + //----------------------------------------------------------------------- + public void factory_ofInstant_minWithMinOffset() { + long days_0000_to_1970 = (146097 * 5) - (30 * 365 + 7); + int year = Year.MIN_VALUE; + long days = (year * 365L + (year / 4 - year / 100 + year / 400)) - days_0000_to_1970; + Instant instant = Instant.ofEpochSecond(days * 24L * 60L * 60L - OFFSET_MIN.getTotalSeconds()); + OffsetDateTime test = OffsetDateTime.ofInstant(instant, OFFSET_MIN); + assertEquals(test.getYear(), Year.MIN_VALUE); + assertEquals(test.getMonth().getValue(), 1); + assertEquals(test.getDayOfMonth(), 1); + assertEquals(test.getOffset(), OFFSET_MIN); + assertEquals(test.getHour(), 0); + assertEquals(test.getMinute(), 0); + assertEquals(test.getSecond(), 0); + assertEquals(test.getNano(), 0); + } + + public void factory_ofInstant_minWithMaxOffset() { + long days_0000_to_1970 = (146097 * 5) - (30 * 365 + 7); + int year = Year.MIN_VALUE; + long days = (year * 365L + (year / 4 - year / 100 + year / 400)) - days_0000_to_1970; + Instant instant = Instant.ofEpochSecond(days * 24L * 60L * 60L - OFFSET_MAX.getTotalSeconds()); + OffsetDateTime test = OffsetDateTime.ofInstant(instant, OFFSET_MAX); + assertEquals(test.getYear(), Year.MIN_VALUE); + assertEquals(test.getMonth().getValue(), 1); + assertEquals(test.getDayOfMonth(), 1); + assertEquals(test.getOffset(), OFFSET_MAX); + assertEquals(test.getHour(), 0); + assertEquals(test.getMinute(), 0); + assertEquals(test.getSecond(), 0); + assertEquals(test.getNano(), 0); + } + + public void factory_ofInstant_maxWithMinOffset() { + long days_0000_to_1970 = (146097 * 5) - (30 * 365 + 7); + int year = Year.MAX_VALUE; + long days = (year * 365L + (year / 4 - year / 100 + year / 400)) + 365 - days_0000_to_1970; + Instant instant = Instant.ofEpochSecond((days + 1) * 24L * 60L * 60L - 1 - OFFSET_MIN.getTotalSeconds()); + OffsetDateTime test = OffsetDateTime.ofInstant(instant, OFFSET_MIN); + assertEquals(test.getYear(), Year.MAX_VALUE); + assertEquals(test.getMonth().getValue(), 12); + assertEquals(test.getDayOfMonth(), 31); + assertEquals(test.getOffset(), OFFSET_MIN); + assertEquals(test.getHour(), 23); + assertEquals(test.getMinute(), 59); + assertEquals(test.getSecond(), 59); + assertEquals(test.getNano(), 0); + } + + public void factory_ofInstant_maxWithMaxOffset() { + long days_0000_to_1970 = (146097 * 5) - (30 * 365 + 7); + int year = Year.MAX_VALUE; + long days = (year * 365L + (year / 4 - year / 100 + year / 400)) + 365 - days_0000_to_1970; + Instant instant = Instant.ofEpochSecond((days + 1) * 24L * 60L * 60L - 1 - OFFSET_MAX.getTotalSeconds()); + OffsetDateTime test = OffsetDateTime.ofInstant(instant, OFFSET_MAX); + assertEquals(test.getYear(), Year.MAX_VALUE); + assertEquals(test.getMonth().getValue(), 12); + assertEquals(test.getDayOfMonth(), 31); + assertEquals(test.getOffset(), OFFSET_MAX); + assertEquals(test.getHour(), 23); + assertEquals(test.getMinute(), 59); + assertEquals(test.getSecond(), 59); + assertEquals(test.getNano(), 0); + } + + //----------------------------------------------------------------------- + @Test(expectedExceptions=DateTimeException.class) + public void factory_ofInstant_maxInstantWithMaxOffset() { + Instant instant = Instant.ofEpochSecond(Long.MAX_VALUE); + OffsetDateTime.ofInstant(instant, OFFSET_MAX); + } + + @Test(expectedExceptions=DateTimeException.class) + public void factory_ofInstant_maxInstantWithMinOffset() { + Instant instant = Instant.ofEpochSecond(Long.MAX_VALUE); + OffsetDateTime.ofInstant(instant, OFFSET_MIN); + } + + //----------------------------------------------------------------------- + private void doTest_factory_ofInstant_all(long minYear, long maxYear) { + long days_0000_to_1970 = (146097 * 5) - (30 * 365 + 7); + int minOffset = (minYear <= 0 ? 0 : 3); + int maxOffset = (maxYear <= 0 ? 0 : 3); + long minDays = (minYear * 365L + ((minYear + minOffset) / 4L - (minYear + minOffset) / 100L + (minYear + minOffset) / 400L)) - days_0000_to_1970; + long maxDays = (maxYear * 365L + ((maxYear + maxOffset) / 4L - (maxYear + maxOffset) / 100L + (maxYear + maxOffset) / 400L)) + 365L - days_0000_to_1970; + + final LocalDate maxDate = LocalDate.of(Year.MAX_VALUE, 12, 31); + OffsetDateTime expected = OffsetDateTime.of(LocalDate.of((int) minYear, 1, 1), LocalTime.of(0, 0, 0, 0), ZoneOffset.UTC); + for (long i = minDays; i < maxDays; i++) { + Instant instant = Instant.ofEpochSecond(i * 24L * 60L * 60L); + try { + OffsetDateTime test = OffsetDateTime.ofInstant(instant, ZoneOffset.UTC); + assertEquals(test, expected); + if (expected.getDate().equals(maxDate) == false) { + expected = expected.plusDays(1); + } + } catch (RuntimeException|Error ex) { + System.out.println("Error: " + i + " " + expected); + throw ex; + } + } + } + + // for performance testing + // private void doTest_factory_ofInstant_all(int minYear, int maxYear) { + // long days_0000_to_1970 = (146097 * 5) - (30 * 365 + 7); + // int minOffset = (minYear <= 0 ? 0 : 3); + // int maxOffset = (maxYear <= 0 ? 0 : 3); + // long minDays = (long) (minYear * 365L + ((minYear + minOffset) / 4L - (minYear + minOffset) / 100L + (minYear + minOffset) / 400L)) - days_0000_to_1970; + // long maxDays = (long) (maxYear * 365L + ((maxYear + maxOffset) / 4L - (maxYear + maxOffset) / 100L + (maxYear + maxOffset) / 400L)) + 365L - days_0000_to_1970; + // + // OffsetDateTime expected = OffsetDateTime.dateTime(minYear, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + // Date cutover = new Date(Long.MIN_VALUE); + // GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + // cal.setGregorianChange(cutover); + // for (long i = minDays; i < maxDays; i++) { + // Instant instant = Instant.instant(i * 24L * 60L * 60L); + // try { + // cal.setTimeInMillis(instant.getEpochSecond() * 1000L); + // assertEquals(cal.get(GregorianCalendar.MONTH), expected.getMonth().getValue() - 1); + // assertEquals(cal.get(GregorianCalendar.DAY_OF_MONTH), expected.getDayOfMonth().getValue()); + // expected = expected.plusDays(1); + // } catch (RuntimeException ex) { + // System.out.println("Error: " + i + " " + expected); + // throw ex; + // } catch (Error ex) { + // System.out.println("Error: " + i + " " + expected); + // throw ex; + // } + // } + // } + + //----------------------------------------------------------------------- + public void test_toInstant_19700101() { + OffsetDateTime dt = OffsetDateTime.of(LocalDate.of(1970, 1, 1), LocalTime.of(0, 0, 0, 0), ZoneOffset.UTC); + Instant test = dt.toInstant(); + assertEquals(test.getEpochSecond(), 0); + assertEquals(test.getNano(), 0); + } + + public void test_toInstant_19700101_oneNano() { + OffsetDateTime dt = OffsetDateTime.of(LocalDate.of(1970, 1, 1), LocalTime.of(0, 0, 0, 1), ZoneOffset.UTC); + Instant test = dt.toInstant(); + assertEquals(test.getEpochSecond(), 0); + assertEquals(test.getNano(), 1); + } + + public void test_toInstant_19700101_minusOneNano() { + OffsetDateTime dt = OffsetDateTime.of(LocalDate.of(1969, 12, 31), LocalTime.of(23, 59, 59, 999999999), ZoneOffset.UTC); + Instant test = dt.toInstant(); + assertEquals(test.getEpochSecond(), -1); + assertEquals(test.getNano(), 999999999); + } + + public void test_toInstant_19700102() { + OffsetDateTime dt = OffsetDateTime.of(LocalDate.of(1970, 1, 2), LocalTime.of(0, 0, 0, 0), ZoneOffset.UTC); + Instant test = dt.toInstant(); + assertEquals(test.getEpochSecond(), 24L * 60L * 60L); + assertEquals(test.getNano(), 0); + } + + public void test_toInstant_19691231() { + OffsetDateTime dt = OffsetDateTime.of(LocalDate.of(1969, 12, 31), LocalTime.of(0, 0, 0, 0), ZoneOffset.UTC); + Instant test = dt.toInstant(); + assertEquals(test.getEpochSecond(), -24L * 60L * 60L); + assertEquals(test.getNano(), 0); + } + + //----------------------------------------------------------------------- + public void test_toEpochSecond_19700101() { + OffsetDateTime dt = OffsetDateTime.of(LocalDate.of(1970, 1, 1), LocalTime.of(0, 0, 0, 0), ZoneOffset.UTC); + assertEquals(dt.toEpochSecond(), 0); + } + + public void test_toEpochSecond_19700101_oneNano() { + OffsetDateTime dt = OffsetDateTime.of(LocalDate.of(1970, 1, 1), LocalTime.of( 0, 0, 0, 1), ZoneOffset.UTC); + assertEquals(dt.toEpochSecond(), 0); + } + + public void test_toEpochSecond_19700101_minusOneNano() { + OffsetDateTime dt = OffsetDateTime.of(LocalDate.of(1969, 12, 31), LocalTime.of(23, 59, 59, 999999999), ZoneOffset.UTC); + assertEquals(dt.toEpochSecond(), -1); + } + + public void test_toEpochSecond_19700102() { + OffsetDateTime dt = OffsetDateTime.of(LocalDate.of(1970, 1, 2), LocalTime.of(0, 0, 0, 0), ZoneOffset.UTC); + assertEquals(dt.toEpochSecond(), 24L * 60L * 60L); + } + + public void test_toEpochSecond_19691231() { + OffsetDateTime dt = OffsetDateTime.of(LocalDate.of(1969, 12, 31), LocalTime.of(0, 0, 0, 0), ZoneOffset.UTC); + assertEquals(dt.toEpochSecond(), -24L * 60L * 60L); + } + +} diff --git a/test/java/time/test/java/time/temporal/TestOffsetTime.java b/test/java/time/test/java/time/temporal/TestOffsetTime.java new file mode 100644 index 0000000000000000000000000000000000000000..d6e9eff0b3f843f70a38f319ad8c2aaf88e14cfc --- /dev/null +++ b/test/java/time/test/java/time/temporal/TestOffsetTime.java @@ -0,0 +1,78 @@ +/* + * 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. + * + * 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 test.java.time.temporal; + +import java.time.temporal.OffsetTime; + +import org.testng.annotations.Test; +import test.java.time.AbstractTest; + +/** + * Test OffsetTime. + */ +@Test +public class TestOffsetTime extends AbstractTest { + + @Test + public void test_immutable() { + assertImmutable(OffsetTime.class); + } + +} diff --git a/test/java/time/test/java/time/temporal/TestThaiBuddhistChronoImpl.java b/test/java/time/test/java/time/temporal/TestThaiBuddhistChronoImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..9fd69cfa76bf876ed826ed1f4174310703fbdf62 --- /dev/null +++ b/test/java/time/test/java/time/temporal/TestThaiBuddhistChronoImpl.java @@ -0,0 +1,122 @@ +/* + * 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) 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 test.java.time.temporal; + +import static org.testng.Assert.assertEquals; + +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +import java.time.LocalDate; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.ChronoLocalDate; +import java.time.calendar.ThaiBuddhistChrono; + +import org.testng.annotations.Test; +import org.testng.annotations.DataProvider; + +/** + * Test. + */ +@Test +public class TestThaiBuddhistChronoImpl { + + /** + * Range of years to check consistency with java.util.Calendar + */ + @DataProvider(name="RangeVersusCalendar") + Object[][] provider_rangeVersusCalendar() { + return new Object[][] { + {LocalDate.of(1583, 1, 1), LocalDate.of(2100, 1, 1)}, + }; + } + + //----------------------------------------------------------------------- + // Verify ThaiBuddhist Calendar matches java.util.Calendar for range + //----------------------------------------------------------------------- + @Test(groups={"implementation"}, dataProvider="RangeVersusCalendar") + public void test_ThaiBuddhistChrono_vsCalendar(LocalDate isoStartDate, LocalDate isoEndDate) { + Locale locale = Locale.forLanguageTag("th-TH--u-ca-buddhist"); + assertEquals(locale.toString(), "th_TH", "Unexpected locale"); + Calendar cal = java.util.Calendar.getInstance(locale); + assertEquals(cal.getCalendarType(), "buddhist", "Unexpected calendar type"); + + ChronoLocalDate thaiDate = ThaiBuddhistChrono.INSTANCE.date(isoStartDate); + + cal.setTimeZone(TimeZone.getTimeZone("GMT+00")); + cal.set(Calendar.YEAR, thaiDate.get(ChronoField.YEAR)); + cal.set(Calendar.MONTH, thaiDate.get(ChronoField.MONTH_OF_YEAR) - 1); + cal.set(Calendar.DAY_OF_MONTH, thaiDate.get(ChronoField.DAY_OF_MONTH)); + + while (thaiDate.isBefore(isoEndDate)) { + assertEquals(thaiDate.get(ChronoField.DAY_OF_MONTH), cal.get(Calendar.DAY_OF_MONTH), "Day mismatch in " + thaiDate + "; cal: " + cal); + assertEquals(thaiDate.get(ChronoField.MONTH_OF_YEAR), cal.get(Calendar.MONTH) + 1, "Month mismatch in " + thaiDate); + assertEquals(thaiDate.get(ChronoField.YEAR_OF_ERA), cal.get(Calendar.YEAR), "Year mismatch in " + thaiDate); + + thaiDate = thaiDate.plus(1, ChronoUnit.DAYS); + cal.add(Calendar.DAY_OF_MONTH, 1); + } + } + + private String calToString(Calendar cal) { + return String.format("%04d-%02d-%02d", + cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH)); + } + +} diff --git a/test/java/time/test/java/time/temporal/TestYear.java b/test/java/time/test/java/time/temporal/TestYear.java new file mode 100644 index 0000000000000000000000000000000000000000..5880ac46820be03331fd9ac39b4f77b7d2937e86 --- /dev/null +++ b/test/java/time/test/java/time/temporal/TestYear.java @@ -0,0 +1,78 @@ +/* + * 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. + * + * 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 test.java.time.temporal; + +import java.time.temporal.Year; + +import org.testng.annotations.Test; +import test.java.time.AbstractTest; + +/** + * Test Year. + */ +@Test +public class TestYear extends AbstractTest { + + @Test + public void test_immutable() { + assertImmutable(Year.class); + } + +} diff --git a/test/java/time/test/java/time/temporal/TestYearMonth.java b/test/java/time/test/java/time/temporal/TestYearMonth.java new file mode 100644 index 0000000000000000000000000000000000000000..ab0d7f4c8cbd8efe7baefd725bbdc6312e9fd584 --- /dev/null +++ b/test/java/time/test/java/time/temporal/TestYearMonth.java @@ -0,0 +1,79 @@ +/* + * 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. + * + * 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 test.java.time.temporal; + +import java.time.temporal.YearMonth; + +import org.testng.annotations.Test; +import test.java.time.AbstractTest; + +/** + * Test YearMonth. + */ +@Test +public class TestYearMonth extends AbstractTest { + + //----------------------------------------------------------------------- + @Test + public void test_immutable() { + assertImmutable(YearMonth.class); + } + +} diff --git a/test/java/time/test/java/time/zone/TestFixedZoneRules.java b/test/java/time/test/java/time/zone/TestFixedZoneRules.java new file mode 100644 index 0000000000000000000000000000000000000000..80569674bb67950492062dc1256fab0026636791 --- /dev/null +++ b/test/java/time/test/java/time/zone/TestFixedZoneRules.java @@ -0,0 +1,101 @@ +/* + * 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. + * + * 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) 2010-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 test.java.time.zone; + +import java.time.zone.*; + +import static org.testng.Assert.assertEquals; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +import org.testng.annotations.Test; + +/** + * Test ZoneRules for fixed offset time-zones. + */ +@Test +public class TestFixedZoneRules { + + private static final ZoneOffset OFFSET_PONE = ZoneOffset.ofHours(1); + + private ZoneRules make(ZoneOffset offset) { + return offset.getRules(); + } + + //----------------------------------------------------------------------- + @Test(groups="implementation") + public void test_data_nullInput() { + ZoneRules test = make(OFFSET_PONE); + assertEquals(test.getOffset((Instant) null), OFFSET_PONE); + assertEquals(test.getOffset((LocalDateTime) null), OFFSET_PONE); + assertEquals(test.getValidOffsets(null).size(), 1); + assertEquals(test.getValidOffsets(null).get(0), OFFSET_PONE); + assertEquals(test.getTransition(null), null); + assertEquals(test.getStandardOffset(null), OFFSET_PONE); + assertEquals(test.getDaylightSavings(null), Duration.ZERO); + assertEquals(test.isDaylightSavings(null), false); + assertEquals(test.nextTransition(null), null); + assertEquals(test.previousTransition(null), null); + } + +} diff --git a/test/java/time/test/java/util/TestFormatter.java b/test/java/time/test/java/util/TestFormatter.java new file mode 100644 index 0000000000000000000000000000000000000000..54331ed2c9bb8db721e8822a85fd1c54c7abb20c --- /dev/null +++ b/test/java/time/test/java/util/TestFormatter.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2012, 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. + * + * 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 test.java.util; + +import java.time.Instant; +import java.time.temporal.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoField; + +import java.util.*; + +import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; + +/* @test + * @summary Unit test for j.u.Formatter threeten date/time support + */ +@Test(groups={"implementation"}) +public class TestFormatter { + + // time + private static String[] fmtStrTime = new String[] { + "H:[%tH] I:[%1$tI] k:[%1$tk] l:[%1$tl] M:[%1$tM] S:[%1$tS] L:[%1$tL] N:[%1$tN] p:[%1$tp]", + "H:[%TH] I:[%1$TI] k:[%1$Tk] l:[%1$Tl] M:[%1$TM] S:[%1$TS] L:[%1$TL] N:[%1$TN] p:[%1$Tp]", + "R:[%tR] T:[%1$tT] r:[%1$tr]", + "R:[%TR] T:[%1$TT] r:[%1$Tr]" + }; + // date + private static String[] fmtStrDate = new String[] { + "B:[%tB] b:[%1$tb] h:[%1$th] A:[%1$tA] a:[%1$ta] C:[%1$tC] Y:[%1$tY] y:[%1$ty] j:[%1$tj] m:[%1$tm] d:[%1$td] e:[%1$te]", + "B:[%TB] b:[%1$Tb] h:[%1$Th] A:[%1$TA] a:[%1$Ta] C:[%1$TC] Y:[%1$TY] y:[%1$Ty] j:[%1$Tj] m:[%1$Tm] d:[%1$Td] e:[%1$Te]", + "D:[%tD] F:[%1$tF]", + "D:[%TD] F:[%1$TF]" + }; + + private int total = 0; + private int failure = 0; + private boolean verbose = true; + + @Test + public void test () { + + int N = 12; + //locales = Locale.getAvailableLocales(); + Locale[] locales = new Locale[] { + Locale.ENGLISH, Locale.FRENCH, Locale.JAPANESE, Locale.CHINESE}; + + Random r = new Random(); + ZonedDateTime zdt = ZonedDateTime.now(); + while (N-- > 0) { + zdt = zdt.withDayOfYear(r.nextInt(365) + 1) + .with(ChronoField.SECOND_OF_DAY, r.nextInt(86400)); + Instant instant = zdt.toInstant(); + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(instant.toEpochMilli()); + + for (Locale locale : locales) { + for (String fmtStr : fmtStrDate) { + testDate(fmtStr, locale, zdt, cal); + } + for (String fmtStr : fmtStrTime) { + testTime(fmtStr, locale, zdt, cal); + } + testZoneId(locale, zdt, cal); + testInstant(locale, instant, zdt, cal); + } + } + if (verbose) { + if (failure != 0) { + System.out.println("Total " + failure + "/" + total + " tests failed"); + } else { + System.out.println("All tests (" + total + ") PASSED"); + } + } + assertEquals(failure, 0); + } + + private String getClassName(Object o) { + Class c = o.getClass(); + return c.getName().substring(c.getPackage().getName().length() + 1); + } + + private String test(String fmtStr, Locale locale, + String expected, Object dt) { + String out = new Formatter( + new StringBuilder(), locale).format(fmtStr, dt).out().toString(); + if (verbose) { + System.out.printf("%-18s : %s%n", getClassName(dt), out); + } + if (expected != null && !out.equals(expected)) { + System.out.printf("=====>%-18s : %s [ FAILED expected: %s ]%n", + getClassName(dt), out, expected); + new RuntimeException().printStackTrace(); + failure++; + } + total++; + return out; + } + + private void printFmtStr(Locale locale, String fmtStr) { + if (verbose) { + System.out.println("--------------------"); + System.out.printf("[%s, %s]%n", locale.toString(), fmtStr); + } + } + + private void testDate(String fmtStr, Locale locale, + ZonedDateTime zdt, Calendar cal) { + printFmtStr(locale, fmtStr); + String expected = test(fmtStr, locale, null, cal); + test(fmtStr, locale, expected, zdt); + test(fmtStr, locale, expected, OffsetDateTime.of(zdt)); + test(fmtStr, locale, expected, zdt.getDateTime()); + test(fmtStr, locale, expected, OffsetDateTime.of(zdt).toOffsetDate()); + test(fmtStr, locale, expected, zdt.getDate()); + } + + private void testTime(String fmtStr, Locale locale, + ZonedDateTime zdt, Calendar cal) { + printFmtStr(locale, fmtStr); + String expected = test(fmtStr, locale, null, cal); + test(fmtStr, locale, expected, zdt); + test(fmtStr, locale, expected, OffsetDateTime.of(zdt)); + test(fmtStr, locale, expected, zdt.getDateTime()); + test(fmtStr, locale, expected, OffsetDateTime.of(zdt).toOffsetTime()); + test(fmtStr, locale, expected, zdt.getTime()); + } + + private void testZoneId(Locale locale, ZonedDateTime zdt, Calendar cal) { + String fmtStr = "z:[%tz] z:[%1$Tz] Z:[%1$tZ] Z:[%1$TZ]"; + printFmtStr(locale, fmtStr); + String expected = test(fmtStr, locale, null, cal); + test(fmtStr, locale, expected, zdt); + // get a new cal with fixed tz + Calendar cal0 = Calendar.getInstance(); + cal0.setTimeInMillis(zdt.toInstant().toEpochMilli()); + cal0.setTimeZone(TimeZone.getTimeZone("GMT" + zdt.getOffset().getId())); + expected = test(fmtStr, locale, null, cal0).replaceAll("GMT", ""); + test(fmtStr, locale, expected, OffsetDateTime.of(zdt)); + test(fmtStr, locale, expected, OffsetDateTime.of(zdt).toOffsetDate()); + test(fmtStr, locale, expected, OffsetDateTime.of(zdt).toOffsetTime()); + + // datetime + zid + fmtStr = "c:[%tc] c:[%1$Tc]"; + printFmtStr(locale, fmtStr); + expected = test(fmtStr, locale, null, cal); + test(fmtStr, locale, expected, zdt); + } + + private void testInstant(Locale locale, Instant instant, + ZonedDateTime zdt, Calendar cal) { + String fmtStr = "s:[%ts] s:[%1$Ts] Q:[%1$tQ] Q:[%1$TQ]"; + printFmtStr(locale, fmtStr); + String expected = test(fmtStr, locale, null, cal); + test(fmtStr, locale, expected, instant); + test(fmtStr, locale, expected, zdt); + test(fmtStr, locale, expected, OffsetDateTime.of(zdt)); + } +}