From 4db2041517bbc64d78f95fd98e6e9caa566082a5 Mon Sep 17 00:00:00 2001 From: Stephen Colebourne Date: Mon, 8 Apr 2013 13:46:58 +0100 Subject: [PATCH] Initial set of extras files --- .../threeten/extra/AbstractSimpleAmount.java | 250 +++++ src/main/java/org/threeten/extra/AmPm.java | 355 +++++++ .../org/threeten/extra/ChronoAdjusters.java | 182 ++++ .../java/org/threeten/extra/DayOfMonth.java | 435 +++++++++ src/main/java/org/threeten/extra/Days.java | 169 ++++ src/main/java/org/threeten/extra/Hours.java | 169 ++++ src/main/java/org/threeten/extra/Minutes.java | 169 ++++ src/main/java/org/threeten/extra/Months.java | 169 ++++ .../org/threeten/extra/QuarterOfYear.java | 439 +++++++++ src/main/java/org/threeten/extra/Seconds.java | 169 ++++ .../java/org/threeten/extra/WeekendRules.java | 91 ++ src/main/java/org/threeten/extra/Weeks.java | 169 ++++ src/main/java/org/threeten/extra/Years.java | 169 ++++ .../extra/chrono/CopticChronology.java | 265 ++++++ .../org/threeten/extra/chrono/CopticDate.java | 400 ++++++++ .../org/threeten/extra/chrono/CopticEra.java | 184 ++++ .../threeten/extra/scale/SystemUTCRules.java | 279 ++++++ .../org/threeten/extra/scale/TAIInstant.java | 451 +++++++++ .../org/threeten/extra/scale/TimeSource.java | 95 ++ .../org/threeten/extra/scale/UTCInstant.java | 528 +++++++++++ .../org/threeten/extra/scale/UTCRules.java | 277 ++++++ .../org/threeten/extra/MockFieldNoValue.java | 105 +++ .../java/org/threeten/extra/TestAmPm.java | 145 +++ .../org/threeten/extra/TestDayOfMonth.java | 307 ++++++ .../java/org/threeten/extra/TestDays.java | 331 +++++++ .../java/org/threeten/extra/TestHours.java | 331 +++++++ .../java/org/threeten/extra/TestMinutes.java | 331 +++++++ .../java/org/threeten/extra/TestMonths.java | 331 +++++++ .../org/threeten/extra/TestQuarterOfYear.java | 265 ++++++ .../java/org/threeten/extra/TestSeconds.java | 331 +++++++ .../org/threeten/extra/TestWeekendRules.java | 133 +++ .../java/org/threeten/extra/TestWeeks.java | 331 +++++++ .../java/org/threeten/extra/TestYears.java | 331 +++++++ .../extra/chrono/TestCopticChrono.java | 202 ++++ .../extra/scale/MockUTCRulesAlwaysLeap.java | 71 ++ .../extra/scale/MockUTCRulesLeapOn1000.java | 71 ++ .../threeten/extra/scale/TestTAIInstant.java | 889 ++++++++++++++++++ .../threeten/extra/scale/TestUTCInstant.java | 682 ++++++++++++++ .../threeten/extra/scale/TestUTCRules.java | 529 +++++++++++ 39 files changed, 11130 insertions(+) create mode 100644 src/main/java/org/threeten/extra/AbstractSimpleAmount.java create mode 100644 src/main/java/org/threeten/extra/AmPm.java create mode 100644 src/main/java/org/threeten/extra/ChronoAdjusters.java create mode 100644 src/main/java/org/threeten/extra/DayOfMonth.java create mode 100644 src/main/java/org/threeten/extra/Days.java create mode 100644 src/main/java/org/threeten/extra/Hours.java create mode 100644 src/main/java/org/threeten/extra/Minutes.java create mode 100644 src/main/java/org/threeten/extra/Months.java create mode 100644 src/main/java/org/threeten/extra/QuarterOfYear.java create mode 100644 src/main/java/org/threeten/extra/Seconds.java create mode 100644 src/main/java/org/threeten/extra/WeekendRules.java create mode 100644 src/main/java/org/threeten/extra/Weeks.java create mode 100644 src/main/java/org/threeten/extra/Years.java create mode 100644 src/main/java/org/threeten/extra/chrono/CopticChronology.java create mode 100644 src/main/java/org/threeten/extra/chrono/CopticDate.java create mode 100644 src/main/java/org/threeten/extra/chrono/CopticEra.java create mode 100644 src/main/java/org/threeten/extra/scale/SystemUTCRules.java create mode 100644 src/main/java/org/threeten/extra/scale/TAIInstant.java create mode 100644 src/main/java/org/threeten/extra/scale/TimeSource.java create mode 100644 src/main/java/org/threeten/extra/scale/UTCInstant.java create mode 100644 src/main/java/org/threeten/extra/scale/UTCRules.java create mode 100644 src/test/java/org/threeten/extra/MockFieldNoValue.java create mode 100644 src/test/java/org/threeten/extra/TestAmPm.java create mode 100644 src/test/java/org/threeten/extra/TestDayOfMonth.java create mode 100644 src/test/java/org/threeten/extra/TestDays.java create mode 100644 src/test/java/org/threeten/extra/TestHours.java create mode 100644 src/test/java/org/threeten/extra/TestMinutes.java create mode 100644 src/test/java/org/threeten/extra/TestMonths.java create mode 100644 src/test/java/org/threeten/extra/TestQuarterOfYear.java create mode 100644 src/test/java/org/threeten/extra/TestSeconds.java create mode 100644 src/test/java/org/threeten/extra/TestWeekendRules.java create mode 100644 src/test/java/org/threeten/extra/TestWeeks.java create mode 100644 src/test/java/org/threeten/extra/TestYears.java create mode 100644 src/test/java/org/threeten/extra/chrono/TestCopticChrono.java create mode 100644 src/test/java/org/threeten/extra/scale/MockUTCRulesAlwaysLeap.java create mode 100644 src/test/java/org/threeten/extra/scale/MockUTCRulesLeapOn1000.java create mode 100644 src/test/java/org/threeten/extra/scale/TestTAIInstant.java create mode 100644 src/test/java/org/threeten/extra/scale/TestUTCInstant.java create mode 100644 src/test/java/org/threeten/extra/scale/TestUTCRules.java diff --git a/src/main/java/org/threeten/extra/AbstractSimpleAmount.java b/src/main/java/org/threeten/extra/AbstractSimpleAmount.java new file mode 100644 index 0000000..866c174 --- /dev/null +++ b/src/main/java/org/threeten/extra/AbstractSimpleAmount.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import org.threeten.bp.jdk8.Jdk8Methods; +import org.threeten.bp.temporal.TemporalUnit; + +/** + * An abstract amount of time measured in terms of a single field, + * such as days or seconds. + *

+ * This class exists to share code between the public implementations. + * + *

Specification for implementors

+ * This is an abstract class and must be implemented with care to ensure + * other classes in the framework operate correctly. + * All instantiable subclasses must be final, immutable and thread-safe. + * + * @param the subclass type + */ +abstract class AbstractSimpleAmount> implements Comparable { + // amount stored in subclass for serialization reasons + + /** + * Constructs a new instance. + */ + AbstractSimpleAmount() { + super(); + } + + //----------------------------------------------------------------------- + /** + * Gets the amount of time. + * + * @return the amount of time, may be negative + */ + public abstract int getAmount(); + + /** + * Returns a new instance of the subclass with a different amount of time. + * + * @param amount the new amount of time, may be negative + * @return a new amount, not null + */ + public abstract T withAmount(int amount); + + //----------------------------------------------------------------------- + /** + * Gets the unit defining the amount of time. + * + * @return the unit, not null + */ + public abstract TemporalUnit getUnit(); + + //----------------------------------------------------------------------- + /** + * Returns a new instance with the specified amount of time added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to add, may be negative + * @return the new amount plus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public T plus(int amount) { + if (amount == 0) { + @SuppressWarnings("unchecked") + T result = (T) this; + return result; + } + return withAmount(Jdk8Methods.safeAdd(getAmount(), amount)); + } + + //----------------------------------------------------------------------- + /** + * Returns a new instance with the specified amount of time subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to take away, may be negative + * @return the new amount minus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public T minus(int amount) { + return (amount == Integer.MIN_VALUE ? plus(Integer.MAX_VALUE).plus(1) : plus(-amount)); + } + + //----------------------------------------------------------------------- + /** + * Returns a new instance with the amount multiplied by the specified scalar. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param scalar the amount to multiply by, may be negative + * @return the new amount multiplied by the specified scalar, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public T multipliedBy(int scalar) { + return withAmount(Jdk8Methods.safeMultiply(getAmount(), scalar)); + } + + /** + * Returns a new instance with the amount divided by the specified divisor. + * The calculation uses integer division, thus 3 divided by 2 is 1. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param divisor the amount to divide by, may be negative + * @return the new amount divided by the specified divisor, not null + * @throws ArithmeticException if the divisor is zero + */ + public T dividedBy(int divisor) { + if (divisor == 1) { + @SuppressWarnings("unchecked") + T result = (T) this; + return result; + } + return withAmount(getAmount() / divisor); + } + + //----------------------------------------------------------------------- + /** + * Returns a new instance with the amount negated. + * + * @return the new amount with a negated amount, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public T negated() { + return withAmount(safeNegate(getAmount())); + } + + /** + * Negates the input value, throwing an exception if an overflow occurs. + * + * @param value the value to negate + * @return the negated value + * @throws ArithmeticException if the value is MIN_VALUE and cannot be negated + */ + private static int safeNegate(int value) { + if (value == Integer.MIN_VALUE) { + throw new ArithmeticException("Integer.MIN_VALUE cannot be negated"); + } + return -value; + } + + //----------------------------------------------------------------------- + /** + * Compares the amount of time in this instance to another instance. + * + * @param other the other amount, not null + * @return the comparator value, negative if less, positive if greater + * @throws NullPointerException if the other amount is null + */ + @Override + public int compareTo(T other) { + int thisValue = this.getAmount(); + int otherValue = other.getAmount(); + return (thisValue < otherValue ? -1 : (thisValue == otherValue ? 0 : 1)); + } + + /** + * Checks if the amount of time in this instance greater than that in another instance. + * + * @param other the other amount, not null + * @return true if this amount is greater + * @throws NullPointerException if the other amount is null + */ + public boolean isGreaterThan(T other) { + return compareTo(other) > 0; + } + + /** + * Checks if the amount of time in this instance less than that in another instance. + * + * @param other the other amount, not null + * @return true if this amount is less + * @throws NullPointerException if the other amount is null + */ + public boolean isLessThan(T other) { + return compareTo(other) < 0; + } + + //----------------------------------------------------------------------- + /** + * Is this instance equal to that specified. + * + * @param obj the other amount of time, null returns false + * @return true if this amount of time is the same as that specified + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof AbstractSimpleAmount) { + AbstractSimpleAmount other = (AbstractSimpleAmount) obj; + return getAmount() == other.getAmount() && getUnit().equals(other.getUnit()); + } + return false; + } + + /** + * Returns the hash code for this amount. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return getUnit().hashCode() ^ getAmount(); + } + + //----------------------------------------------------------------------- + /** + * Returns a string representation of the amount of time. + * + * @return the amount of time in ISO8601 string format + */ + @Override + public abstract String toString(); + +} diff --git a/src/main/java/org/threeten/extra/AmPm.java b/src/main/java/org/threeten/extra/AmPm.java new file mode 100644 index 0000000..e7789b7 --- /dev/null +++ b/src/main/java/org/threeten/extra/AmPm.java @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import static org.threeten.bp.temporal.ChronoField.AMPM_OF_DAY; +import static org.threeten.bp.temporal.ChronoField.HOUR_OF_DAY; +import static org.threeten.bp.temporal.ChronoUnit.HALF_DAYS; + +import java.util.Calendar; +import java.util.Locale; + +import org.threeten.bp.DateTimeException; +import org.threeten.bp.format.DateTimeFormatterBuilder; +import org.threeten.bp.format.TextStyle; +import org.threeten.bp.temporal.ChronoField; +import org.threeten.bp.temporal.Temporal; +import org.threeten.bp.temporal.TemporalAccessor; +import org.threeten.bp.temporal.TemporalAdjuster; +import org.threeten.bp.temporal.TemporalField; +import org.threeten.bp.temporal.TemporalQueries; +import org.threeten.bp.temporal.TemporalQuery; +import org.threeten.bp.temporal.ValueRange; + +/** + * A half-day before or after midday, with the values 'AM' and 'PM'. + *

+ * {@code AmPm} is an enum representing the half-day concepts of AM and PM. + * AM is defined as from 00:00 to 11:59, while PM is defined from 12:00 to 23:59. + *

+ * All date-time fields have an {@code int} value. + * The {@code int} value follows {@link Calendar}, assigning 0 to AM and 1 to PM. + * 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 AmPm}. + * 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 AM/PM + * concept defined exactly equivalent to the ISO calendar system. + * + *

Specification for implementors

+ * This is an immutable and thread-safe enum. + */ +public enum AmPm implements TemporalAccessor, TemporalAdjuster { + + /** + * The singleton instance for the morning, AM - ante meridiem. + * This has the numeric value of {@code 0}. + */ + AM, + /** + * The singleton instance for the afternoon, PM - post meridiem. + * This has the numeric value of {@code 1}. + */ + PM; + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code AmPm} from an {@code int} value. + *

+ * {@code AmPm} is an enum representing before and after midday. + * This factory allows the enum to be obtained from the {@code int} value. + * The {@code int} value follows {@link Calendar}, assigning 0 to AM and 1 to PM. + * + * @param amPmValue the AM/PM value to represent, from 0 (AM) to 1 (PM) + * @return the AM/PM, not null + * @throws DateTimeException if the value is invalid + */ + public static AmPm of(int amPmValue) { + switch (amPmValue) { + case 0: return AM; + case 1: return PM; + default: throw new DateTimeException("Invalid value for AM/PM: " + amPmValue); + } + } + + /** + * Obtains an instance of {@code AmPm} from an hour-of-day. + *

+ * {@code AmPm} is an enum representing before and after midday. + * This factory allows the enum to be obtained from the hour-of-day value, from 0 to 23. + * + * @param hourOfDay the hour-of-day to extract from, from 0 to 23 + * @return the AM/PM, not null + * @throws DateTimeException if the hour-of-day is invalid + */ + public static AmPm ofHour(int hourOfDay) { + HOUR_OF_DAY.checkValidValue(hourOfDay); + return hourOfDay < 12 ? AM : PM; + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code AmPm} 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#AMPM_OF_DAY AMPM_OF_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 AmPm::from}. + * + * @param temporal the temporal object to convert, not null + * @return the AM/PM, not null + * @throws DateTimeException if unable to convert to a {@code AmPm} + */ + public static AmPm from(TemporalAccessor temporal) { + if (temporal instanceof AmPm) { + return (AmPm) temporal; + } + return of(temporal.get(AMPM_OF_DAY)); + } + + //----------------------------------------------------------------------- + /** + * Gets the AM/PM {@code int} value. + *

+ * The values are numbered following {@link Calendar}, assigning 0 to AM and 1 to PM. + * + * @return the AM/PM value, from 0 (AM) to 1 (PM) + */ + public int getValue() { + return ordinal(); + } + + //----------------------------------------------------------------------- + /** + * Gets the textual representation, such as 'AM' or 'PM'. + *

+ * This returns the textual name used to identify the AM/PM. + * 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 AM/PM, not null + */ + public String getDisplayName(TextStyle style, Locale locale) { + return new DateTimeFormatterBuilder().appendText(AMPM_OF_DAY, style).toFormatter(locale).format(this); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this AM/PM 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#AMPM_OF_DAY AMPM_OF_DAY} 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.isSupportedBy(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 AM/PM, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == AMPM_OF_DAY; + } + return field != null && field.isSupportedBy(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 AM/PM 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#AMPM_OF_DAY AMPM_OF_DAY} then the + * range of the AM/PM, from 0 to 1, 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.rangeRefinedBy(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 == AMPM_OF_DAY) { + return field.range(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.rangeRefinedBy(this); + } + + /** + * Gets the value of the specified field from this AM/PM as an {@code int}. + *

+ * This queries this AM/PM 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#AMPM_OF_DAY AMPM_OF_DAY} then the + * value of the AM/PM, from 0 to 1, 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.getFrom(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 == AMPM_OF_DAY) { + return getValue(); + } + return range(field).checkValidIntValue(getLong(field), field); + } + + /** + * Gets the value of the specified field from this AM/PM as a {@code long}. + *

+ * This queries this AM/PM 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#AMPM_OF_DAY AMPM_OF_DAY} then the + * value of the AM/PM, from 0 to 1, 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.getFrom(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 == AMPM_OF_DAY) { + return getValue(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.getFrom(this); + } + + //----------------------------------------------------------------------- + /** + * Queries this AM/PM using the specified query. + *

+ * This queries this AM/PM 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 == TemporalQueries.precision()) { + return (R) HALF_DAYS; + } else if (query == TemporalQueries.zoneId() || query == TemporalQueries.chronology()) { + return null; + } + return query.queryFrom(this); + } + + /** + * Adjusts the specified temporal object to have this AM/PM. + *

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

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * passing {@link ChronoField#AMPM_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 = thisDayOfWeek.adjustInto(temporal);
+     *   temporal = temporal.with(thisDayOfWeek);
+     * 
+ *

+ * 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(AMPM_OF_DAY, getValue()); + } + +} diff --git a/src/main/java/org/threeten/extra/ChronoAdjusters.java b/src/main/java/org/threeten/extra/ChronoAdjusters.java new file mode 100644 index 0000000..0bb4759 --- /dev/null +++ b/src/main/java/org/threeten/extra/ChronoAdjusters.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import org.threeten.bp.chrono.ChronoLocalDate; +import org.threeten.bp.chrono.HijrahChronology; +import org.threeten.bp.chrono.JapaneseChronology; +import org.threeten.bp.chrono.MinguoChronology; +import org.threeten.bp.chrono.ThaiBuddhistChronology; +import org.threeten.bp.temporal.Temporal; +import org.threeten.bp.temporal.TemporalAdjuster; + +/** + * Adjusters that allow dates to be adjusted in terms of a calendar system. + */ +public final class ChronoAdjusters { + + /** + * Restricted constructor. + */ + private ChronoAdjusters() { + } + + //----------------------------------------------------------------------- + /** + * Convenience wrapper allowing a date to be easily adjusted in the Minguo calendar system. + *

+ * This allows the specified adjuster to be run in terms of a Minguo date. + * This would be used as follows: + *

+     *  date = date.with(minguo(temporal -> temporal.plus(1, MONTHS)));
+     * 
+ * or in JDK 1.7: + *
+     *  date = date.with(minguo(new TemporalAdjuster() {
+     *    @Override
+     *    public Temporal adjustInto(Temporal temporal) {
+     *        return temporal.plus(1, MONTHS);
+     *    }
+     *  }));
+     * 
+ * + * @param adjuster the adjuster to run in the Minguo calendar system + * @return the adjuster, not null + */ + public static TemporalAdjuster minguo(final TemporalAdjuster adjuster) { + return new TemporalAdjuster() { + @Override + public Temporal adjustInto(Temporal temporal) { + ChronoLocalDate baseDate = MinguoChronology.INSTANCE.date(temporal); + ChronoLocalDate adjustedDate = (ChronoLocalDate) adjuster.adjustInto(baseDate); + return temporal.with(adjustedDate); + } + }; + } + + /** + * Convenience wrapper allowing a date to be easily adjusted in the Hijrah calendar system. + *

+ * This allows the specified adjuster to be run in terms of a Hijrah date. + * This would be used as follows: + *

+     *  date = date.with(hijrah(temporal -> temporal.plus(1, MONTHS)));
+     * 
+ * or in JDK 1.7: + *
+     *  date = date.with(hijrah(new TemporalAdjuster() {
+     *    @Override
+     *    public Temporal adjustInto(Temporal temporal) {
+     *        return temporal.plus(1, MONTHS);
+     *    }
+     *  }));
+     * 
+ * + * @param adjuster the adjuster to run in the Hijrah calendar system + * @return the adjuster, not null + */ + public static TemporalAdjuster hijrah(final TemporalAdjuster adjuster) { + return new TemporalAdjuster() { + @Override + public Temporal adjustInto(Temporal temporal) { + ChronoLocalDate baseDate = HijrahChronology.INSTANCE.date(temporal); + ChronoLocalDate adjustedDate = (ChronoLocalDate) adjuster.adjustInto(baseDate); + return temporal.with(adjustedDate); + } + }; + } + + /** + * Convenience wrapper allowing a date to be easily adjusted in the Japanese calendar system. + *

+ * This allows the specified adjuster to be run in terms of a Japanese date. + * This would be used as follows: + *

+     *  date = date.with(japanese(temporal -> temporal.plus(1, MONTHS)));
+     * 
+ * or in JDK 1.7: + *
+     *  date = date.with(japanese(new TemporalAdjuster() {
+     *    @Override
+     *    public Temporal adjustInto(Temporal temporal) {
+     *        return temporal.plus(1, MONTHS);
+     *    }
+     *  }));
+     * 
+ * + * @param adjuster the adjuster to run in the Japanese calendar system + * @return the adjuster, not null + */ + public static TemporalAdjuster japanese(final TemporalAdjuster adjuster) { + return new TemporalAdjuster() { + @Override + public Temporal adjustInto(Temporal temporal) { + ChronoLocalDate baseDate = JapaneseChronology.INSTANCE.date(temporal); + ChronoLocalDate adjustedDate = (ChronoLocalDate) adjuster.adjustInto(baseDate); + return temporal.with(adjustedDate); + } + }; + } + + /** + * Convenience wrapper allowing a date to be easily adjusted in the ThaiBuddhist calendar system. + *

+ * This allows the specified adjuster to be run in terms of a ThaiBuddhist date. + * This would be used as follows: + *

+     *  date = date.with(thaiBuddhist(temporal -> temporal.plus(1, MONTHS)));
+     * 
+ * or in JDK 1.7: + *
+     *  date = date.with(thaiBuddhist(new TemporalAdjuster() {
+     *    @Override
+     *    public Temporal adjustInto(Temporal temporal) {
+     *        return temporal.plus(1, MONTHS);
+     *    }
+     *  }));
+     * 
+ * + * @param adjuster the adjuster to run in the ThaiBuddhist calendar system + * @return the adjuster, not null + */ + public static TemporalAdjuster thaiBuddhist(final TemporalAdjuster adjuster) { + return new TemporalAdjuster() { + @Override + public Temporal adjustInto(Temporal temporal) { + ChronoLocalDate baseDate = ThaiBuddhistChronology.INSTANCE.date(temporal); + ChronoLocalDate adjustedDate = (ChronoLocalDate) adjuster.adjustInto(baseDate); + return temporal.with(adjustedDate); + } + }; + } + +} diff --git a/src/main/java/org/threeten/extra/DayOfMonth.java b/src/main/java/org/threeten/extra/DayOfMonth.java new file mode 100644 index 0000000..270df39 --- /dev/null +++ b/src/main/java/org/threeten/extra/DayOfMonth.java @@ -0,0 +1,435 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import static org.threeten.bp.temporal.ChronoField.DAY_OF_MONTH; +import static org.threeten.bp.temporal.ChronoUnit.DAYS; + +import java.io.Serializable; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import org.threeten.bp.DateTimeException; +import org.threeten.bp.LocalDate; +import org.threeten.bp.Month; +import org.threeten.bp.MonthDay; +import org.threeten.bp.YearMonth; +import org.threeten.bp.temporal.ChronoField; +import org.threeten.bp.temporal.Temporal; +import org.threeten.bp.temporal.TemporalAccessor; +import org.threeten.bp.temporal.TemporalAdjuster; +import org.threeten.bp.temporal.TemporalField; +import org.threeten.bp.temporal.TemporalQueries; +import org.threeten.bp.temporal.TemporalQuery; +import org.threeten.bp.temporal.ValueRange; + +/** + * A representation of a day-of-month in the ISO-8601 calendar system. + *

+ * {@code DayOfMonth} allows the day-of-month to be represented in a type-safe way. + * The value can range from 1 to 31, as there is no month or year to validate against. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + */ +public final class DayOfMonth + implements Comparable, TemporalAccessor, TemporalAdjuster, Serializable { + + /** + * A serialization identifier for this instance. + */ + private static final long serialVersionUID = -8840172642009917873L; + /** + * Cache of singleton instances. + */ + private static final AtomicReferenceArray CACHE = new AtomicReferenceArray(31); + + /** + * The day-of-month being represented, from 1 to 31. + */ + private final int dayOfMonth; + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code DayOfMonth}. + *

+ * A day-of-month object represents one of the 31 days of the month, from 1 to 31. + * + * @param dayOfMonth the day-of-month to represent, from 1 to 31 + * @return the day-of-month, not null + * @throws DateTimeException if the day-of-month is invalid + */ + public static DayOfMonth of(int dayOfMonth) { + try { + DayOfMonth result = CACHE.get(--dayOfMonth); + if (result == null) { + DayOfMonth temp = new DayOfMonth(dayOfMonth + 1); + CACHE.compareAndSet(dayOfMonth, null, temp); + result = CACHE.get(dayOfMonth); + } + return result; + } catch (IndexOutOfBoundsException ex) { + throw new DateTimeException("Invalid value for DayOfMonth: " + ++dayOfMonth); + } + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code DayOfMonth} 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_MONTH DAY_OF_MONTH} field. + *

+ * This method matches the signature of the functional interface {@link TemporalQuery} + * allowing it to be used as a query via method reference, {@code DayOfMonth::from}. + * + * @param temporal the temporal object to convert, not null + * @return the day-of-month, not null + * @throws DateTimeException if unable to convert to a {@code DayOfMonth} + */ + public static DayOfMonth from(TemporalAccessor temporal) { + if (temporal instanceof DayOfMonth) { + return (DayOfMonth) temporal; + } + return of(temporal.get(DAY_OF_MONTH)); + } + + //----------------------------------------------------------------------- + /** + * Constructs an instance with the specified day-of-month. + * + * @param dayOfMonth the day-of-month to represent + */ + private DayOfMonth(int dayOfMonth) { + this.dayOfMonth = dayOfMonth; + } + + /** + * Resolve the singleton. + * + * @return the singleton, not null + */ + private Object readResolve() { + return of(dayOfMonth); + } + + //----------------------------------------------------------------------- + /** + * Gets the field that defines how the day-of-month field operates. + *

+ * The field provides access to the minimum and maximum values, and a + * generic way to access values within a date-time. + * + * @return the day-of-month field, not null + */ + public TemporalField getField() { + return DAY_OF_MONTH; + } + + /** + * Gets the day-of-month value. + * + * @return the day-of-month, from 1 to 31 + */ + public int getValue() { + return dayOfMonth; + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this day-of-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 {@link ChronoField#DAY_OF_MONTH DAY_OF_MONTH} 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.isSupportedBy(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-month, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == DAY_OF_MONTH; + } + return field != null && field.isSupportedBy(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-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#DAY_OF_MONTH DAY_OF_MONTH} then the + * range of the day-of-month, from 1 to 31, 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.rangeRefinedBy(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_MONTH) { + return field.range(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.rangeRefinedBy(this); + } + + /** + * Gets the value of the specified field from this day-of-month as an {@code int}. + *

+ * This queries this day-of-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#DAY_OF_MONTH DAY_OF_MONTH} then the + * value of the day-of-month, from 1 to 31, 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.getFrom(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_MONTH) { + return getValue(); + } + return range(field).checkValidIntValue(getLong(field), field); + } + + /** + * Gets the value of the specified field from this day-of-month as a {@code long}. + *

+ * This queries this day-of-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#DAY_OF_MONTH DAY_OF_MONTH} then the + * value of the day-of-month, from 1 to 31, 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.getFrom(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_MONTH) { + return getValue(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.getFrom(this); + } + + //----------------------------------------------------------------------- + /** + * Queries this day-of-month using the specified query. + *

+ * This queries this day-of-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 == TemporalQueries.precision()) { + return (R) DAYS; + } else if (query == TemporalQueries.zoneId() || query == TemporalQueries.chronology()) { + return null; + } + return query.queryFrom(this); + } + + /** + * Adjusts the specified temporal object to have this day-of-month. + *

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

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * passing {@link ChronoField#DAY_OF_MONTH} 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 = thisDayOfWeek.adjustInto(temporal);
+     *   temporal = temporal.with(thisDayOfWeek);
+     * 
+ *

+ * 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_MONTH, getValue()); + } + + //----------------------------------------------------------------------- + /** + * Combines this day with a month to create a {@code MonthDay}. + *

+ * This returns a {@code MonthDay} formed from this day and the specified month. + * + * @param month the month to use, from 1 to 12 + * @return the date formed from this day and the specified month, not null + * @throws DateTimeException if the month is invalid + */ + public MonthDay atMonth(int month) { + return MonthDay.of(month, dayOfMonth); + } + + /** + * Combines this day with a month to create a {@code MonthDay}. + *

+ * This returns a {@code MonthDay} formed from this day and the specified month. + * + * @param month the month to use, not null + * @return the date formed from this day and the specified month, not null + */ + public MonthDay atMonth(Month month) { + return MonthDay.of(month, dayOfMonth); + } + + /** + * Combines this day with a year-month to create a {@code LocalDate}. + *

+ * This returns a {@code LocalDate} formed from this day-of-month and the specified year-month. + *

+ * If the day-of-month value if not valid for the year-month, it is adjusted + * to the last valid day-of-month. + * + * @param yearMonth the year-month to use, not null + * @return the date formed from this day and the specified year-month, not null + */ + public LocalDate atYearMonth(YearMonth yearMonth) { + int day = Math.min(dayOfMonth, yearMonth.lengthOfMonth()); + return yearMonth.atDay(day); + } + + //----------------------------------------------------------------------- + /** + * Compares this day-of-month instance to another. + * + * @param otherDayOfMonth the other day-of-month instance, not null + * @return the comparator value, negative if less, positive if greater + */ + @Override + public int compareTo(DayOfMonth otherDayOfMonth) { + return Integer.compare(dayOfMonth, otherDayOfMonth.dayOfMonth); + } + + //----------------------------------------------------------------------- + /** + * Is this instance equal to that specified, evaluating the day-of-month. + * + * @param otherDayOfMonth the other day-of-month instance, null returns false + * @return true if the day-of-month is the same + */ + @Override + public boolean equals(Object otherDayOfMonth) { + return this == otherDayOfMonth; + } + + /** + * A hash code for the day-of-month object. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + return dayOfMonth; + } + + /** + * A string describing the day-of-month object. + * + * @return a string describing this object + */ + @Override + public String toString() { + return "DayOfMonth=" + getValue(); + } + +} diff --git a/src/main/java/org/threeten/extra/Days.java b/src/main/java/org/threeten/extra/Days.java new file mode 100644 index 0000000..c1e9236 --- /dev/null +++ b/src/main/java/org/threeten/extra/Days.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import static org.threeten.bp.temporal.ChronoUnit.DAYS; + +import java.io.Serializable; + +import org.threeten.bp.temporal.TemporalUnit; + +/** + * An amount of time measured in days, such as '6 Days'. + *

+ * This class stores an amount of time in terms of the days unit of time. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + */ +public final class Days extends AbstractSimpleAmount implements Serializable { + + /** + * A constant for zero days. + */ + public static final Days ZERO = new Days(0); + /** + * A serialization identifier for this class. + */ + private static final long serialVersionUID = -8903767091325669093L; + + /** + * The number of days. + */ + private final int days; + + /** + * Obtains an instance of {@code Days}. + * + * @param days the number of days the instance will represent, may be negative + * @return the {@code Days} instance, not null + */ + public static Days of(int days) { + if (days == 0) { + return ZERO; + } + return new Days(days); + } + + //----------------------------------------------------------------------- + /** + * Constructs an instance using a specific number of days. + * + * @param days the days to use + */ + private Days(int days) { + super(); + this.days = days; + } + + /** + * Resolves singletons. + * + * @return the singleton instance + */ + private Object readResolve() { + return Days.of(days); + } + + //----------------------------------------------------------------------- + /** + * Gets the number of days in this amount. + * + * @return the number of days, may be negative + */ + @Override + public int getAmount() { + return days; + } + + /** + * Returns a new instance of the subclass with a different number of days. + * + * @param amount the number of days to set in the new instance, may be negative + * @return a new period element, not null + */ + @Override + public Days withAmount(int amount) { + return Days.of(amount); + } + + //----------------------------------------------------------------------- + /** + * Gets the unit defining the amount of time. + * + * @return the days unit, not null + */ + @Override + public TemporalUnit getUnit() { + return DAYS; + } + + //----------------------------------------------------------------------- + /** + * Returns a new instance with the specified amount of time added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to add, may be negative + * @return the new amount plus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public Days plus(Days amount) { + return plus(amount.getAmount()); + } + + /** + * Returns a new instance with the specified amount of time subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to add, may be negative + * @return the new amount minus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public Days minus(Days amount) { + return minus(amount.getAmount()); + } + + //----------------------------------------------------------------------- + /** + * Returns a string representation of the number of days. + * This will be in the format 'PnD' where n is the number of days. + * + * @return the number of days in ISO8601 string format + */ + @Override + public String toString() { + return "P" + days + "D"; + } + +} diff --git a/src/main/java/org/threeten/extra/Hours.java b/src/main/java/org/threeten/extra/Hours.java new file mode 100644 index 0000000..346d55f --- /dev/null +++ b/src/main/java/org/threeten/extra/Hours.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import static org.threeten.bp.temporal.ChronoUnit.HOURS; + +import java.io.Serializable; + +import org.threeten.bp.temporal.TemporalUnit; + +/** + * An amount of time measured in hours, such as '6 Hours'. + *

+ * This class stores an amount of time in terms of the hours unit of time. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + */ +public final class Hours extends AbstractSimpleAmount implements Serializable { + + /** + * A constant for zero hours. + */ + public static final Hours ZERO = new Hours(0); + /** + * A serialization identifier for this class. + */ + private static final long serialVersionUID = -8903767091325669093L; + + /** + * The number of hours. + */ + private final int hours; + + /** + * Obtains an instance of {@code Hours}. + * + * @param hours the number of hours the instance will represent, may be negative + * @return the {@code Hours} instance, not null + */ + public static Hours of(int hours) { + if (hours == 0) { + return ZERO; + } + return new Hours(hours); + } + + //----------------------------------------------------------------------- + /** + * Constructs an instance using a specific number of hours. + * + * @param hours the hours to use + */ + private Hours(int hours) { + super(); + this.hours = hours; + } + + /** + * Resolves singletons. + * + * @return the singleton instance + */ + private Object readResolve() { + return Hours.of(hours); + } + + //----------------------------------------------------------------------- + /** + * Gets the number of hours in this amount. + * + * @return the number of hours, may be negative + */ + @Override + public int getAmount() { + return hours; + } + + /** + * Returns a new instance of the subclass with a different number of hours. + * + * @param amount the number of hours to set in the new instance, may be negative + * @return a new period element, not null + */ + @Override + public Hours withAmount(int amount) { + return Hours.of(amount); + } + + //----------------------------------------------------------------------- + /** + * Gets the unit defining the amount of time. + * + * @return the hours unit, not null + */ + @Override + public TemporalUnit getUnit() { + return HOURS; + } + + //----------------------------------------------------------------------- + /** + * Returns a new instance with the specified amount of time added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to add, may be negative + * @return the new amount plus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public Hours plus(Hours amount) { + return plus(amount.getAmount()); + } + + /** + * Returns a new instance with the specified amount of time subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to add, may be negative + * @return the new amount minus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public Hours minus(Hours amount) { + return minus(amount.getAmount()); + } + + //----------------------------------------------------------------------- + /** + * Returns a string representation of the number of hours. + * This will be in the format 'PTnH' where n is the number of hours. + * + * @return the number of hours in ISO8601 string format + */ + @Override + public String toString() { + return "PT" + hours + "H"; + } + +} diff --git a/src/main/java/org/threeten/extra/Minutes.java b/src/main/java/org/threeten/extra/Minutes.java new file mode 100644 index 0000000..77683c8 --- /dev/null +++ b/src/main/java/org/threeten/extra/Minutes.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import static org.threeten.bp.temporal.ChronoUnit.MINUTES; + +import java.io.Serializable; + +import org.threeten.bp.temporal.TemporalUnit; + +/** + * An amount of time measured in minutes, such as '6 Minutes'. + *

+ * This class stores an amount of time in terms of the minutes unit of time. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + */ +public final class Minutes extends AbstractSimpleAmount implements Serializable { + + /** + * A constant for zero minutes. + */ + public static final Minutes ZERO = new Minutes(0); + /** + * A serialization identifier for this class. + */ + private static final long serialVersionUID = -8903767091325669093L; + + /** + * The number of minutes. + */ + private final int minutes; + + /** + * Obtains an instance of {@code Minutes}. + * + * @param minutes the number of minutes the instance will represent, may be negative + * @return the {@code Minutes} instance, not null + */ + public static Minutes of(int minutes) { + if (minutes == 0) { + return ZERO; + } + return new Minutes(minutes); + } + + //----------------------------------------------------------------------- + /** + * Constructs an instance using a specific number of minutes. + * + * @param minutes the minutes to use + */ + private Minutes(int minutes) { + super(); + this.minutes = minutes; + } + + /** + * Resolves singletons. + * + * @return the singleton instance + */ + private Object readResolve() { + return Minutes.of(minutes); + } + + //----------------------------------------------------------------------- + /** + * Gets the number of minutes in this amount. + * + * @return the number of minutes, may be negative + */ + @Override + public int getAmount() { + return minutes; + } + + /** + * Returns a new instance of the subclass with a different number of minutes. + * + * @param amount the number of minutes to set in the new instance, may be negative + * @return a new period element, not null + */ + @Override + public Minutes withAmount(int amount) { + return Minutes.of(amount); + } + + //----------------------------------------------------------------------- + /** + * Gets the unit defining the amount of time. + * + * @return the minutes unit, not null + */ + @Override + public TemporalUnit getUnit() { + return MINUTES; + } + + //----------------------------------------------------------------------- + /** + * Returns a new instance with the specified amount of time added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to add, may be negative + * @return the new amount plus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public Minutes plus(Minutes amount) { + return plus(amount.getAmount()); + } + + /** + * Returns a new instance with the specified amount of time subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to add, may be negative + * @return the new amount minus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public Minutes minus(Minutes amount) { + return minus(amount.getAmount()); + } + + //----------------------------------------------------------------------- + /** + * Returns a string representation of the number of minutes. + * This will be in the format 'PTnM' where n is the number of minutes. + * + * @return the number of minutes in ISO8601 string format + */ + @Override + public String toString() { + return "PT" + minutes + "M"; + } + +} diff --git a/src/main/java/org/threeten/extra/Months.java b/src/main/java/org/threeten/extra/Months.java new file mode 100644 index 0000000..7ff1564 --- /dev/null +++ b/src/main/java/org/threeten/extra/Months.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import static org.threeten.bp.temporal.ChronoUnit.MONTHS; + +import java.io.Serializable; + +import org.threeten.bp.temporal.TemporalUnit; + +/** + * An amount of time measured in months, such as '6 Months'. + *

+ * This class stores an amount of time in terms of the months unit of time. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + */ +public final class Months extends AbstractSimpleAmount implements Serializable { + + /** + * A constant for zero months. + */ + public static final Months ZERO = new Months(0); + /** + * A serialization identifier for this class. + */ + private static final long serialVersionUID = -8903767091325669093L; + + /** + * The number of months. + */ + private final int months; + + /** + * Obtains an instance of {@code Months}. + * + * @param months the number of months the instance will represent, may be negative + * @return the {@code Months} instance, not null + */ + public static Months of(int months) { + if (months == 0) { + return ZERO; + } + return new Months(months); + } + + //----------------------------------------------------------------------- + /** + * Constructs an instance using a specific number of months. + * + * @param months the months to use + */ + private Months(int months) { + super(); + this.months = months; + } + + /** + * Resolves singletons. + * + * @return the singleton instance + */ + private Object readResolve() { + return Months.of(months); + } + + //----------------------------------------------------------------------- + /** + * Gets the number of months in this amount. + * + * @return the number of months, may be negative + */ + @Override + public int getAmount() { + return months; + } + + /** + * Returns a new instance of the subclass with a different number of months. + * + * @param amount the number of months to set in the new instance, may be negative + * @return a new period element, not null + */ + @Override + public Months withAmount(int amount) { + return Months.of(amount); + } + + //----------------------------------------------------------------------- + /** + * Gets the unit defining the amount of time. + * + * @return the months unit, not null + */ + @Override + public TemporalUnit getUnit() { + return MONTHS; + } + + //----------------------------------------------------------------------- + /** + * Returns a new instance with the specified amount of time added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to add, may be negative + * @return the new amount plus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public Months plus(Months amount) { + return plus(amount.getAmount()); + } + + /** + * Returns a new instance with the specified amount of time subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to add, may be negative + * @return the new amount minus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public Months minus(Months amount) { + return minus(amount.getAmount()); + } + + //----------------------------------------------------------------------- + /** + * Returns a string representation of the number of months. + * This will be in the format 'PnM' where n is the number of months. + * + * @return the number of months in ISO8601 string format + */ + @Override + public String toString() { + return "P" + months + "M"; + } + +} diff --git a/src/main/java/org/threeten/extra/QuarterOfYear.java b/src/main/java/org/threeten/extra/QuarterOfYear.java new file mode 100644 index 0000000..0a2a937 --- /dev/null +++ b/src/main/java/org/threeten/extra/QuarterOfYear.java @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import static org.threeten.bp.temporal.IsoFields.QUARTER_OF_YEAR; +import static org.threeten.bp.temporal.IsoFields.QUARTER_YEARS; + +import java.util.Locale; +import java.util.Objects; + +import org.threeten.bp.DateTimeException; +import org.threeten.bp.Month; +import org.threeten.bp.chrono.Chronology; +import org.threeten.bp.chrono.IsoChronology; +import org.threeten.bp.format.DateTimeFormatterBuilder; +import org.threeten.bp.format.TextStyle; +import org.threeten.bp.temporal.ChronoField; +import org.threeten.bp.temporal.IsoFields; +import org.threeten.bp.temporal.Temporal; +import org.threeten.bp.temporal.TemporalAccessor; +import org.threeten.bp.temporal.TemporalAdjuster; +import org.threeten.bp.temporal.TemporalField; +import org.threeten.bp.temporal.TemporalQueries; +import org.threeten.bp.temporal.TemporalQuery; +import org.threeten.bp.temporal.ValueRange; + +/** + * A quarter-of-year, such as 'Q2'. + *

+ * {@code QuarterOfYear} is an enum representing the 4 quarters of the year - + * Q1, Q2, Q3 and Q4. These are defined as January to March, April to June, + * July to September and October to December. + *

+ * The {@code int} value follows the quarter, from 1 (Q1) to 4 (Q4). + * 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 QuarterOfYear}. + * 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 quarter-of-year + * concept defined exactly equivalent to the ISO calendar system. + * + *

Specification for implementors

+ * This is an immutable and thread-safe enum. + */ +public enum QuarterOfYear implements TemporalAccessor, TemporalAdjuster { + + /** + * The singleton instance for the first quarter-of-year, from January to March. + * This has the numeric value of {@code 1}. + */ + Q1, + /** + * The singleton instance for the second quarter-of-year, from April to June. + * This has the numeric value of {@code 2}. + */ + Q2, + /** + * The singleton instance for the third quarter-of-year, from July to September. + * This has the numeric value of {@code 3}. + */ + Q3, + /** + * The singleton instance for the fourth quarter-of-year, from October to December. + * This has the numeric value of {@code 4}. + */ + Q4; + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code QuarterOfYear} from an {@code int} value. + *

+ * {@code QuarterOfYear} is an enum representing the 4 quarters of the year. + * This factory allows the enum to be obtained from the {@code int} value. + * The {@code int} value follows the quarter, from 1 (Q1) to 4 (Q4). + * + * @param quarterOfYear the quarter-of-year to represent, from 1 (Q1) to 4 (Q4) + * @return the QuarterOfYear singleton, not null + * @throws DateTimeException if the value is invalid + */ + public static QuarterOfYear of(int quarterOfYear) { + switch (quarterOfYear) { + case 1: return Q1; + case 2: return Q2; + case 3: return Q3; + case 4: return Q4; + default: throw new DateTimeException("Invalid value for QuarterOfYear: " + quarterOfYear); + } + } + + /** + * Obtains an instance of {@code QuarterOfYear} from a month-of-year. + *

+ * {@code QuarterOfYear} is an enum representing the 4 quarters of the year. + * This factory allows the enum to be obtained from the {@code Month} value. + *

+ * January to March are Q1, April to June are Q2, July to September are Q3 + * and October to December are Q4. + * + * @param month the month-of-year to convert from, from 1 to 12 + * @return the QuarterOfYear singleton, not null + */ + public static QuarterOfYear ofMonth(Month month) { + Objects.requireNonNull(month, "month"); + return of(month.ordinal() / 3 + 1); + } + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code QuarterOfYear} 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 IsoFields#QUARTER_OF_YEAR QUARTER_OF_YEAR} field. + *

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

+ * The values are numbered following the ISO-8601 standard, + * from 1 (Q1) to 4 (Q4). + * + * @return the quarter-of-year, from 1 (Q1) to 4 (Q4) + */ + public int getValue() { + return ordinal() + 1; + } + + //----------------------------------------------------------------------- + /** + * Gets the textual representation, such as 'Q1' or '4th quarter'. + *

+ * This returns the textual name used to identify the quarter-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 quarter-of-year, not null + */ + public String getDisplayName(TextStyle style, Locale locale) { + return new DateTimeFormatterBuilder().appendText(QUARTER_OF_YEAR, style).toFormatter(locale).format(this); + } + + //----------------------------------------------------------------------- + /** + * Checks if the specified field is supported. + *

+ * This checks if this quarter-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. + *

+ * All {@code ChronoField} instances will return false. + *

+ * If the field is {@link IsoFields#QUARTER_OF_YEAR QUARTER_OF_YEAR} then this + * method returns true. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.isSupportedBy(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 quarter-of-year, false if not + */ + @Override + public boolean isSupported(TemporalField field) { + if (field == QUARTER_OF_YEAR) { + return true; + } else if (field instanceof ChronoField) { + return false; + } + return field != null && field.isSupportedBy(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 quarter-of-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. + *

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

+ * If the field is {@link IsoFields#QUARTER_OF_YEAR QUARTER_OF_YEAR} then this + * method returns the range of the quarter-of-year, from 1 to 4. + * 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.rangeRefinedBy(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 == QUARTER_OF_YEAR) { + return QUARTER_OF_YEAR.range(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.rangeRefinedBy(this); + } + + /** + * Gets the value of the specified field from this quarter-of-year as an {@code int}. + *

+ * This queries this quarter-of-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. + *

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

+ * If the field is {@link IsoFields#QUARTER_OF_YEAR QUARTER_OF_YEAR} then the + * value of the quarter-of-year, from 0 to 1, will be returned. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.getFrom(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 == QUARTER_OF_YEAR) { + return getValue(); + } + return range(field).checkValidIntValue(getLong(field), field); + } + + /** + * Gets the value of the specified field from this quarter-of-year as a {@code long}. + *

+ * This queries this quarter-of-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. + *

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

+ * If the field is {@link IsoFields#QUARTER_OF_YEAR QUARTER_OF_YEAR} then the + * value of the quarter-of-year, from 0 to 1, will be returned. + *

+ * If the field is not a {@code ChronoField}, then the result of this method + * is obtained by invoking {@code TemporalField.getFrom(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 == QUARTER_OF_YEAR) { + return getValue(); + } else if (field instanceof ChronoField) { + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.getFrom(this); + } + + //----------------------------------------------------------------------- + /** + * Returns the quarter that is the specified number of quarters after this one. + *

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

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

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

+ * This instance is immutable and unaffected by this method call. + * + * @param quarters the quarters to subtract, positive or negative + * @return the resulting quarter, not null + */ + public QuarterOfYear minus(long quarters) { + return plus(-(quarters % 4)); + } + + //----------------------------------------------------------------------- + /** + * Gets the first of the three months that this quarter refers to. + *

+ * Q1 will return January.
+ * Q2 will return April.
+ * Q3 will return July.
+ * Q4 will return October. + *

+ * To obtain the other two months of the quarter, simply use {@link Month#plus(long)} + * on the returned month. + * + * @return the first month in the quarter, not null + */ + public Month firstMonth() { + switch (this) { + case Q1: return Month.JANUARY; + case Q2: return Month.APRIL; + case Q3: return Month.JULY; + case Q4: return Month.OCTOBER; + default: throw new IllegalStateException("Unreachable"); + } + } + + //----------------------------------------------------------------------- + /** + * Queries this quarter-of-year using the specified query. + *

+ * This queries this quarter-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 == TemporalQueries.precision()) { + return (R) QUARTER_YEARS; + } else if (query == TemporalQueries.zoneId() || query == TemporalQueries.chronology()) { + return null; + } + return query.queryFrom(this); + } + + /** + * Adjusts the specified temporal object to have this quarter-of-year. + *

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

+ * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} + * passing {@link IsoFields#QUARTER_OF_YEAR} 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 = thisDayOfWeek.adjustInto(temporal);
+     *   temporal = temporal.with(thisDayOfWeek);
+     * 
+ *

+ * 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 (Chronology.from(temporal).equals(IsoChronology.INSTANCE) == false) { + throw new DateTimeException("Adjustment only supported on ISO date-time"); + } + return temporal.with(QUARTER_OF_YEAR, getValue()); + } + +} diff --git a/src/main/java/org/threeten/extra/Seconds.java b/src/main/java/org/threeten/extra/Seconds.java new file mode 100644 index 0000000..06df9cc --- /dev/null +++ b/src/main/java/org/threeten/extra/Seconds.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import static org.threeten.bp.temporal.ChronoUnit.SECONDS; + +import java.io.Serializable; + +import org.threeten.bp.temporal.TemporalUnit; + +/** + * An amount of time measured in seconds, such as '6 Seconds'. + *

+ * This class stores an amount of time in terms of the seconds unit of time. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + */ +public final class Seconds extends AbstractSimpleAmount implements Serializable { + + /** + * A constant for zero seconds. + */ + public static final Seconds ZERO = new Seconds(0); + /** + * A serialization identifier for this class. + */ + private static final long serialVersionUID = -8903767091325669093L; + + /** + * The number of seconds. + */ + private final int seconds; + + /** + * Obtains an instance of {@code Seconds}. + * + * @param seconds the number of seconds the instance will represent, may be negative + * @return the {@code Seconds} instance, not null + */ + public static Seconds of(int seconds) { + if (seconds == 0) { + return ZERO; + } + return new Seconds(seconds); + } + + //----------------------------------------------------------------------- + /** + * Constructs an instance using a specific number of seconds. + * + * @param seconds the seconds to use + */ + private Seconds(int seconds) { + super(); + this.seconds = seconds; + } + + /** + * Resolves singletons. + * + * @return the singleton instance + */ + private Object readResolve() { + return Seconds.of(seconds); + } + + //----------------------------------------------------------------------- + /** + * Gets the number of seconds in this amount. + * + * @return the number of seconds, may be negative + */ + @Override + public int getAmount() { + return seconds; + } + + /** + * Returns a new instance of the subclass with a different number of seconds. + * + * @param amount the number of seconds to set in the new instance, may be negative + * @return a new period element, not null + */ + @Override + public Seconds withAmount(int amount) { + return Seconds.of(amount); + } + + //----------------------------------------------------------------------- + /** + * Gets the unit defining the amount of time. + * + * @return the seconds unit, not null + */ + @Override + public TemporalUnit getUnit() { + return SECONDS; + } + + //----------------------------------------------------------------------- + /** + * Returns a new instance with the specified amount of time added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to add, may be negative + * @return the new amount plus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public Seconds plus(Seconds amount) { + return plus(amount.getAmount()); + } + + /** + * Returns a new instance with the specified amount of time subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to add, may be negative + * @return the new amount minus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public Seconds minus(Seconds amount) { + return minus(amount.getAmount()); + } + + //----------------------------------------------------------------------- + /** + * Returns a string representation of the number of seconds. + * This will be in the format 'PTnS' where n is the number of seconds. + * + * @return the number of seconds in ISO8601 string format + */ + @Override + public String toString() { + return "PT" + seconds + "S"; + } + +} diff --git a/src/main/java/org/threeten/extra/WeekendRules.java b/src/main/java/org/threeten/extra/WeekendRules.java new file mode 100644 index 0000000..f3f1c41 --- /dev/null +++ b/src/main/java/org/threeten/extra/WeekendRules.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import static org.threeten.bp.temporal.ChronoField.DAY_OF_WEEK; +import static org.threeten.bp.temporal.ChronoUnit.DAYS; + +import org.threeten.bp.temporal.Temporal; +import org.threeten.bp.temporal.TemporalAdjuster; + +/** + * A helper class for rules around weekends. + * + *

Specification for implementors

+ * This is a thread-safe utility class. + * All returned classes are immutable and thread-safe. + */ +public final class WeekendRules { + + /** + * Restricted constructor. + */ + private WeekendRules() { + } + + /** + * Returns the next non weekend day adjuster, which adjusts the date one day + * forward skipping Saturday and Sunday. + *

+ * Some territories have weekends that do not consist of Saturday and Sunday. + * No implementation is supplied to support this, however an adjuster + * can be easily written to do so. + * + * @return the next working day adjuster, not null + */ + public static TemporalAdjuster nextNonWeekendDay() { + return Adjuster.NEXT_NON_WEEKEND; + } + + //----------------------------------------------------------------------- + /** + * Enum implementing the adjusters. + */ + private static enum Adjuster implements TemporalAdjuster { + /** Next non weekend day adjuster. */ + NEXT_NON_WEEKEND { + @Override + public Temporal adjustInto(Temporal dateTime) { + int dow = dateTime.get(DAY_OF_WEEK); + switch (dow) { + case 6: // Saturday + return dateTime.plus(2, DAYS); + case 5: // Friday + return dateTime.plus(3, DAYS); + default: + return dateTime.plus(1, DAYS); + } + } + }, + } + +} diff --git a/src/main/java/org/threeten/extra/Weeks.java b/src/main/java/org/threeten/extra/Weeks.java new file mode 100644 index 0000000..caf6da0 --- /dev/null +++ b/src/main/java/org/threeten/extra/Weeks.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import static org.threeten.bp.temporal.ChronoUnit.WEEKS; + +import java.io.Serializable; + +import org.threeten.bp.temporal.TemporalUnit; + +/** + * An amount of time measured in weeks, such as '6 Weeks'. + *

+ * This class stores an amount of time in terms of the weeks unit of time. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + */ +public final class Weeks extends AbstractSimpleAmount implements Serializable { + + /** + * A constant for zero weeks. + */ + public static final Weeks ZERO = new Weeks(0); + /** + * A serialization identifier for this class. + */ + private static final long serialVersionUID = -8903767091325669093L; + + /** + * The number of weeks. + */ + private final int weeks; + + /** + * Obtains an instance of {@code Weeks}. + * + * @param weeks the number of weeks the instance will represent, may be negative + * @return the {@code Weeks} instance, not null + */ + public static Weeks of(int weeks) { + if (weeks == 0) { + return ZERO; + } + return new Weeks(weeks); + } + + //----------------------------------------------------------------------- + /** + * Constructs an instance using a specific number of weeks. + * + * @param weeks the weeks to use + */ + private Weeks(int weeks) { + super(); + this.weeks = weeks; + } + + /** + * Resolves singletons. + * + * @return the singleton instance + */ + private Object readResolve() { + return Weeks.of(weeks); + } + + //----------------------------------------------------------------------- + /** + * Gets the number of weeks in this amount. + * + * @return the number of weeks, may be negative + */ + @Override + public int getAmount() { + return weeks; + } + + /** + * Returns a new instance of the subclass with a different number of weeks. + * + * @param amount the number of weeks to set in the new instance, may be negative + * @return a new period element, not null + */ + @Override + public Weeks withAmount(int amount) { + return Weeks.of(amount); + } + + //----------------------------------------------------------------------- + /** + * Gets the unit defining the amount of time. + * + * @return the weeks unit, not null + */ + @Override + public TemporalUnit getUnit() { + return WEEKS; + } + + //----------------------------------------------------------------------- + /** + * Returns a new instance with the specified amount of time added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to add, may be negative + * @return the new amount plus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public Weeks plus(Weeks amount) { + return plus(amount.getAmount()); + } + + /** + * Returns a new instance with the specified amount of time subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to add, may be negative + * @return the new amount minus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public Weeks minus(Weeks amount) { + return minus(amount.getAmount()); + } + + //----------------------------------------------------------------------- + /** + * Returns a string representation of the number of weeks. + * This will be in the format 'PnW' where n is the number of weeks. + * + * @return the number of weeks in ISO8601 string format + */ + @Override + public String toString() { + return "P" + weeks + "W"; + } + +} diff --git a/src/main/java/org/threeten/extra/Years.java b/src/main/java/org/threeten/extra/Years.java new file mode 100644 index 0000000..497dfeb --- /dev/null +++ b/src/main/java/org/threeten/extra/Years.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import static org.threeten.bp.temporal.ChronoUnit.YEARS; + +import java.io.Serializable; + +import org.threeten.bp.temporal.TemporalUnit; + +/** + * An amount of time measured in years, such as '6 Years'. + *

+ * This class stores an amount of time in terms of the years unit of time. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + */ +public final class Years extends AbstractSimpleAmount implements Serializable { + + /** + * A constant for zero years. + */ + public static final Years ZERO = new Years(0); + /** + * A serialization identifier for this class. + */ + private static final long serialVersionUID = -8903767091325669093L; + + /** + * The number of years. + */ + private final int years; + + /** + * Obtains an instance of {@code Years}. + * + * @param years the number of years the instance will represent, may be negative + * @return the {@code Years} instance, not null + */ + public static Years of(int years) { + if (years == 0) { + return ZERO; + } + return new Years(years); + } + + //----------------------------------------------------------------------- + /** + * Constructs an instance using a specific number of years. + * + * @param years the years to use + */ + private Years(int years) { + super(); + this.years = years; + } + + /** + * Resolves singletons. + * + * @return the singleton instance + */ + private Object readResolve() { + return Years.of(years); + } + + //----------------------------------------------------------------------- + /** + * Gets the number of years in this amount. + * + * @return the number of years, may be negative + */ + @Override + public int getAmount() { + return years; + } + + /** + * Returns a new instance of the subclass with a different number of years. + * + * @param amount the number of years to set in the new instance, may be negative + * @return a new period element, not null + */ + @Override + public Years withAmount(int amount) { + return Years.of(amount); + } + + //----------------------------------------------------------------------- + /** + * Gets the unit defining the amount of time. + * + * @return the years unit, not null + */ + @Override + public TemporalUnit getUnit() { + return YEARS; + } + + //----------------------------------------------------------------------- + /** + * Returns a new instance with the specified amount of time added. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to add, may be negative + * @return the new amount plus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public Years plus(Years amount) { + return plus(amount.getAmount()); + } + + /** + * Returns a new instance with the specified amount of time subtracted. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param amount the amount of time to add, may be negative + * @return the new amount minus the specified amount of time, not null + * @throws ArithmeticException if the result overflows an {@code int} + */ + public Years minus(Years amount) { + return minus(amount.getAmount()); + } + + //----------------------------------------------------------------------- + /** + * Returns a string representation of the number of years. + * This will be in the format 'PnY' where n is the number of years. + * + * @return the number of years in ISO8601 string format + */ + @Override + public String toString() { + return "P" + years + "Y"; + } + +} diff --git a/src/main/java/org/threeten/extra/chrono/CopticChronology.java b/src/main/java/org/threeten/extra/chrono/CopticChronology.java new file mode 100644 index 0000000..c73c33d --- /dev/null +++ b/src/main/java/org/threeten/extra/chrono/CopticChronology.java @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra.chrono; + +import static org.threeten.bp.temporal.ChronoField.EPOCH_DAY; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +import org.threeten.bp.Clock; +import org.threeten.bp.DateTimeException; +import org.threeten.bp.Instant; +import org.threeten.bp.ZoneId; +import org.threeten.bp.chrono.ChronoLocalDateTime; +import org.threeten.bp.chrono.ChronoZonedDateTime; +import org.threeten.bp.chrono.Chronology; +import org.threeten.bp.chrono.Era; +import org.threeten.bp.jdk8.Jdk8Methods; +import org.threeten.bp.temporal.ChronoField; +import org.threeten.bp.temporal.TemporalAccessor; +import org.threeten.bp.temporal.ValueRange; + +/** + * 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. + *

+ * + *

Specification for implementors

+ * This class is immutable and thread-safe. + */ +public final class CopticChronology extends Chronology implements Serializable { + + /** + * Singleton instance of the Coptic chronology. + */ + public static final CopticChronology INSTANCE = new CopticChronology(); + + /** + * 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); + + /** + * Restricted constructor. + */ + private CopticChronology() { + } + + /** + * 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 // override with covariant return type + public CopticDate date(Era era, int yearOfEra, int month, int dayOfMonth) { + return (CopticDate) super.date(era, yearOfEra, month, dayOfMonth); + } + + @Override // override with covariant return type + public CopticDate date(int prolepticYear, int month, int dayOfMonth) { + return new CopticDate(prolepticYear, month, dayOfMonth); + } + + @Override // override with covariant return type + public CopticDate dateYearDay(Era era, int yearOfEra, int dayOfYear) { + return (CopticDate) super.dateYearDay(era, yearOfEra, dayOfYear); + } + + @Override // override with covariant return type + public CopticDate dateYearDay(int prolepticYear, int dayOfYear) { + return new CopticDate(prolepticYear, (dayOfYear - 1) / 30 + 1, (dayOfYear - 1) % 30 + 1); + } + + //----------------------------------------------------------------------- + @Override // override with covariant return type + public CopticDate date(TemporalAccessor temporal) { + if (temporal instanceof CopticDate) { + return (CopticDate) temporal; + } + return CopticDate.ofEpochDay(temporal.getLong(EPOCH_DAY)); + } + + @Override // override with covariant return type + public ChronoLocalDateTime localDateTime(TemporalAccessor temporal) { + return (ChronoLocalDateTime) super.localDateTime(temporal); + } + + @Override // override with covariant return type + public ChronoZonedDateTime zonedDateTime(TemporalAccessor temporal) { + return (ChronoZonedDateTime) super.zonedDateTime(temporal); + } + + @Override // override with covariant return type + public ChronoZonedDateTime zonedDateTime(Instant instant, ZoneId zone) { + return (ChronoZonedDateTime) super.zonedDateTime(instant, zone); + } + + //----------------------------------------------------------------------- + @Override // override with covariant return type + public CopticDate dateNow() { + return (CopticDate) super.dateNow(); + } + + @Override // override with covariant return type + public CopticDate dateNow(ZoneId zone) { + return (CopticDate) super.dateNow(zone); + } + + @Override // override with covariant return type + public CopticDate dateNow(Clock clock) { + Objects.requireNonNull(clock, "clock"); + return (CopticDate) super.dateNow(clock); + } + + //----------------------------------------------------------------------- + /** + * 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 Jdk8Methods.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/src/main/java/org/threeten/extra/chrono/CopticDate.java b/src/main/java/org/threeten/extra/chrono/CopticDate.java new file mode 100644 index 0000000..d127816 --- /dev/null +++ b/src/main/java/org/threeten/extra/chrono/CopticDate.java @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra.chrono; + +import static org.threeten.bp.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; +import static org.threeten.bp.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; +import static org.threeten.bp.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; +import static org.threeten.bp.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; +import static org.threeten.bp.temporal.ChronoField.DAY_OF_MONTH; +import static org.threeten.bp.temporal.ChronoField.MONTH_OF_YEAR; + +import java.io.Serializable; + +import org.threeten.bp.DateTimeException; +import org.threeten.bp.LocalDate; +import org.threeten.bp.Period; +import org.threeten.bp.Year; +import org.threeten.bp.chrono.ChronoLocalDate; +import org.threeten.bp.jdk8.DefaultInterfaceChronoLocalDate; +import org.threeten.bp.jdk8.Jdk8Methods; +import org.threeten.bp.temporal.ChronoField; +import org.threeten.bp.temporal.ChronoUnit; +import org.threeten.bp.temporal.Temporal; +import org.threeten.bp.temporal.TemporalAdjuster; +import org.threeten.bp.temporal.TemporalAmount; +import org.threeten.bp.temporal.TemporalField; +import org.threeten.bp.temporal.TemporalUnit; +import org.threeten.bp.temporal.ValueRange; + +/** + * A date in the Coptic calendar system. + *

+ * This implements {@code ChronoLocalDate} for the {@link CopticChronology Coptic calendar}. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + */ +public final class CopticDate + extends DefaultInterfaceChronoLocalDate + 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 a date in Coptic calendar system from the Era, year-of-era, + * month-of-year and day-of-month. + * + * @param era the CopticEra, not null + * @param year the calendar system year-of-era + * @param month the calendar system month-of-year + * @param dayOfMonth the calendar system day-of-month + * @return the date in this calendar system, not null + */ + public static CopticDate of(CopticEra era, int year, int month, int dayOfMonth) { + return (CopticDate)CopticChronology.INSTANCE.date(era, year, month, dayOfMonth); + } + + /** + * 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 + */ + public static CopticDate ofEpochDay(long epochDay) { + // TODO: validate +// if (epochDay < MIN_EPOCH_DAY || epochDay > MAX_EPOCH_DAY) { +// throw new CalendricalRuleException("Date exceeds supported range for CopticDate", Coptic.YEAR); +// } + 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 = CopticChronology.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) { + CopticChronology.MOY_RANGE.checkValidValue(month, MONTH_OF_YEAR); + ValueRange range; + if (month == 13) { + range = CopticChronology.INSTANCE.isLeapYear(prolepticYear) ? CopticChronology.DOM_RANGE_LEAP : CopticChronology.DOM_RANGE_NONLEAP; + } else { + range = CopticChronology.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 CopticChronology getChronology() { + return CopticChronology.INSTANCE; + } + + //----------------------------------------------------------------------- + @Override + public int lengthOfMonth() { + switch (month) { + case 13: + return (isLeapYear() ? 6 : 5); + default: + return 30; + } + } + + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return ((ChronoField) field).isDateField(); + } + return field != null && field.isSupportedBy(this); + } + + @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 getChronology().range(f); + } + throw new DateTimeException("Unsupported field: " + field.getName()); + } + return field.rangeRefinedBy(this); + } + + @Override + public long getLong(TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case DAY_OF_WEEK: return getDayOfWeek(); + 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: return toEpochDay(); + 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 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.getFrom(this); + } + + private int getDayOfYear() { + return (month - 1) * 30 + day; + } + + private int getDayOfWeek() { + return Jdk8Methods.floorMod(toEpochDay() + 3, 7) + 1; + } + + //------------------------------------------------------------------------- + @Override + public CopticDate with(TemporalAdjuster adjuster) { + return (CopticDate) adjuster.adjustInto(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 - getDayOfWeek()); + 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.adjustInto(this, newValue); + } + + //----------------------------------------------------------------------- + @Override + public CopticDate plus(TemporalAmount amount) { + return (CopticDate) super.plus(amount); + } + + @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(Jdk8Methods.safeMultiply(amountToAdd, 7)); + case MONTHS: return plusMonths(amountToAdd); + case YEARS: return plusYears(amountToAdd); + case DECADES: return plusYears(Jdk8Methods.safeMultiply(amountToAdd, 10)); + case CENTURIES: return plusYears(Jdk8Methods.safeMultiply(amountToAdd, 100)); + case MILLENNIA: return plusYears(Jdk8Methods.safeMultiply(amountToAdd, 1000)); +// case ERAS: throw new DateTimeException("Unable to add era, standard calendar system only has one era"); +// case FOREVER: return (period == 0 ? this : (period > 0 ? LocalDate.MAX_DATE : LocalDate.MIN_DATE)); + } + throw new DateTimeException(unit.getName() + " not valid for CopticDate"); + } + return unit.addTo(this, amountToAdd); + } + + //----------------------------------------------------------------------- + private CopticDate plusYears(long years) { + return plusMonths(Jdk8Methods.safeMultiply(years, 13)); + } + + private CopticDate plusMonths(long months) { + if (months == 0) { + return this; + } + long curEm = prolepticYear * 13L + (month - 1); + long calcEm = Jdk8Methods.safeAdd(curEm, months); + int newYear = Jdk8Methods.safeToInt(Jdk8Methods.floorDiv(calcEm, 13)); + int newMonth = Jdk8Methods.floorMod(calcEm, 13) + 1; + return resolvePreviousValid(newYear, newMonth, day); + } + + private CopticDate plusDays(long days) { + if (days == 0) { + return this; + } + return CopticDate.ofEpochDay(Jdk8Methods.safeAdd(toEpochDay(), days)); + } + + //------------------------------------------------------------------------- + @Override + public CopticDate minus(TemporalAmount amount) { + return (CopticDate) super.minus(amount); + } + + @Override + public CopticDate minus(long amountToSubtract, TemporalUnit unit) { + return (CopticDate) super.minus(amountToSubtract, unit); + } + +// public CopticDate withEra(Era era) { +// return with(ChronoField.ERA, era.getValue()); +// } +// +// public CopticDate withYear(int year) { +// return with(ChronoField.YEAR_OF_ERA, year); +// } +// +// public CopticDate withMonth(int month) { +// return with(ChronoField.MONTH_OF_YEAR, month); +// } +// +// public CopticDate withDayOfMonth(int dayOfMonth) { +// return with(ChronoField.DAY_OF_MONTH, month); +// } +// +// public CopticDate withDayOfYear(int dayOfYear) { +// return with(ChronoField.DAY_OF_YEAR, month); +// } +// +// public CopticDate minusYears(long yearsToSubtract) { +// return (yearsToSubtract == Long.MIN_VALUE ? plusYears(Long.MAX_VALUE).plusYears(1) : plusYears(-yearsToSubtract)); +// } +// +// public CopticDate minusMonths(long monthsToSubtract) { +// return (monthsToSubtract == Long.MIN_VALUE ? plusMonths(Long.MAX_VALUE).plusMonths(1) : plusMonths(-monthsToSubtract)); +// } +// +// public CopticDate minusWeeks(long weeksToSubtract) { +// return (weeksToSubtract == Long.MIN_VALUE ? plusWeeks(Long.MAX_VALUE).plusWeeks(1) : plusWeeks(-weeksToSubtract)); +// } +// +// public CopticDate minusDays(long daysToSubtract) { +// return (daysToSubtract == Long.MIN_VALUE ? plusDays(Long.MAX_VALUE).plusDays(1) : plusDays(-daysToSubtract)); +// } + + @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 (getChronology().equals(end.getChronology()) == 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); + } + + @Override + public Period periodUntil(ChronoLocalDate endDate) { + throw new UnsupportedOperationException(); + } + + //----------------------------------------------------------------------- + @Override + public long toEpochDay() { + long year = (long) prolepticYear; + long copticEpochDay = ((year - 1) * 365) + Jdk8Methods.floorDiv(year, 4) + (getDayOfYear() - 1); + return copticEpochDay - EPOCH_DAY_DIFFERENCE; + } + +} diff --git a/src/main/java/org/threeten/extra/chrono/CopticEra.java b/src/main/java/org/threeten/extra/chrono/CopticEra.java new file mode 100644 index 0000000..fdd0abc --- /dev/null +++ b/src/main/java/org/threeten/extra/chrono/CopticEra.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2007-2013, 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, + * PAMUREMENT 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 org.threeten.extra.chrono; + +import static org.threeten.bp.temporal.ChronoField.ERA; + +import java.util.Locale; + +import org.threeten.bp.DateTimeException; +import org.threeten.bp.chrono.Era; +import org.threeten.bp.format.DateTimeFormatterBuilder; +import org.threeten.bp.format.TextStyle; +import org.threeten.bp.temporal.ChronoField; +import org.threeten.bp.temporal.Temporal; +import org.threeten.bp.temporal.TemporalField; +import org.threeten.bp.temporal.TemporalQueries; +import org.threeten.bp.temporal.TemporalQuery; +import org.threeten.bp.temporal.ValueRange; + +/** + * 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. + * + *

Specification for implementors

+ * This is an immutable and thread-safe enum. + */ +public 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) + */ + @Override + public int getValue() { + return ordinal(); + } + + @Override + public CopticChronology getChronology() { + return CopticChronology.INSTANCE; + } + + // JDK8 default methods: + //----------------------------------------------------------------------- + @Override + public CopticDate date(int year, int month, int day) { + return (CopticDate) getChronology().date(this, year, month, day); + } + + @Override + public CopticDate dateYearDay(int year, int dayOfYear) { + return (CopticDate) getChronology().dateYearDay(this, year, dayOfYear); + } + + //----------------------------------------------------------------------- + @Override + public boolean isSupported(TemporalField field) { + if (field instanceof ChronoField) { + return field == ERA; + } + return field != null && field.isSupportedBy(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.rangeRefinedBy(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.getFrom(this); + } + + //------------------------------------------------------------------------- + @Override + public Temporal adjustInto(Temporal temporal) { + return temporal.with(ERA, getValue()); + } + + @SuppressWarnings("unchecked") + @Override + public R query(TemporalQuery query) { + if (query == TemporalQueries.chronology()) { + return (R) getChronology(); + } + return query.queryFrom(this); + } + + //----------------------------------------------------------------------- + @Override + public String getDisplayName(TextStyle style, Locale locale) { + return new DateTimeFormatterBuilder().appendText(ERA, style).toFormatter(locale).format(this); + } + +} diff --git a/src/main/java/org/threeten/extra/scale/SystemUTCRules.java b/src/main/java/org/threeten/extra/scale/SystemUTCRules.java new file mode 100644 index 0000000..fb1f170 --- /dev/null +++ b/src/main/java/org/threeten/extra/scale/SystemUTCRules.java @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra.scale; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.io.StreamCorruptedException; +import java.net.URL; +import java.util.Arrays; +import java.util.ConcurrentModificationException; +import java.util.Enumeration; +import java.util.concurrent.atomic.AtomicReference; + +import org.threeten.bp.jdk8.Jdk8Methods; + +/** + * System default UTC rules. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + */ +final class SystemUTCRules extends UTCRules implements Serializable { + + /** + * Singleton. + */ + static final SystemUTCRules INSTANCE = new SystemUTCRules(); + /** + * Serialization version. + */ + private static final long serialVersionUID = 7594178360693417218L; + + /** + * The table of leap second dates. + */ + private AtomicReference dataRef = new AtomicReference(loadLeapSeconds()); + + /** Data holder. */ + private static final class Data implements Serializable { + /** Serialization version. */ + private static final long serialVersionUID = -3655687912882817265L; + /** Constructor. */ + private Data(long[] dates, int[] offsets, long[] taiSeconds) { + super(); + this.dates = dates; + this.offsets = offsets; + this.taiSeconds = taiSeconds; + } + /** The table of leap second date when the leap second occurs. */ + final long[] dates; + /** The table of TAI offset after the leap second. */ + final int[] offsets; + /** The table of TAI second when the new offset starts. */ + final long[] taiSeconds; + + /** + * @return The modified Julian Date of the newest leap second + */ + public long getNewestDate() { + return dates[dates.length - 1]; + } + } + + //----------------------------------------------------------------------- + /** + * Restricted constructor. + */ + private SystemUTCRules() { + } + + /** + * Resolves singleton. + * + * @return the resolved instance, not null + */ + private Object readResolve() { + return INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Adds a new leap second to these rules. + * + * @param mjDay the modified julian date that the leap second occurs at the end of + * @param leapAdjustment the leap seconds to add/remove at the end of the day, either -1 or 1 + * @throws IllegalArgumentException if the leap adjustment is invalid + * @throws IllegalArgumentException if the day is before or equal the last known leap second day + * and the definition does not match a previously registered leap + * @throws ConcurrentModificationException if another thread updates the rules at the same time + */ + void registerLeapSecond(long mjDay, int leapAdjustment) { + if (leapAdjustment != -1 && leapAdjustment != 1) { + throw new IllegalArgumentException("Leap adjustment must be -1 or 1"); + } + Data data = dataRef.get(); + int pos = Arrays.binarySearch(data.dates, mjDay); + int currentAdj = pos > 0 ? data.offsets[pos] - data.offsets[pos - 1] : 0; + if (currentAdj == leapAdjustment) { + return; // matches previous definition + } + if (mjDay <= data.dates[data.dates.length - 1]) { + throw new IllegalArgumentException("Date must be after the last configured leap second date"); + } + long[] dates = Arrays.copyOf(data.dates, data.dates.length + 1); + int[] offsets = Arrays.copyOf(data.offsets, data.offsets.length + 1); + long[] taiSeconds = Arrays.copyOf(data.taiSeconds, data.taiSeconds.length + 1); + int offset = offsets[offsets.length - 2] + leapAdjustment; + dates[dates.length - 1] = mjDay; + offsets[offsets.length - 1] = offset; + taiSeconds[taiSeconds.length - 1] = tai(mjDay, offset); + Data newData = new Data(dates, offsets, taiSeconds); + if (dataRef.compareAndSet(data, newData) == false) { + throw new ConcurrentModificationException("Unable to update leap second rules as they have already been updated"); + } + } + + //----------------------------------------------------------------------- + @Override + public String getName() { + return "System"; + } + + @Override + public int getLeapSecondAdjustment(long mjDay) { + Data data = dataRef.get(); + int pos = Arrays.binarySearch(data.dates, mjDay); + return pos > 0 ? data.offsets[pos] - data.offsets[pos - 1] : 0; + } + + @Override + public int getTAIOffset(long mjDay) { + Data data = dataRef.get(); + int pos = Arrays.binarySearch(data.dates, mjDay); + pos = (pos < 0 ? ~pos : pos); + return pos > 0 ? data.offsets[pos - 1] : 10; + } + + @Override + public long[] getLeapSecondDates() { + Data data = dataRef.get(); + return data.dates.clone(); + } + + //----------------------------------------------------------------------- + @Override + protected UTCInstant convertToUTC(TAIInstant taiInstant) { + Data data = dataRef.get(); + long[] mjds = data.dates; + long[] tais = data.taiSeconds; + int pos = Arrays.binarySearch(tais, taiInstant.getTAISeconds()); + pos = (pos >= 0 ? pos : ~pos - 1); + int taiOffset = (pos >= 0 ? data.offsets[pos] : 10); + long adjustedTaiSecs = taiInstant.getTAISeconds() - taiOffset; + long mjd = Jdk8Methods.floorDiv(adjustedTaiSecs, SECS_PER_DAY) + OFFSET_MJD_TAI; + long nod = Jdk8Methods.floorMod(adjustedTaiSecs, SECS_PER_DAY) * NANOS_PER_SECOND + taiInstant.getNano(); + long mjdNextRegionStart = (pos + 1 < mjds.length ? mjds[pos + 1] + 1 : Long.MAX_VALUE); + if (mjd == mjdNextRegionStart) { // in leap second + mjd--; + nod = SECS_PER_DAY * NANOS_PER_SECOND + (nod / NANOS_PER_SECOND) * NANOS_PER_SECOND + nod % NANOS_PER_SECOND; + } + return UTCInstant.ofModifiedJulianDay(mjd, nod, this); + } + + //----------------------------------------------------------------------- + /** + * Loads the rules from files in the class loader, often jar files. + * + * @return the list of loaded rules, not null + * @throws Exception if an error occurs + */ + private static Data loadLeapSeconds() { + Data bestData = null; + URL url = null; + try { + Enumeration en = Thread.currentThread().getContextClassLoader().getResources("javax/time/LeapSecondRules.dat"); + while (en.hasMoreElements()) { + url = en.nextElement(); + Data candidate = loadLeapSeconds(url); + if (bestData == null || candidate.getNewestDate() > bestData.getNewestDate()) { + bestData = candidate; + } + } + } catch (Exception ex) { + throw new RuntimeException("Unable to load time-zone rule data: " + url, ex); + } + if (bestData == null) { + // no data on classpath, but we allow manual registration of leap seconds + // setup basic known data - MJD 1972-01-01 is 41317L, where offset was 10 + bestData = new Data(new long[] {41317L}, new int[] {10}, new long[] {tai(41317L, 10)}); + } + return bestData; + } + + /** + * Loads the leap second rules from a URL, often in a jar file. + * + * @param url the jar file to load, not null + * @throws Exception if an error occurs + */ + private static Data loadLeapSeconds(URL url) throws ClassNotFoundException, IOException { + boolean throwing = false; + InputStream in = null; + try { + in = url.openStream(); + DataInputStream dis = new DataInputStream(in); + if (dis.readByte() != 1) { + throw new StreamCorruptedException("File format not recognised"); + } + int leaps = dis.readInt(); + long[] dates = new long[leaps]; + int[] offsets = new int[leaps]; + long[] taiSeconds = new long[leaps]; + for (int i = 0 ; i < leaps; ++i) { + long changeMjd = dis.readLong(); // date leap second is added + int offset = dis.readInt(); + dates[i] = changeMjd; + offsets[i] = offset; + taiSeconds[i] = tai(changeMjd, offset); + } + return new Data(dates, offsets, taiSeconds); + } catch (IOException ex) { + throwing = true; + throw ex; + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ex) { + if (throwing == false) { + throw ex; + } + } + } + } + } + + /** + * Gets the TAI seconds for the start of the day following the day passed in. + * + * @param changeMjd the MJD that the leap second is added to + * @param offset the new offset after the leap + * @return the TAI seconds + */ + private static long tai(long changeMjd, int offset) { + return (changeMjd + 1 - OFFSET_MJD_TAI) * SECS_PER_DAY + offset; + } + +} diff --git a/src/main/java/org/threeten/extra/scale/TAIInstant.java b/src/main/java/org/threeten/extra/scale/TAIInstant.java new file mode 100644 index 0000000..10b88f3 --- /dev/null +++ b/src/main/java/org/threeten/extra/scale/TAIInstant.java @@ -0,0 +1,451 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra.scale; + +import java.io.Serializable; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.threeten.bp.DateTimeException; +import org.threeten.bp.Duration; +import org.threeten.bp.Instant; +import org.threeten.bp.format.DateTimeParseException; +import org.threeten.bp.jdk8.Jdk8Methods; + +/** + * An instantaneous point on the time-line measured in the TAI time-scale. + *

+ * Most of the Time Framework for Java works on the assumption that the time-line is + * simple, there are no leap-seconds and there are always 24 * 60 * 60 seconds in a day. + * However, the Earth's rotation is not straightforward, and a solar day does not match + * this definition. + *

+ * This class is an alternative representation based on the TAI time-scale. + * TAI is a single incrementing count of SI seconds. + * There are no leap seconds or other discontinuities. + *

+ * As a result of the simple definition, this time-scale would make an excellent timestamp. + * However, there are, at the time of writing, few easy ways to obtain an accurate TAI instant, + * but it is relatively easy to obtain a GPS instant. + * GPS and TAI differ by the fixed amount of 19 seconds. + *

+ * The duration between two points on the TAI time-scale is calculated solely using this class. + * Do not use the {@code between} method on {@code Duration} as that will lose information. + * Instead use {@link #durationUntil(TAIInstant)} on this class. + *

+ * It is intended that most applications will use the {@code Instant} class + * which uses the UTC-SLS mapping from UTC to guarantee 86400 seconds per day. + * Specialist applications with access to an accurate time-source may find this class useful. + * + *

Time-scale

+ *

+ * The TAI time-scale is a very simple well-regarded representation of time. + * The scale is defined using atomic clocks counting SI seconds. + * It has proceeded in a continuous uninterrupted manner since the defined + * epoch of {@code 1958-01-01T00:00:00(TAI)}. + * There are no leap seconds or other discontinuities. + *

+ * This class may be used for instants in the far past and far future. + * Since some instants will be prior to 1958, it is not strictly an implementation of TAI. + * Instead, it is a proleptic time-scale based on TAI and equivalent to it since 1958. + * + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + */ +public final class TAIInstant + implements Comparable, Serializable { + // does not implement InstantProvider as that would enable methods like + // Duration.between which gives the wrong answer due to lossy conversion + + /** + * Constant for nanos per second. + */ + private static final int NANOS_PER_SECOND = 1000000000; + /** + * Parse regex. + */ + private static final Pattern PARSER = Pattern.compile("([-]?[0-9]+)\\.([0-9]{9})s[(]TAI[)]"); + /** + * Serialization version. + */ + private static final long serialVersionUID = 2133469726395847026L; + + /** + * The number of seconds from the epoch of 1958-01-01T00:00:00(TAI). + */ + 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 an instance of {@code TAIInstant} from the number of seconds from + * the TAI epoch of 1958-01-01T00:00:00(TAI) with a 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: + *

+     *  TAIInstant.ofSeconds(3, 1);
+     *  TAIInstant.ofSeconds(4, -999999999);
+     *  TAIInstant.ofSeconds(2, 1000000001);
+     * 
+ * + * @param taiSeconds the number of seconds from the epoch of 1958-01-01T00:00:00(TAI) + * @param nanoAdjustment the nanosecond adjustment to the number of seconds, positive or negative + * @return the TAI instant, not null + */ + public static TAIInstant ofTAISeconds(long taiSeconds, long nanoAdjustment) { + long secs = Jdk8Methods.safeAdd(taiSeconds, Jdk8Methods.floorDiv(nanoAdjustment, NANOS_PER_SECOND)); + int nos = Jdk8Methods.floorMod(nanoAdjustment, NANOS_PER_SECOND); + return new TAIInstant(secs, nos); + } + + /** + * Obtains an instance of {@code TAIInstant} from an {@code Instant} + * using the system default leap second rules. + *

+ * Converting a UTC-SLS instant to a TAI instant requires leap second rules. + * This method uses the latest available system rules. + * The conversion first maps from UTC-SLS to UTC, then converts to TAI. + *

+ * Conversion from an {@code Instant} will not be completely accurate near + * a leap second in accordance with UTC-SLS. + * + * @param instant the instant to convert, not null + * @return the TAI instant, not null + * @throws ArithmeticException if the calculation exceeds the supported range + */ + public static TAIInstant of(Instant instant) { + return UTCInstant.of(instant).toTAIInstant(); + } + + /** + * Obtains an instance of {@code TAIInstant} from a {@code UTCInstant}. + *

+ * Converting a UTC instant to a TAI instant requires leap second rules. + * This method uses the rules held in within the UTC instant. + *

+ * Conversion from a {@code UTCInstant} will be entirely accurate. + * The resulting TAI instant will not reference the leap second rules, so + * converting back to a UTC instant may result in a different UTC instant. + * + * @param instant the instant to convert, not null + * @return the TAI instant, not null + * @throws ArithmeticException if the calculation exceeds the supported range + */ + public static TAIInstant of(UTCInstant instant) { + return instant.toTAIInstant(); + } + + /** + * Obtains an instance of {@code TAIInstant} from a text string. + *

+ * The following format is accepted: + *

    + *
  • {@code {seconds}.{nanosOfSecond}s(TAI)} + *

+ * The accepted format is strict. + * The seconds part must contain only numbers and a possible leading negative sign. + * The nanoseconds part must contain exactly nine digits. + * The trailing literal must be exactly specified. + * This format parses the {@code toString} format. + * + * @param text the text to parse such as "12345.123456789s(TAI)", not null + * @return the parsed instant, not null + * @throws DateTimeException if the text cannot be parsed + */ + public static TAIInstant parse(CharSequence text) { + Objects.requireNonNull(text, "text"); + Matcher matcher = PARSER.matcher(text); + if (matcher.matches()) { + try { + long seconds = Long.parseLong(matcher.group(1)); + long nanos = Long.parseLong(matcher.group(2)); + return TAIInstant.ofTAISeconds(seconds, nanos); + } catch (NumberFormatException ex) { + throw new DateTimeParseException("The text could not be parsed", text, 0, ex); + } + } + throw new DateTimeParseException("The text could not be parsed", text, 0); + } + + //----------------------------------------------------------------------- + /** + * Constructs an instance. + * + * @param taiSeconds the number of TAI seconds from the epoch + * @param nanoOfSecond the nanoseconds within the second, from 0 to 999,999,999 + */ + private TAIInstant(long taiSeconds, int nanoOfSecond) { + super(); + this.seconds = taiSeconds; + this.nanos = nanoOfSecond; + } + + //----------------------------------------------------------------------- + /** + * Gets the number of seconds from the TAI epoch of 1958-01-01T00:00:00(TAI). + *

+ * The TAI second count is a simple incrementing count of seconds where + * second 0 is 1958-01-01T00:00:00(TAI). + * The nanosecond part of the day is returned by {@code getNanosOfSecond}. + * + * @return the seconds from the epoch of 1958-01-01T00:00:00(TAI) + */ + public long getTAISeconds() { + return seconds; + } + + /** + * Returns a copy of this {@code TAIInstant} with the number of seconds + * from the TAI epoch of 1958-01-01T00:00:00(TAI). + *

+ * The TAI second count is a simple incrementing count of seconds where + * second 0 is 1958-01-01T00:00:00(TAI). + * The nanosecond part of the day is returned by {@code getNanosOfSecond}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param taiSeconds the number of seconds from the epoch of 1958-01-01T00:00:00(TAI) + * @return a {@code TAIInstant} based on this instant with the requested second, not null + */ + public TAIInstant withTAISeconds(long taiSeconds) { + return ofTAISeconds(taiSeconds, nanos); + } + + /** + * 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 getTAISeconds}. + * + * @return the nanoseconds within the second, from 0 to 999,999,999 + */ + public int getNano() { + return nanos; + } + + /** + * Returns a copy of this {@code TAIInstant} with the nano-of-second value changed. + *

+ * The nanosecond-of-second value measures the total number of nanoseconds from + * the second returned by {@code getTAISeconds}. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanoOfSecond the nano-of-second, from 0 to 999,999,999 + * @return a {@code TAIInstant} based on this instant with the requested nano-of-second, not null + * @throws IllegalArgumentException if nanoOfSecond is out of range + */ + public TAIInstant withNano(int nanoOfSecond) { + if (nanoOfSecond < 0 || nanoOfSecond >= NANOS_PER_SECOND) { + throw new IllegalArgumentException("NanoOfSecond must be from 0 to 999,999,999"); + } + return ofTAISeconds(seconds, nanoOfSecond); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this instant with the specified duration added. + *

+ * The duration is added using simple addition of the seconds and nanoseconds + * in the duration to the seconds and nanoseconds of this instant. + * As a result, the duration is treated as being measured in TAI compatible seconds + * for the purpose of this method. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param duration the duration to add, not null + * @return a {@code TAIInstant} based on this instant with the duration added, not null + * @throws ArithmeticException if the calculation exceeds the supported range + */ + public TAIInstant plus(Duration duration) { + long secsToAdd = duration.getSeconds(); + int nanosToAdd = duration.getNano(); + if ((secsToAdd | nanosToAdd) == 0) { + return this; + } + long secs = Jdk8Methods.safeAdd(seconds, secsToAdd); + long nanoAdjustment = ((long) nanos) + nanosToAdd; // safe int+int + return ofTAISeconds(secs, nanoAdjustment); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this instant with the specified duration subtracted. + *

+ * The duration is subtracted using simple subtraction of the seconds and nanoseconds + * in the duration from the seconds and nanoseconds of this instant. + * As a result, the duration is treated as being measured in TAI compatible seconds + * for the purpose of this method. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param duration the duration to subtract, not null + * @return a {@code TAIInstant} based on this instant with the duration subtracted, not null + * @throws ArithmeticException if the calculation exceeds the supported range + */ + public TAIInstant minus(Duration duration) { + long secsToSubtract = duration.getSeconds(); + int nanosToSubtract = duration.getNano(); + if ((secsToSubtract | nanosToSubtract) == 0) { + return this; + } + long secs = Jdk8Methods.safeSubtract(seconds, secsToSubtract); + long nanoAdjustment = ((long) nanos) - nanosToSubtract; // safe int+int + return ofTAISeconds(secs, nanoAdjustment); + } + + //----------------------------------------------------------------------- + /** + * Returns the duration between this instant and the specified instant. + *

+ * This calculates the duration between this instant and another based on + * the TAI time-scale. Adding the duration to this instant using {@link #plus} + * will always result in an instant equal to the specified instant. + * + * @param taiInstant the instant to calculate the duration until, not null + * @return the duration until the specified instant, may be negative, not null + * @throws ArithmeticException if the calculation exceeds the supported range + */ + public Duration durationUntil(TAIInstant taiInstant) { + long durSecs = Jdk8Methods.safeSubtract(taiInstant.seconds, seconds); + long durNanos = taiInstant.nanos - nanos; + return Duration.ofSeconds(durSecs, durNanos); + } + + //----------------------------------------------------------------------- + /** + * Converts this instant to a {@code UTCInstant} using the system default + * leap second rules. + *

+ * This method converts this instant from the TAI to the UTC time-scale using the + * system default leap-second rules. This conversion does not lose information + * and the UTC instant may safely be converted back to a {@code TAIInstant}. + * + * @return a {@code UTCInstant} representing the same instant using the system leap second rules, not null + */ + public UTCInstant toUTCInstant() { + return UTCInstant.of(this, UTCRules.system()); + } + + /** + * Converts this instant to an {@code Instant} using the system default + * leap second rules. + *

+ * This method converts this instant from the TAI to the UTC-SLS time-scale using the + * system default leap-second rules to convert to UTC. + * This conversion will lose information around a leap second in accordance with UTC-SLS. + * Converting back to a {@code TAIInstant} may result in a slightly different instant. + * + * @return an {@code Instant} representing the best approximation of this instant, not null + */ + public Instant toInstant() { + return toUTCInstant().toInstant(); + } + + //----------------------------------------------------------------------- + /** + * Compares this instant to another based on the time-line. + * + * @param otherInstant the other instant to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + public int compareTo(TAIInstant otherInstant) { + int cmp = Long.compare(seconds, otherInstant.seconds); + if (cmp != 0) { + return cmp; + } + return nanos - otherInstant.nanos; + } + + //----------------------------------------------------------------------- + /** + * Checks if this instant is equal to the specified {@code TAIInstant}. + * + * @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 TAIInstant) { + TAIInstant other = (TAIInstant) 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() { + // TODO: Evaluate hash code + return ((int) (seconds ^ (seconds >>> 32))) + 51 * nanos; + } + + //----------------------------------------------------------------------- + /** + * A string representation of this instant. + *

+ * The string is formatted as {@code {seconds).(nanosOfSecond}s(TAI)}. + * At least one second digit will be present. + * The nanoseconds will always be nine digits. + * + * @return a representation of this instant, not null + */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(seconds); + int pos = buf.length(); + buf.append(nanos + NANOS_PER_SECOND); + buf.setCharAt(pos, '.'); + buf.append("s(TAI)"); + return buf.toString(); + } + +} diff --git a/src/main/java/org/threeten/extra/scale/TimeSource.java b/src/main/java/org/threeten/extra/scale/TimeSource.java new file mode 100644 index 0000000..367d4ff --- /dev/null +++ b/src/main/java/org/threeten/extra/scale/TimeSource.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra.scale; + +import org.threeten.bp.Clock; +import org.threeten.bp.Instant; + +/** + * A clock that provides the current UTC or TAI instant. + *

+ * This clock differs from {@link Clock} in providing access to the current instant + * in the UTC and TAI time-scales. However, there is currently no implementation that + * provides accurate UTC or TAI. + * + *

Specification for implementors

+ * This abstract class must be implemented with care to ensure other classes in + * the framework 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. + *

+ * Subclass implementations should implement {@code Serializable} wherever possible. + * They should also be immutable and thread-safe, implementing {@code equals()}, + * {@code hashCode()} and {@code toString()} based on their state. + */ +public interface TimeSource { + + /** + * Gets the current {@code Instant}. + *

+ * The instant returned by this method will vary according to the implementation. + * For example, the time-source returned by {@link #system()} will return + * an instant based on {@link System#currentTimeMillis()}. + * + * @return the current {@code Instant} from this time-source, not null + * @throws RuntimeException if the instant cannot be obtained, not thrown by most implementations + */ + public abstract Instant instant(); + + /** + * Gets the current {@code UTCInstant}. + *

+ * The UTC time-scale is the current world civil time and includes leap seconds. + * An accurate implementation of this interface will return the correct UTC instant. + * + * @return the current {@code UTCInstant} from this time-source, not null + * @throws RuntimeException if the instant cannot be obtained, not thrown by most implementations + */ + public abstract UTCInstant utcInstant(); + + /** + * Gets the current {@code TAIInstant}. + *

+ * The TAI time-scale is a simple incrementing number of seconds from the TAI epoch of 1958-01-01(TAI). + * It ignores all human concepts of time such as days. + * An accurate implementation of this interface will return the correct TAI instant. + * + * @return the current {@code TAIInstant} from this time-source, not null + * @throws RuntimeException if the instant cannot be obtained, not thrown by most implementations + */ + public abstract TAIInstant taiInstant(); + +} diff --git a/src/main/java/org/threeten/extra/scale/UTCInstant.java b/src/main/java/org/threeten/extra/scale/UTCInstant.java new file mode 100644 index 0000000..7602390 --- /dev/null +++ b/src/main/java/org/threeten/extra/scale/UTCInstant.java @@ -0,0 +1,528 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra.scale; + +import java.io.Serializable; + +import org.threeten.bp.Duration; +import org.threeten.bp.Instant; +import org.threeten.bp.LocalDate; +import org.threeten.bp.temporal.JulianFields; + +/** + * An instantaneous point on the time-line measured in the UTC time-scale + * with leap seconds. + *

+ * Most of the Java time classes work on the assumption that the time-line is simple, + * there are no leap-seconds and there are always 24 * 60 * 60 seconds in a day. + * However, the Earth's rotation is not straightforward, and a solar day does not match + * this definition. + *

+ * This class is an alternative representation based on the UTC time-scale which + * includes leap-seconds. Leap-seconds are additional seconds that are inserted into the + * year-month-day-hour-minute-second time-line in order to keep UTC in line with the solar day. + * When a leap second occurs, an accurate clock will show the time {@code 23:59:60} just before midnight. + *

+ * Leap-seconds are announced in advance, typically at least six months. + * The {@link UTCRules} class models which dates have leap-seconds. + * Alternative implementations of the rules may be supplied. + *

+ * The default rules implementation fixes the start point of UTC as 1972. + * This date was chosen as UTC was more complex before 1972. + *

+ * The duration between two points on the UTC time-scale is calculated solely using this class. + * Do not use the {@code between} method on {@code Duration} as that will lose information. + * Instead use {@link #durationUntil(UTCInstant)} on this class. + *

+ * It is intended that most applications will use the {@code Instant} class + * which uses the UTC-SLS mapping from UTC to guarantee 86400 seconds per day. + * Specialist applications with access to an accurate time-source may find this class useful. + * + *

Time-scale

+ *

+ * The length of the solar day is the standard way that humans measure time. + * As the Earth's rotation changes, the length of the day varies. + * In general, a solar day is slightly longer than 86400 SI seconds. + * The actual length is not predictable and can only be determined by measurement. + * The UT1 time-scale captures these measurements. + *

+ * 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. + * If it is removed, then the relevant date will have no time of {@code 23:59:59}. + * If it is added, then the relevant date will have an extra second of {@code 23:59:60}. + *

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

+ * This class may be used for instants in the far past and far future. + * Since some instants will be prior to 1972, it is not strictly an implementation of UTC. + * Instead, it is a proleptic time-scale based on UTC and equivalent to it since 1972. + * Prior to 1972, the default rules fix the UTC-TAI offset at 10 seconds. + * While not historically accurate, it is a simple, easy definition, suitable for this library. + *

+ * The standard Java epoch of {@code 1970-01-01} is prior to the introduction of whole leap-seconds into UTC in 1972. + * As such, the Time Framework for Java needs to define what the 1970 epoch actually means. + * The chosen definition follows the UTC definition given above, such that {@code 1970-01-01} is 10 seconds + * offset from TAI. + * + *

Specification for implementors

+ * This class is immutable and thread-safe. + */ +public final class UTCInstant + implements Comparable, Serializable { + // does not implement InstantProvider as that would enable methods like + // Duration.between which gives the wrong answer due to lossy conversion + + /** + * Constant for seconds per day. + */ + private static final long SECS_PER_DAY = 24 * 60 * 60; + /** + * Constant for nanos per second. + */ + private static final long NANOS_PER_SECOND = 1000000000; + /** + * Serialization version. + */ + private static final long serialVersionUID = 2600294095511836210L; + + /** + * The Modified Julian Day, from the epoch of 1858-11-17. + */ + private final long mjDay; + /** + * The number of nanoseconds, later along the time-line, from the MJD field. + * This is always positive and includes leap seconds. + */ + private final long nanoOfDay; + /** + * The leap second rules. + */ + private final UTCRules rules; + + //----------------------------------------------------------------------- + /** + * Obtains an instance of {@code UTCInstant} from a Modified Julian Day with + * a nanosecond fraction of second using the system default leap second rules. + *

+ * This factory creates an instance of a UTC instant. + * The nanosecond of day value includes any leap second and has a valid range from + * {@code 0} to {@code 86,400,000,000,000 - 1} on days other than leap-second-days + * and other lengths on leap-second-days. + *

+ * The nanosecond value must be positive even for negative values of Modified + * Julian Days. One nanosecond before Modified Julian Day zero will be + * {@code -1} days and the maximum nanosecond value. + * + * @param mjDay the date as a Modified Julian Day (number of days from the epoch of 1858-11-17) + * @param nanoOfDay the nanoseconds within the day, including leap seconds + * @return the UTC instant, not null + * @throws IllegalArgumentException if nanoOfDay is out of range + */ + public static UTCInstant ofModifiedJulianDay(long mjDay, long nanoOfDay) { + return ofModifiedJulianDay(mjDay, nanoOfDay, UTCRules.system()); + } + + /** + * Obtains an instance of {@code UTCInstant} from a Modified Julian Day with + * a nanosecond fraction of second using the specified leap second rules. + *

+ * This factory creates an instance of a UTC instant. + * The nanosecond of day value includes any leap second and has a valid range from + * {@code 0} to {@code 86,400,000,000,000 - 1} on days other than leap-second-days + * and other lengths on leap-second-days. + *

+ * The nanosecond value must be positive even for negative values of Modified + * Julian Days. One nanosecond before Modified Julian Day zero will be + * {@code -1} days and the maximum nanosecond value. + * + * @param mjDay the date as a Modified Julian Day (number of days from the epoch of 1858-11-17) + * @param nanoOfDay the nanoseconds within the day, including leap seconds + * @return the UTC instant, not null + * @throws IllegalArgumentException if nanoOfDay is out of range + */ + public static UTCInstant ofModifiedJulianDay(long mjDay, long nanoOfDay, UTCRules rules) { + long leapSecs = rules.getLeapSecondAdjustment(mjDay); + long maxNanos = (SECS_PER_DAY + leapSecs) * NANOS_PER_SECOND; + if (nanoOfDay < 0 || nanoOfDay >= maxNanos) { + throw new IllegalArgumentException("Nanosecond-of-day must be between 0 and " + maxNanos + " on date " + mjDay); + } + return new UTCInstant(mjDay, nanoOfDay, rules); + } + + /** + * Obtains an instance of {@code UTCInstant} from a provider of instants + * using the system default leap second rules. + *

+ * This method converts from the UTC-SLS to the UTC time-scale using the + * system default leap-second rules. This conversion will lose information + * around a leap second in accordance with UTC-SLS. + * Converting back to an {@code Instant} may result in a slightly different instant. + * + * @param instant the instant to convert, not null + * @return the UTC instant, not null + */ + public static UTCInstant of(Instant instant) { + return of(instant, UTCRules.system()); + } + + /** + * Obtains an instance of {@code UTCInstant} from a provider of instants + * using the specified leap second rules. + *

+ * This method converts from the UTC-SLS to the UTC time-scale using the + * specified leap-second rules. This conversion will lose information + * around a leap second in accordance with UTC-SLS. + * Converting back to an {@code Instant} may result in a slightly different instant. + * + * @param instant the instant to convert, not null + * @param rules the leap second rules, not null + * @return the UTC instant, not null + */ + public static UTCInstant of(Instant instant, UTCRules rules) { + return rules.convertToUTC(instant); + } + + /** + * Obtains an instance of {@code UTCInstant} from a TAI instant + * using the system default leap second rules. + *

+ * This method converts from the TAI to the UTC time-scale using the + * system default leap-second rules. This conversion does not lose information + * and the UTC instant may safely be converted back to a {@code TAIInstant}. + * + * @param taiInstant the TAI instant to convert, not null + * @return the UTC instant, not null + */ + public static UTCInstant of(TAIInstant taiInstant) { + return of(taiInstant, UTCRules.system()); + } + + /** + * Obtains an instance of {@code UTCInstant} from a TAI instant + * using the specified leap second rules. + *

+ * This method converts from the TAI to the UTC time-scale using the + * specified leap-second rules. This conversion does not lose information + * and the UTC instant may safely be converted back to a {@code TAIInstant}. + * + * @param taiInstant the TAI instant to convert, not null + * @param rules the leap second rules, not null + * @return the UTC instant, not null + */ + public static UTCInstant of(TAIInstant taiInstant, UTCRules rules) { + return rules.convertToUTC(taiInstant); + } + + //----------------------------------------------------------------------- + /** + * Constructs an instance. + * + * @param mjDay the date as a Modified Julian Day (number of days from the epoch of 1858-11-17) + * @param nanoOfDay the nanoseconds within the day, including leap seconds + * @param rules the leap second rules, not null + */ + private UTCInstant(long mjDay, long nanoOfDay, UTCRules rules) { + super(); + this.mjDay = mjDay; + this.nanoOfDay = nanoOfDay; + this.rules = rules; + } + + //----------------------------------------------------------------------- + /** + * Gets the leap second rules defining when leap seconds occur. + * + * @return the leap seconds rules + */ + public UTCRules getRules() { + return rules; + } + + /** + * Gets the Modified Julian Day (MJD). + *

+ * The Modified Julian Day count is a simple incrementing count of days + * where day 0 is 1858-11-17. + * The nanosecond part of the day is returned by {@code getNanosOfDay}. + * The day varies in length, being one second longer on a leap day. + * + * @return the Modified Julian Day based on the epoch 1858-11-17 + */ + public long getModifiedJulianDay() { + return mjDay; + } + + /** + * Returns a copy of this {@code UTCInstant} with the Modified Julian Day (MJD) altered. + *

+ * The Modified Julian Day count is a simple incrementing count of days + * where day 0 is 1858-11-17. + * The nanosecond part of the day is returned by {@code getNanosOfDay}. + * The day varies in length, being one second longer on a leap day. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param mjDay the date as a Modified Julian Day (number of days from the epoch of 1858-11-17) + * @return a {@code UTCInstant} based on this instant with the requested day, not null + * @throws IllegalArgumentException if nanoOfDay becomes invalid + */ + public UTCInstant withModifiedJulianDay(long mjDay) { + return ofModifiedJulianDay(mjDay, nanoOfDay, rules); + } + + /** + * Gets the number of nanoseconds, later along the time-line, from the start + * of the Modified Julian Day. + *

+ * The nanosecond-of-day value measures the total number of nanoseconds within + * the day from the start of the day returned by {@code getModifiedJulianDay}. + * This value will include any additional leap seconds. + * + * @return the nanoseconds within the day, including leap seconds + */ + public long getNanoOfDay() { + return nanoOfDay; + } + + /** + * Returns a copy of this {@code UTCInstant} with the nano-of-day altered. + *

+ * The nanosecond-of-day value measures the total number of nanoseconds within + * the day from the start of the day returned by {@code getModifiedJulianDay}. + * This value will include any additional leap seconds. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param nanoOfDay the nanoseconds within the day, including leap seconds + * @return a {@code UTCInstant} based on this instant with the requested nano-of-day, not null + * @throws IllegalArgumentException if the nanoOfDay value is invalid + */ + public UTCInstant withNanoOfDay(long nanoOfDay) { + return ofModifiedJulianDay(mjDay, nanoOfDay, rules); + } + + //----------------------------------------------------------------------- + /** + * Checks if the instant is within a leap second. + *

+ * This method returns true when an accurate clock would return a seconds + * field of 60. + * + * @return true if this instant is within a leap second + */ + public boolean isLeapSecond() { + return nanoOfDay > SECS_PER_DAY * NANOS_PER_SECOND; + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this instant with the specified duration added. + *

+ * The duration is added using simple addition of the seconds and nanoseconds + * in the duration to the seconds and nanoseconds of this instant. + * As a result, the duration is treated as being measured in TAI compatible seconds + * for the purpose of this method. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param duration the duration to add, not null + * @return a {@code UTCInstant} with the duration added, not null + * @throws ArithmeticException if the calculation exceeds the supported range + */ + public UTCInstant plus(Duration duration) { + return UTCInstant.of(toTAIInstant().plus(duration), rules); + } + + //----------------------------------------------------------------------- + /** + * Returns a copy of this instant with the specified duration subtracted. + *

+ * The duration is subtracted using simple subtraction of the seconds and nanoseconds + * in the duration from the seconds and nanoseconds of this instant. + * As a result, the duration is treated as being measured in TAI compatible seconds + * for the purpose of this method. + *

+ * This instance is immutable and unaffected by this method call. + * + * @param duration the duration to subtract, not null + * @return a {@code UTCInstant} with the duration subtracted, not null + * @throws ArithmeticException if the calculation exceeds the supported range + */ + public UTCInstant minus(Duration duration) { + return UTCInstant.of(toTAIInstant().minus(duration), rules); + } + + //----------------------------------------------------------------------- + /** + * Returns the duration between this instant and the specified instant. + *

+ * This calculates the duration between this instant and another based on + * the UTC time-scale. Any leap seconds that occur will be included in the duration. + * Adding the duration to this instant using {@link #plus} will always result + * in an instant equal to the specified instant. + * + * @param utcInstant the instant to calculate the duration until, not null + * @return the duration until the specified instant, may be negative, not null + * @throws ArithmeticException if the calculation exceeds the supported range + */ + public Duration durationUntil(UTCInstant utcInstant) { + TAIInstant thisTAI = toTAIInstant(); + TAIInstant otherTAI = utcInstant.toTAIInstant(); + return thisTAI.durationUntil(otherTAI); + } + + //----------------------------------------------------------------------- + /** + * Converts this instant to a {@code TAIInstant} using the stored + * leap second rules. + *

+ * This method converts from the UTC to the TAI time-scale using the stored leap-second rules. + * Conversion to a {@code TAIInstant} retains the same point on the time-line + * but loses the stored rules. If the TAI instant is converted back to a UTC instant + * with different or updated rules then the calculated UTC instant may be different. + * + * @return a {@code TAIInstant} representing the same instant, not null + * @throws ArithmeticException if the calculation exceeds the supported range + */ + public TAIInstant toTAIInstant() { + return rules.convertToTAI(this); + } + + /** + * Converts this instant to an {@code Instant} using the system default + * leap second rules. + *

+ * This method converts this instant from the UTC to the UTC-SLS time-scale using the + * stored leap-second rules. + * This conversion will lose information around a leap second in accordance with UTC-SLS. + * Converting back to a {@code UTCInstant} may result in a slightly different instant. + * + * @return an {@code Instant} representing the best approximation of this instant, not null + * @throws ArithmeticException if the calculation exceeds the supported range + */ + public Instant toInstant() { + return rules.convertToInstant(this); + } + + //----------------------------------------------------------------------- + /** + * Compares this instant to another based on the time-line, ignoring the rules. + *

+ * The comparison is based on the positions on the time-line. + * Ignoring the rules makes this comparison inconsistent with equals. + * + * @param otherInstant the other instant to compare to, not null + * @return the comparator value, negative if less, positive if greater + */ + public int compareTo(UTCInstant otherInstant) { + int cmp = Long.compare(mjDay, otherInstant.mjDay); + if (cmp != 0) { + return cmp; + } + return Long.compare(nanoOfDay, otherInstant.nanoOfDay); + } + + //----------------------------------------------------------------------- + /** + * Checks if this instant is equal to the specified {@code UTCInstant}. + *

+ * The comparison is based on the positions on the time-line and the rules. + * This definition means that two instants representing the same instant on + * the time-line will differ if the rules differ. To compare the time-line + * instant, convert both instants to a {@code TAIInstant}. + * + * @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 UTCInstant) { + UTCInstant other = (UTCInstant) otherInstant; + return this.mjDay == other.mjDay && + this.nanoOfDay == other.nanoOfDay && + this.rules.equals(other.rules); + } + return false; + } + + /** + * Returns a hash code for this instant. + * + * @return a suitable hash code + */ + @Override + public int hashCode() { + // TODO: Evaluate hash code + return ((int) (mjDay ^ (mjDay >>> 32))) + 51 * ((int) (nanoOfDay ^ (nanoOfDay >>> 32))) + + rules.hashCode(); + } + + //----------------------------------------------------------------------- + /** + * A string representation of this instant. + *

+ * The string is formatted using ISO-8601. + * + * @return a representation of this instant, not null + */ + @Override + public String toString() { + LocalDate date = LocalDate.MAX.with(JulianFields.MODIFIED_JULIAN_DAY, mjDay); // TODO: capacity/import issues + StringBuilder buf = new StringBuilder(18); + int sod = (int) (nanoOfDay / NANOS_PER_SECOND); + int hourValue = sod / (60 * 60); + int minuteValue = (sod / 60) % 60; + int secondValue = sod % 60; + if (hourValue == 24) { + hourValue = 23; + minuteValue = 59; + secondValue += 60; + } + int nanoValue = (int) (nanoOfDay % NANOS_PER_SECOND); + buf.append(date).append('T') + .append(hourValue < 10 ? "0" : "").append(hourValue) + .append(minuteValue < 10 ? ":0" : ":").append(minuteValue) + .append(secondValue < 10 ? ":0" : ":").append(secondValue); + int pos = buf.length(); + buf.append(nanoValue + NANOS_PER_SECOND); + buf.setCharAt(pos, '.'); + buf.append("(UTC)"); + return buf.toString(); + } + +} diff --git a/src/main/java/org/threeten/extra/scale/UTCRules.java b/src/main/java/org/threeten/extra/scale/UTCRules.java new file mode 100644 index 0000000..8e6a107 --- /dev/null +++ b/src/main/java/org/threeten/extra/scale/UTCRules.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra.scale; + +import java.util.ConcurrentModificationException; + +import org.threeten.bp.Instant; +import org.threeten.bp.jdk8.Jdk8Methods; + +/** + * Rules defining the UTC time-scale, notably when leap seconds occur. + *

+ * This class defines the UTC time-scale including when leap seconds occur. + * Subclasses obtain the data from a suitable source, such as TZDB or GPS. + *

+ * The static methods on this class provide access to the system leap second rules. + * These are used by default. + * + *

Specification for implementors

+ * This is an abstract class and must be implemented with care + * to ensure other classes in the framework operate correctly. + * All implementations must be final, immutable and thread-safe. + * Subclasses should be {@code Serializable} wherever possible. + */ +public abstract class UTCRules { + + /** + * Constant for the offset from MJD day 0 to the Java Epoch of 1970-01-01: 40587. + */ + static final int OFFSET_MJD_EPOCH = 40587; + /** + * Constant for the offset from MJD day 0 to TAI day 0 of 1958-01-01: 36204. + */ + static final int OFFSET_MJD_TAI = 36204; + /** + * Constant for number of seconds per standard day: 86,400. + */ + static final long SECS_PER_DAY = 24L * 60L * 60L; + /** + * Constant for nanos per standard second: 1,000,000,000. + */ + static final long NANOS_PER_SECOND = 1000000000L; + + /** + * Gets the system default leap second rules. + *

+ * The system default rules are serializable, immutable and thread-safe. + * They will remain up to date as new leap seconds are added. + * + * @return the system rules, not null + */ + public static UTCRules system() { + return SystemUTCRules.INSTANCE; + } + + /** + * Adds a new leap second to the system default leap second rules. + *

+ * This method registers a new leap second with the system leap second rules. + * Once registered, there is no way to deregister the leap second. + *

+ * Calling this method is thread-safe. + * Its effects are immediately visible in all threads. + * Where possible, only call this method from a single thread to avoid the possibility of + * a {@code ConcurrentModificationException}. + *

+ * If the leap second being added matches a previous definition, then the method returns normally. + * If the date is before the last registered date and doesn't match, then an exception is thrown. + * + * @param mjDay the modified julian date that the leap second occurs at the end of + * @param leapAdjustment the leap seconds to add/remove at the end of the day, either -1 or 1 + * @throws IllegalArgumentException if the leap adjustment is invalid + * @throws IllegalArgumentException if the day is before or equal the last known leap second day + * and the definition does not match a previously registered leap + * @throws ConcurrentModificationException if another thread updates the rules at the same time + */ + public static void registerSystemLeapSecond(long mjDay, int leapAdjustment) { + SystemUTCRules.INSTANCE.registerLeapSecond(mjDay, leapAdjustment); + } + + //----------------------------------------------------------------------- + /** + * Creates an instance of the rules. + */ + protected UTCRules() { + } + + //----------------------------------------------------------------------- + /** + * The name of these rules. + * + * @return the name, not null + */ + public abstract String getName(); + + /** + * Gets the leap second adjustment on the specified date. + *

+ * The UTC standard restricts the adjustment to a day to {@code -1} or {@code 1}. + *

+ * Any leap seconds are added to, or removed from, the end of the specified date. + *

+ * NOTE: If the UTC specification is altered to allow multiple leap seconds at once, + * then the result of this method would change. + * + * @param mjDay the date as a Modified Julian Day (number of days from the epoch of 1858-11-17) + * @return the number of seconds added, or removed, from the date, either -1 or 1 + */ + public abstract int getLeapSecondAdjustment(long mjDay); + + /** + * Gets the offset to TAI on the specified date. + *

+ * The TAI offset starts at 10 in 1972 and varies from then on based on the + * dates of leap seconds. + * The offset will apply for the whole of the date. + * + * @param mjDay the date as a Modified Julian Day (number of days from the epoch of 1858-11-17) + * @return the TAI offset in seconds + */ + public abstract int getTAIOffset(long mjDay); + + /** + * Gets all known leap second dates. + *

+ * The dates are returned as Modified Julian Day values. + * The leap second is added to, or removed from, the end of the specified dates. + * The dates will be sorted from earliest to latest. + * + * @return an array of leap second dates expressed as Modified Julian Day values, not null + */ + public abstract long[] getLeapSecondDates(); + + //----------------------------------------------------------------------- + /** + * Converts a {@code UTCInstant} to a {@code TAIInstant}. + *

+ * This method converts from the UTC to the TAI time-scale using the + * leap-second rules of the implementation. + *

+ * The standard implementation uses {@code getTAIOffset}. + * + * @param utcInstant the UTC instant to convert, not null + * @return the converted TAI instant, not null + * @throws ArithmeticException if the capacity is exceeded + */ + protected TAIInstant convertToTAI(UTCInstant utcInstant) { + long mjd = utcInstant.getModifiedJulianDay(); + long nod = utcInstant.getNanoOfDay(); + long taiUtcDaySeconds = Jdk8Methods.safeMultiply(mjd - OFFSET_MJD_TAI, SECS_PER_DAY); + long taiSecs = Jdk8Methods.safeAdd(taiUtcDaySeconds, nod / NANOS_PER_SECOND + getTAIOffset(mjd)); + int nos = (int) (nod % NANOS_PER_SECOND); + return TAIInstant.ofTAISeconds(taiSecs, nos); + } + + /** + * Converts a {@code TAIInstant} to a {@code UTCInstant}. + *

+ * This method converts from the TAI to the UTC time-scale using the + * leap-second rules of the implementation. + * + * @param taiInstant the TAI instant to convert, not null + * @return the converted UTC instant, not null + * @throws ArithmeticException if the capacity is exceeded + */ + protected abstract UTCInstant convertToUTC(TAIInstant taiInstant); + + //----------------------------------------------------------------------- + /** + * Converts a {@code UTCInstant} to an {@code Instant}. + *

+ * This method converts from the UTC time-scale to one with 86400 seconds per day + * using the leap-second rules of the implementation. + *

+ * The standard implementation uses the UTC-SLS algorithm. + * Overriding this algorithm is possible, however doing so will conflict other parts + * of the specification. + *

+ * The algorithm calculates the UTC-SLS nanos-of-day {@code US} from the UTC nanos-of day {@code U}.
+ * Let {@code L = getLeapAdjustment(mjd)}.
+ * Let {@code B = 86400 + L - 1000}.
+ * Let {@code US = U - L * (U - B) / 1000}.
+ * Where the algorithm is applied while {@code U >= B}. + * + * @param utcInstant the UTC instant to convert, not null + * @return the converted instant, not null + * @throws ArithmeticException if the capacity is exceeded + */ + protected Instant convertToInstant(UTCInstant utcInstant) { + long mjd = utcInstant.getModifiedJulianDay(); + long utcNanos = utcInstant.getNanoOfDay(); + long epochDay = Jdk8Methods.safeSubtract(mjd, OFFSET_MJD_EPOCH); + long epochSec = Jdk8Methods.safeMultiply(epochDay, SECS_PER_DAY); + int leapAdj = getLeapSecondAdjustment(mjd); + long startSlsNanos = (SECS_PER_DAY + leapAdj - 1000) * NANOS_PER_SECOND; + long slsNanos = utcNanos; + if (leapAdj != 0 && utcNanos >= startSlsNanos) { + slsNanos = utcNanos - leapAdj * (utcNanos - startSlsNanos) / 1000; // apply UTC-SLS mapping + } + return Instant.ofEpochSecond(epochSec + slsNanos / NANOS_PER_SECOND, slsNanos % NANOS_PER_SECOND); + } + + /** + * Converts an {@code Instant} to a {@code UTCInstant}. + *

+ * This method converts from an instant with 86400 seconds per day to the UTC + * time-scale using the leap-second rules of the implementation. + *

+ * The standard implementation uses the UTC-SLS algorithm. + * Overriding this algorithm is possible, however doing so will conflict other parts + * of the specification. + *

+ * The algorithm calculates the UTC nanos-of-day {@code U} from the UTC-SLS nanos-of day {@code US}.
+ * Let {@code L = getLeapAdjustment(mjd)}.
+ * Let {@code B = 86400 + L - 1000}.
+ * Let {@code U = B + ((US - B) * 1000) / (1000 - L)}.
+ * Where the algorithm is applied while {@code US >= B}.
+ * (This algorithm has been tuned for integer arithmetic from the UTC-SLS specification.) + * + * @param instant the instant to convert, not null + * @return the converted UTC instant, not null + * @throws ArithmeticException if the capacity is exceeded + */ + protected UTCInstant convertToUTC(Instant instant) { + long epochDay = Jdk8Methods.floorDiv(instant.getEpochSecond(), SECS_PER_DAY); + long mjd = epochDay + OFFSET_MJD_EPOCH; + long slsNanos = Jdk8Methods.floorMod(instant.getEpochSecond(), SECS_PER_DAY) * NANOS_PER_SECOND + instant.getNano(); + int leapAdj = getLeapSecondAdjustment(mjd); + long startSlsNanos = (SECS_PER_DAY + leapAdj - 1000) * NANOS_PER_SECOND; + long utcNanos = slsNanos; + if (leapAdj != 0 && slsNanos >= startSlsNanos) { + utcNanos = startSlsNanos + ((slsNanos - startSlsNanos) * 1000) / (1000 - leapAdj); // apply UTC-SLS mapping + } + return UTCInstant.ofModifiedJulianDay(mjd, utcNanos, this); + } + + //----------------------------------------------------------------------- + /** + * A string representation of these rules. + * + * @return the string representation, not null + */ + @Override + public String toString() { + return "UTCRules[" + getName() + ']'; + } + +} diff --git a/src/test/java/org/threeten/extra/MockFieldNoValue.java b/src/test/java/org/threeten/extra/MockFieldNoValue.java new file mode 100644 index 0000000..e0b0d0d --- /dev/null +++ b/src/test/java/org/threeten/extra/MockFieldNoValue.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import static org.threeten.bp.temporal.ChronoUnit.MONTHS; +import static org.threeten.bp.temporal.ChronoUnit.WEEKS; + +import java.util.Map; + +import org.threeten.bp.DateTimeException; +import org.threeten.bp.temporal.Temporal; +import org.threeten.bp.temporal.TemporalAccessor; +import org.threeten.bp.temporal.TemporalField; +import org.threeten.bp.temporal.TemporalUnit; +import org.threeten.bp.temporal.ValueRange; + +/** + * Mock DateTimeField 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 int compare(TemporalAccessor dateTime1, TemporalAccessor dateTime2) { + return Long.compare(getFrom(dateTime1), getFrom(dateTime2)); + } + + //----------------------------------------------------------------------- + @Override + public boolean isSupportedBy(TemporalAccessor dateTime) { + return true; + } + + @Override + public ValueRange rangeRefinedBy(TemporalAccessor dateTime) { + return ValueRange.of(1, 20); + } + + @Override + public long getFrom(TemporalAccessor dateTime) { + throw new DateTimeException("Mock"); + } + + @Override + public R adjustInto(R dateTime, long newValue) { + throw new DateTimeException("Mock"); + } + + //----------------------------------------------------------------------- + @Override + public Map resolve(TemporalAccessor temporal, long value) { + return null; + } + +} diff --git a/src/test/java/org/threeten/extra/TestAmPm.java b/src/test/java/org/threeten/extra/TestAmPm.java new file mode 100644 index 0000000..e2e608d --- /dev/null +++ b/src/test/java/org/threeten/extra/TestAmPm.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.io.Serializable; +import java.util.Locale; + +import org.testng.annotations.Test; +import org.threeten.bp.DateTimeException; +import org.threeten.bp.LocalDate; +import org.threeten.bp.LocalTime; +import org.threeten.bp.format.TextStyle; +import org.threeten.bp.temporal.TemporalAccessor; + +/** + * Test AmPm. + */ +@Test +public class TestAmPm { + + //----------------------------------------------------------------------- + @Test(groups={"implementation"}) + public void test_interfaces() { + assertTrue(Enum.class.isAssignableFrom(AmPm.class)); + assertTrue(Serializable.class.isAssignableFrom(AmPm.class)); + assertTrue(Comparable.class.isAssignableFrom(AmPm.class)); + } + + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_int_singleton_equals() { + for (int i = 0; i <= 1; i++) { + AmPm test = AmPm.of(i); + assertEquals(test.getValue(), i); + } + } + + @Test(groups={"implementation"}) + public void test_factory_int_singleton_same() { + for (int i = 0; i <= 1; i++) { + AmPm test = AmPm.of(i); + assertEquals(test.getValue(), i); + assertSame(AmPm.of(i), test); + } + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_valueTooLow() { + AmPm.of(-1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_int_valueTooHigh() { + AmPm.of(2); + } + + //----------------------------------------------------------------------- + // from(TemporalAccessor) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_factory_TemporalAccessor() { + assertEquals(AmPm.from(LocalTime.of(8, 30)), AmPm.AM); + assertEquals(AmPm.from(LocalTime.of(17, 30)), AmPm.PM); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_factory_TemporalAccessor_invalid_noDerive() { + AmPm.from(LocalDate.of(2007, 7, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_factory_TemporalAccessor_null() { + AmPm.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // getDisplayName() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_getText() { + assertEquals(AmPm.AM.getDisplayName(TextStyle.SHORT, Locale.US), "AM"); + } + + @Test(expectedExceptions = NullPointerException.class, groups={"tck"}) + public void test_getText_nullStyle() { + AmPm.AM.getDisplayName(null, Locale.US); + } + + @Test(expectedExceptions = NullPointerException.class, groups={"tck"}) + public void test_getText_nullLocale() { + AmPm.AM.getDisplayName(TextStyle.FULL, null); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString() { + assertEquals(AmPm.AM.toString(), "AM"); + assertEquals(AmPm.PM.toString(), "PM"); + } + + //----------------------------------------------------------------------- + // generated methods + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_enum() { + assertEquals(AmPm.valueOf("AM"), AmPm.AM); + assertEquals(AmPm.values()[0], AmPm.AM); + } + +} diff --git a/src/test/java/org/threeten/extra/TestDayOfMonth.java b/src/test/java/org/threeten/extra/TestDayOfMonth.java new file mode 100644 index 0000000..0783e56 --- /dev/null +++ b/src/test/java/org/threeten/extra/TestDayOfMonth.java @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; +import static org.threeten.bp.temporal.ChronoField.DAY_OF_MONTH; + +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.lang.reflect.Field; +import java.lang.reflect.Modifier; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.threeten.bp.DateTimeException; +import org.threeten.bp.LocalDate; +import org.threeten.bp.LocalTime; +import org.threeten.bp.temporal.Temporal; +import org.threeten.bp.temporal.TemporalAccessor; +import org.threeten.bp.temporal.TemporalAdjuster; + +/** + * Test DayOfMonth. + */ +@Test +public class TestDayOfMonth { + + private static final int MAX_LENGTH = 31; + + @BeforeMethod + public void setUp() { + } + + //----------------------------------------------------------------------- + public void test_interfaces() { + assertTrue(Serializable.class.isAssignableFrom(DayOfMonth.class)); + assertTrue(Comparable.class.isAssignableFrom(DayOfMonth.class)); + assertTrue(TemporalAccessor.class.isAssignableFrom(DayOfMonth.class)); + assertTrue(TemporalAdjuster.class.isAssignableFrom(DayOfMonth.class)); + } + + public void test_serialization() throws IOException, ClassNotFoundException { + DayOfMonth test = DayOfMonth.of(1); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(test); + oos.close(); + + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream( + baos.toByteArray())); + assertEquals(ois.readObject(), test); + } + + public void test_immutable() { + Class cls = DayOfMonth.class; + assertTrue(Modifier.isPublic(cls.getModifiers())); + assertTrue(Modifier.isFinal(cls.getModifiers())); + Field[] fields = cls.getDeclaredFields(); + for (Field field : fields) { + 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()); + } + } + } + + //----------------------------------------------------------------------- + public void test_factory_int_singleton() { + for (int i = 1; i <= MAX_LENGTH; i++) { + DayOfMonth test = DayOfMonth.of(i); + assertEquals(test.getValue(), i); + assertEquals(DayOfMonth.of(i), test); + } + } + + @Test(expectedExceptions=DateTimeException.class) + public void test_factory_int_minuteTooLow() { + DayOfMonth.of(0); + } + + @Test(expectedExceptions=DateTimeException.class) + public void test_factory_int_hourTooHigh() { + DayOfMonth.of(32); + } + + //----------------------------------------------------------------------- + // from(TemporalAccessor) + //----------------------------------------------------------------------- + public void test_factory_TemporalAccessor_notLeapYear() { + LocalDate date = LocalDate.of(2007, 1, 1); + for (int i = 1; i <= 31; i++) { // Jan + assertEquals(DayOfMonth.from(date).getValue(), i); + date = date.plusDays(1); + } + for (int i = 1; i <= 28; i++) { // Feb + assertEquals(DayOfMonth.from(date).getValue(), i); + date = date.plusDays(1); + } + for (int i = 1; i <= 31; i++) { // Mar + assertEquals(DayOfMonth.from(date).getValue(), i); + date = date.plusDays(1); + } + for (int i = 1; i <= 30; i++) { // Apr + assertEquals(DayOfMonth.from(date).getValue(), i); + date = date.plusDays(1); + } + for (int i = 1; i <= 31; i++) { // May + assertEquals(DayOfMonth.from(date).getValue(), i); + date = date.plusDays(1); + } + for (int i = 1; i <= 30; i++) { // Jun + assertEquals(DayOfMonth.from(date).getValue(), i); + date = date.plusDays(1); + } + for (int i = 1; i <= 31; i++) { // Jul + assertEquals(DayOfMonth.from(date).getValue(), i); + date = date.plusDays(1); + } + for (int i = 1; i <= 31; i++) { // Aug + assertEquals(DayOfMonth.from(date).getValue(), i); + date = date.plusDays(1); + } + for (int i = 1; i <= 30; i++) { // Sep + assertEquals(DayOfMonth.from(date).getValue(), i); + date = date.plusDays(1); + } + for (int i = 1; i <= 31; i++) { // Oct + assertEquals(DayOfMonth.from(date).getValue(), i); + date = date.plusDays(1); + } + for (int i = 1; i <= 30; i++) { // Nov + assertEquals(DayOfMonth.from(date).getValue(), i); + date = date.plusDays(1); + } + for (int i = 1; i <= 31; i++) { // Dec + assertEquals(DayOfMonth.from(date).getValue(), i); + date = date.plusDays(1); + } + } + + public void test_factory_TemporalAccessor_leapYear() { + LocalDate date = LocalDate.of(2008, 1, 1); + for (int i = 1; i <= 31; i++) { // Jan + assertEquals(DayOfMonth.from(date).getValue(), i); + date = date.plusDays(1); + } + for (int i = 1; i <= 29; i++) { // Feb + assertEquals(DayOfMonth.from(date).getValue(), i); + date = date.plusDays(1); + } + for (int i = 1; i <= 31; i++) { // Mar + assertEquals(DayOfMonth.from(date).getValue(), i); + date = date.plusDays(1); + } + } + + @Test(expectedExceptions=DateTimeException.class) + public void test_factory_TemporalAccessor_noDerive() { + DayOfMonth.from(LocalTime.NOON); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_factory_TemporalAccessor_null() { + DayOfMonth.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + public void test_getField() { + assertSame(DayOfMonth.of(1).getField(), DAY_OF_MONTH); + } + + //----------------------------------------------------------------------- + // adjustInto() + //----------------------------------------------------------------------- + public void test_adjustDate() { + LocalDate base = LocalDate.of(2007, 1, 1); + LocalDate expected = base; + for (int i = 1; i <= MAX_LENGTH; i++) { // Jan + Temporal result = DayOfMonth.of(i).adjustInto(base); + assertEquals(result, expected); + expected = expected.plusDays(1); + } + } + + @Test(expectedExceptions=DateTimeException.class) + public void test_adjustDate_april31() { + LocalDate base = LocalDate.of(2007, 4, 1); + DayOfMonth test = DayOfMonth.of(31); + test.adjustInto(base); + } + + @Test(expectedExceptions=DateTimeException.class) + public void test_adjustDate_february29_notLeapYear() { + LocalDate base = LocalDate.of(2007, 2, 1); + DayOfMonth test = DayOfMonth.of(29); + test.adjustInto(base); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_adjustDate_nullLocalDate() { + LocalDate date = null; + DayOfMonth test = DayOfMonth.of(1); + test.adjustInto(date); + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + public void test_compareTo() { + for (int i = 1; i <= MAX_LENGTH; i++) { + DayOfMonth a = DayOfMonth.of(i); + for (int j = 1; j <= MAX_LENGTH; j++) { + DayOfMonth b = DayOfMonth.of(j); + if (i < j) { + assertEquals(a.compareTo(b), -1); + assertEquals(b.compareTo(a), 1); + } else if (i > j) { + assertEquals(a.compareTo(b), 1); + assertEquals(b.compareTo(a), -1); + } else { + assertEquals(a.compareTo(b), 0); + assertEquals(b.compareTo(a), 0); + } + } + } + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_compareTo_nullDayOfMonth() { + DayOfMonth doy = null; + DayOfMonth test = DayOfMonth.of(1); + test.compareTo(doy); + } + + //----------------------------------------------------------------------- + // equals() / hashCode() + //----------------------------------------------------------------------- + public void test_equals() { + for (int i = 1; i <= MAX_LENGTH; i++) { + DayOfMonth a = DayOfMonth.of(i); + for (int j = 1; j <= MAX_LENGTH; j++) { + DayOfMonth b = DayOfMonth.of(j); + assertEquals(a.equals(b), i == j); + assertEquals(a.hashCode() == b.hashCode(), i == j); + } + } + } + + public void test_equals_nullDayOfMonth() { + DayOfMonth doy = null; + DayOfMonth test = DayOfMonth.of(1); + assertEquals(test.equals(doy), false); + } + + public void test_equals_incorrectType() { + DayOfMonth test = DayOfMonth.of(1); + assertEquals(test.equals("Incorrect type"), false); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + public void test_toString() { + for (int i = 1; i <= MAX_LENGTH; i++) { + DayOfMonth a = DayOfMonth.of(i); + assertEquals(a.toString(), "DayOfMonth=" + i); + } + } + +} diff --git a/src/test/java/org/threeten/extra/TestDays.java b/src/test/java/org/threeten/extra/TestDays.java new file mode 100644 index 0000000..aa69387 --- /dev/null +++ b/src/test/java/org/threeten/extra/TestDays.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +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 static org.threeten.bp.temporal.ChronoUnit.DAYS; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import org.testng.annotations.Test; +import org.threeten.bp.temporal.TemporalUnit; + +/** + * Test class. + */ +@Test +public class TestDays { + + //----------------------------------------------------------------------- + public void test_isSerializable() { + assertTrue(Serializable.class.isAssignableFrom(Days.class)); + } + + //----------------------------------------------------------------------- + public void test_factoryZeroSingleton() { + assertSame(Days.ZERO, Days.of(0)); + assertSame(Days.ZERO, Days.of(0)); + assertEquals(0, Days.ZERO.getAmount()); + } + + //----------------------------------------------------------------------- + public void test_factoryGetDays() { + assertEquals(1, Days.of(1).getAmount()); + assertEquals(2, Days.of(2).getAmount()); + assertEquals(Integer.MAX_VALUE, Days.of(Integer.MAX_VALUE).getAmount()); + assertEquals(-1, Days.of(-1).getAmount()); + assertEquals(-2, Days.of(-2).getAmount()); + assertEquals(Integer.MIN_VALUE, Days.of(Integer.MIN_VALUE).getAmount()); + } + + //----------------------------------------------------------------------- + public void test_deserializationSingleton() throws Exception { + Days orginal = Days.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); + Days ser = (Days) in.readObject(); + assertSame(Days.ZERO, ser); + } + + //----------------------------------------------------------------------- + public void test_compareTo() { + Days test5 = Days.of(5); + Days test6 = Days.of(6); + assertEquals(0, test5.compareTo(test5)); + assertEquals(-1, test5.compareTo(test6)); + assertEquals(1, test6.compareTo(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_compareTo_null() { + Days test5 = Days.of(5); + test5.compareTo(null); + } + + //----------------------------------------------------------------------- + public void test_isGreaterThan() { + Days test5 = Days.of(5); + Days test6 = Days.of(6); + assertEquals(false, test5.isGreaterThan(test5)); + assertEquals(false, test5.isGreaterThan(test6)); + assertEquals(true, test6.isGreaterThan(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_isGreaterThan_null() { + Days test5 = Days.of(5); + test5.isGreaterThan(null); + } + + //----------------------------------------------------------------------- + public void test_isLessThan() { + Days test5 = Days.of(5); + Days test6 = Days.of(6); + assertEquals(false, test5.isLessThan(test5)); + assertEquals(true, test5.isLessThan(test6)); + assertEquals(false, test6.isLessThan(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_isLessThan_null() { + Days test5 = Days.of(5); + test5.isLessThan(null); + } + + //----------------------------------------------------------------------- + public void test_equals() { + Days test5 = Days.of(5); + Days test6 = Days.of(6); + assertEquals(true, test5.equals(test5)); + assertEquals(false, test5.equals(test6)); + assertEquals(false, test6.equals(test5)); + } + + public void test_equals_null() { + Days test5 = Days.of(5); + assertEquals(false, test5.equals(null)); + } + + public void test_equals_otherClass() { + Days test5 = Days.of(5); + assertEquals(false, test5.equals("")); + } + + //----------------------------------------------------------------------- + public void test_hashCode() { + Days test5 = Days.of(5); + Days test6 = Days.of(6); + assertEquals(true, test5.hashCode() == test5.hashCode()); + assertEquals(false, test5.hashCode() == test6.hashCode()); + } + + //----------------------------------------------------------------------- + public void test_getUnit() { + TemporalUnit unit = Days.of(5).getUnit(); + assertNotNull(unit); + assertEquals(unit, DAYS); + } + + //----------------------------------------------------------------------- + public void test_plus() { + Days test5 = Days.of(5); + assertEquals(Days.of(5), test5.plus(0)); + assertEquals(Days.of(7), test5.plus(2)); + assertEquals(Days.of(3), test5.plus(-2)); + assertEquals(Days.of(Integer.MAX_VALUE), Days.of(Integer.MAX_VALUE - 1).plus(1)); + assertEquals(Days.of(Integer.MIN_VALUE), Days.of(Integer.MIN_VALUE + 1).plus(-1)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_overflowTooBig() { + Days.of(Integer.MAX_VALUE - 1).plus(2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_overflowTooSmall() { + Days.of(Integer.MIN_VALUE + 1).plus(-2); + } + + //----------------------------------------------------------------------- + public void test_plus_Days() { + Days test5 = Days.of(5); + assertEquals(Days.of(5), test5.plus(Days.of(0))); + assertEquals(Days.of(7), test5.plus(Days.of(2))); + assertEquals(Days.of(3), test5.plus(Days.of(-2))); + assertEquals(Days.of(Integer.MAX_VALUE), + Days.of(Integer.MAX_VALUE - 1).plus(Days.of(1))); + assertEquals(Days.of(Integer.MIN_VALUE), + Days.of(Integer.MIN_VALUE + 1).plus(Days.of(-1))); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_Days_overflowTooBig() { + Days.of(Integer.MAX_VALUE - 1).plus(Days.of(2)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_Days_overflowTooSmall() { + Days.of(Integer.MIN_VALUE + 1).plus(Days.of(-2)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_plus_Days_null() { + Days.of(Integer.MIN_VALUE + 1).plus(null); + } + + //----------------------------------------------------------------------- + public void test_minus() { + Days test5 = Days.of(5); + assertEquals(Days.of(5), test5.minus(0)); + assertEquals(Days.of(3), test5.minus(2)); + assertEquals(Days.of(7), test5.minus(-2)); + assertEquals(Days.of(Integer.MAX_VALUE), Days.of(Integer.MAX_VALUE - 1).minus(-1)); + assertEquals(Days.of(Integer.MIN_VALUE), Days.of(Integer.MIN_VALUE + 1).minus(1)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_overflowTooBig() { + Days.of(Integer.MAX_VALUE - 1).minus(-2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_overflowTooSmall() { + Days.of(Integer.MIN_VALUE + 1).minus(2); + } + + //----------------------------------------------------------------------- + public void test_minus_Days() { + Days test5 = Days.of(5); + assertEquals(Days.of(5), test5.minus(Days.of(0))); + assertEquals(Days.of(3), test5.minus(Days.of(2))); + assertEquals(Days.of(7), test5.minus(Days.of(-2))); + assertEquals(Days.of(Integer.MAX_VALUE), + Days.of(Integer.MAX_VALUE - 1).minus(Days.of(-1))); + assertEquals(Days.of(Integer.MIN_VALUE), + Days.of(Integer.MIN_VALUE + 1).minus(Days.of(1))); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_Days_overflowTooBig() { + Days.of(Integer.MAX_VALUE - 1).minus(Days.of(-2)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_Days_overflowTooSmall() { + Days.of(Integer.MIN_VALUE + 1).minus(Days.of(2)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_minus_Days_null() { + Days.of(Integer.MIN_VALUE + 1).minus(null); + } + + //----------------------------------------------------------------------- + public void test_multipliedBy() { + Days test5 = Days.of(5); + assertEquals(Days.of(0), test5.multipliedBy(0)); + assertEquals(Days.of(5), test5.multipliedBy(1)); + assertEquals(Days.of(10), test5.multipliedBy(2)); + assertEquals(Days.of(15), test5.multipliedBy(3)); + assertEquals(Days.of(-15), test5.multipliedBy(-3)); + } + + public void test_multipliedBy_negate() { + Days test5 = Days.of(5); + assertEquals(Days.of(-15), test5.multipliedBy(-3)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_multipliedBy_overflowTooBig() { + Days.of(Integer.MAX_VALUE / 2 + 1).multipliedBy(2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_multipliedBy_overflowTooSmall() { + Days.of(Integer.MIN_VALUE / 2 - 1).multipliedBy(2); + } + + //----------------------------------------------------------------------- + public void test_dividedBy() { + Days test12 = Days.of(12); + assertEquals(Days.of(12), test12.dividedBy(1)); + assertEquals(Days.of(6), test12.dividedBy(2)); + assertEquals(Days.of(4), test12.dividedBy(3)); + assertEquals(Days.of(3), test12.dividedBy(4)); + assertEquals(Days.of(2), test12.dividedBy(5)); + assertEquals(Days.of(2), test12.dividedBy(6)); + assertEquals(Days.of(-4), test12.dividedBy(-3)); + } + + public void test_dividedBy_negate() { + Days test12 = Days.of(12); + assertEquals(Days.of(-4), test12.dividedBy(-3)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_dividedBy_divideByZero() { + Days.of(1).dividedBy(0); + } + + //----------------------------------------------------------------------- + public void test_negated() { + assertEquals(Days.of(0), Days.of(0).negated()); + assertEquals(Days.of(-12), Days.of(12).negated()); + assertEquals(Days.of(12), Days.of(-12).negated()); + assertEquals(Days.of(-Integer.MAX_VALUE), Days.of(Integer.MAX_VALUE).negated()); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_negated_overflow() { + Days.of(Integer.MIN_VALUE).negated(); + } + + //----------------------------------------------------------------------- + public void test_toString() { + Days test5 = Days.of(5); + assertEquals("P5D", test5.toString()); + Days testM1 = Days.of(-1); + assertEquals("P-1D", testM1.toString()); + } + +} diff --git a/src/test/java/org/threeten/extra/TestHours.java b/src/test/java/org/threeten/extra/TestHours.java new file mode 100644 index 0000000..b0ef14a --- /dev/null +++ b/src/test/java/org/threeten/extra/TestHours.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +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 static org.threeten.bp.temporal.ChronoUnit.HOURS; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import org.testng.annotations.Test; +import org.threeten.bp.temporal.TemporalUnit; + +/** + * Test class. + */ +@Test +public class TestHours { + + //----------------------------------------------------------------------- + public void test_isSerializable() { + assertTrue(Serializable.class.isAssignableFrom(Hours.class)); + } + + //----------------------------------------------------------------------- + public void test_factoryZeroSingleton() { + assertSame(Hours.ZERO, Hours.of(0)); + assertSame(Hours.ZERO, Hours.of(0)); + assertEquals(0, Hours.ZERO.getAmount()); + } + + //----------------------------------------------------------------------- + public void test_factoryGetHours() { + assertEquals(1, Hours.of(1).getAmount()); + assertEquals(2, Hours.of(2).getAmount()); + assertEquals(Integer.MAX_VALUE, Hours.of(Integer.MAX_VALUE).getAmount()); + assertEquals(-1, Hours.of(-1).getAmount()); + assertEquals(-2, Hours.of(-2).getAmount()); + assertEquals(Integer.MIN_VALUE, Hours.of(Integer.MIN_VALUE).getAmount()); + } + + //----------------------------------------------------------------------- + public void test_deserializationSingleton() throws Exception { + Hours orginal = Hours.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); + Hours ser = (Hours) in.readObject(); + assertSame(Hours.ZERO, ser); + } + + //----------------------------------------------------------------------- + public void test_compareTo() { + Hours test5 = Hours.of(5); + Hours test6 = Hours.of(6); + assertEquals(0, test5.compareTo(test5)); + assertEquals(-1, test5.compareTo(test6)); + assertEquals(1, test6.compareTo(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_compareTo_null() { + Hours test5 = Hours.of(5); + test5.compareTo(null); + } + + //----------------------------------------------------------------------- + public void test_isGreaterThan() { + Hours test5 = Hours.of(5); + Hours test6 = Hours.of(6); + assertEquals(false, test5.isGreaterThan(test5)); + assertEquals(false, test5.isGreaterThan(test6)); + assertEquals(true, test6.isGreaterThan(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_isGreaterThan_null() { + Hours test5 = Hours.of(5); + test5.isGreaterThan(null); + } + + //----------------------------------------------------------------------- + public void test_isLessThan() { + Hours test5 = Hours.of(5); + Hours test6 = Hours.of(6); + assertEquals(false, test5.isLessThan(test5)); + assertEquals(true, test5.isLessThan(test6)); + assertEquals(false, test6.isLessThan(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_isLessThan_null() { + Hours test5 = Hours.of(5); + test5.isLessThan(null); + } + + //----------------------------------------------------------------------- + public void test_equals() { + Hours test5 = Hours.of(5); + Hours test6 = Hours.of(6); + assertEquals(true, test5.equals(test5)); + assertEquals(false, test5.equals(test6)); + assertEquals(false, test6.equals(test5)); + } + + public void test_equals_null() { + Hours test5 = Hours.of(5); + assertEquals(false, test5.equals(null)); + } + + public void test_equals_otherClass() { + Hours test5 = Hours.of(5); + assertEquals(false, test5.equals("")); + } + + //----------------------------------------------------------------------- + public void test_hashCode() { + Hours test5 = Hours.of(5); + Hours test6 = Hours.of(6); + assertEquals(true, test5.hashCode() == test5.hashCode()); + assertEquals(false, test5.hashCode() == test6.hashCode()); + } + + //----------------------------------------------------------------------- + public void test_getUnit() { + TemporalUnit unit = Hours.of(5).getUnit(); + assertNotNull(unit); + assertEquals(unit, HOURS); + } + + //----------------------------------------------------------------------- + public void test_plus() { + Hours test5 = Hours.of(5); + assertEquals(Hours.of(5), test5.plus(0)); + assertEquals(Hours.of(7), test5.plus(2)); + assertEquals(Hours.of(3), test5.plus(-2)); + assertEquals(Hours.of(Integer.MAX_VALUE), Hours.of(Integer.MAX_VALUE - 1).plus(1)); + assertEquals(Hours.of(Integer.MIN_VALUE), Hours.of(Integer.MIN_VALUE + 1).plus(-1)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_overflowTooBig() { + Hours.of(Integer.MAX_VALUE - 1).plus(2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_overflowTooSmall() { + Hours.of(Integer.MIN_VALUE + 1).plus(-2); + } + + //----------------------------------------------------------------------- + public void test_plus_Hours() { + Hours test5 = Hours.of(5); + assertEquals(Hours.of(5), test5.plus(Hours.of(0))); + assertEquals(Hours.of(7), test5.plus(Hours.of(2))); + assertEquals(Hours.of(3), test5.plus(Hours.of(-2))); + assertEquals(Hours.of(Integer.MAX_VALUE), + Hours.of(Integer.MAX_VALUE - 1).plus(Hours.of(1))); + assertEquals(Hours.of(Integer.MIN_VALUE), + Hours.of(Integer.MIN_VALUE + 1).plus(Hours.of(-1))); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_Hours_overflowTooBig() { + Hours.of(Integer.MAX_VALUE - 1).plus(Hours.of(2)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_Hours_overflowTooSmall() { + Hours.of(Integer.MIN_VALUE + 1).plus(Hours.of(-2)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_plus_Hours_null() { + Hours.of(Integer.MIN_VALUE + 1).plus(null); + } + + //----------------------------------------------------------------------- + public void test_minus() { + Hours test5 = Hours.of(5); + assertEquals(Hours.of(5), test5.minus(0)); + assertEquals(Hours.of(3), test5.minus(2)); + assertEquals(Hours.of(7), test5.minus(-2)); + assertEquals(Hours.of(Integer.MAX_VALUE), Hours.of(Integer.MAX_VALUE - 1).minus(-1)); + assertEquals(Hours.of(Integer.MIN_VALUE), Hours.of(Integer.MIN_VALUE + 1).minus(1)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_overflowTooBig() { + Hours.of(Integer.MAX_VALUE - 1).minus(-2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_overflowTooSmall() { + Hours.of(Integer.MIN_VALUE + 1).minus(2); + } + + //----------------------------------------------------------------------- + public void test_minus_Hours() { + Hours test5 = Hours.of(5); + assertEquals(Hours.of(5), test5.minus(Hours.of(0))); + assertEquals(Hours.of(3), test5.minus(Hours.of(2))); + assertEquals(Hours.of(7), test5.minus(Hours.of(-2))); + assertEquals(Hours.of(Integer.MAX_VALUE), + Hours.of(Integer.MAX_VALUE - 1).minus(Hours.of(-1))); + assertEquals(Hours.of(Integer.MIN_VALUE), + Hours.of(Integer.MIN_VALUE + 1).minus(Hours.of(1))); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_Hours_overflowTooBig() { + Hours.of(Integer.MAX_VALUE - 1).minus(Hours.of(-2)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_Hours_overflowTooSmall() { + Hours.of(Integer.MIN_VALUE + 1).minus(Hours.of(2)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_minus_Hours_null() { + Hours.of(Integer.MIN_VALUE + 1).minus(null); + } + + //----------------------------------------------------------------------- + public void test_multipliedBy() { + Hours test5 = Hours.of(5); + assertEquals(Hours.of(0), test5.multipliedBy(0)); + assertEquals(Hours.of(5), test5.multipliedBy(1)); + assertEquals(Hours.of(10), test5.multipliedBy(2)); + assertEquals(Hours.of(15), test5.multipliedBy(3)); + assertEquals(Hours.of(-15), test5.multipliedBy(-3)); + } + + public void test_multipliedBy_negate() { + Hours test5 = Hours.of(5); + assertEquals(Hours.of(-15), test5.multipliedBy(-3)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_multipliedBy_overflowTooBig() { + Hours.of(Integer.MAX_VALUE / 2 + 1).multipliedBy(2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_multipliedBy_overflowTooSmall() { + Hours.of(Integer.MIN_VALUE / 2 - 1).multipliedBy(2); + } + + //----------------------------------------------------------------------- + public void test_dividedBy() { + Hours test12 = Hours.of(12); + assertEquals(Hours.of(12), test12.dividedBy(1)); + assertEquals(Hours.of(6), test12.dividedBy(2)); + assertEquals(Hours.of(4), test12.dividedBy(3)); + assertEquals(Hours.of(3), test12.dividedBy(4)); + assertEquals(Hours.of(2), test12.dividedBy(5)); + assertEquals(Hours.of(2), test12.dividedBy(6)); + assertEquals(Hours.of(-4), test12.dividedBy(-3)); + } + + public void test_dividedBy_negate() { + Hours test12 = Hours.of(12); + assertEquals(Hours.of(-4), test12.dividedBy(-3)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_dividedBy_divideByZero() { + Hours.of(1).dividedBy(0); + } + + //----------------------------------------------------------------------- + public void test_negated() { + assertEquals(Hours.of(0), Hours.of(0).negated()); + assertEquals(Hours.of(-12), Hours.of(12).negated()); + assertEquals(Hours.of(12), Hours.of(-12).negated()); + assertEquals(Hours.of(-Integer.MAX_VALUE), Hours.of(Integer.MAX_VALUE).negated()); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_negated_overflow() { + Hours.of(Integer.MIN_VALUE).negated(); + } + + //----------------------------------------------------------------------- + public void test_toString() { + Hours test5 = Hours.of(5); + assertEquals("PT5H", test5.toString()); + Hours testM1 = Hours.of(-1); + assertEquals("PT-1H", testM1.toString()); + } + +} diff --git a/src/test/java/org/threeten/extra/TestMinutes.java b/src/test/java/org/threeten/extra/TestMinutes.java new file mode 100644 index 0000000..f4c2adb --- /dev/null +++ b/src/test/java/org/threeten/extra/TestMinutes.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +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 static org.threeten.bp.temporal.ChronoUnit.MINUTES; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import org.testng.annotations.Test; +import org.threeten.bp.temporal.TemporalUnit; + +/** + * Test class. + */ +@Test +public class TestMinutes { + + //----------------------------------------------------------------------- + public void test_isSerializable() { + assertTrue(Serializable.class.isAssignableFrom(Minutes.class)); + } + + //----------------------------------------------------------------------- + public void test_factoryZeroSingleton() { + assertSame(Minutes.ZERO, Minutes.of(0)); + assertSame(Minutes.ZERO, Minutes.of(0)); + assertEquals(0, Minutes.ZERO.getAmount()); + } + + //----------------------------------------------------------------------- + public void test_factoryGetMinutes() { + assertEquals(1, Minutes.of(1).getAmount()); + assertEquals(2, Minutes.of(2).getAmount()); + assertEquals(Integer.MAX_VALUE, Minutes.of(Integer.MAX_VALUE).getAmount()); + assertEquals(-1, Minutes.of(-1).getAmount()); + assertEquals(-2, Minutes.of(-2).getAmount()); + assertEquals(Integer.MIN_VALUE, Minutes.of(Integer.MIN_VALUE).getAmount()); + } + + //----------------------------------------------------------------------- + public void test_deserializationSingleton() throws Exception { + Minutes orginal = Minutes.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); + Minutes ser = (Minutes) in.readObject(); + assertSame(Minutes.ZERO, ser); + } + + //----------------------------------------------------------------------- + public void test_compareTo() { + Minutes test5 = Minutes.of(5); + Minutes test6 = Minutes.of(6); + assertEquals(0, test5.compareTo(test5)); + assertEquals(-1, test5.compareTo(test6)); + assertEquals(1, test6.compareTo(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_compareTo_null() { + Minutes test5 = Minutes.of(5); + test5.compareTo(null); + } + + //----------------------------------------------------------------------- + public void test_isGreaterThan() { + Minutes test5 = Minutes.of(5); + Minutes test6 = Minutes.of(6); + assertEquals(false, test5.isGreaterThan(test5)); + assertEquals(false, test5.isGreaterThan(test6)); + assertEquals(true, test6.isGreaterThan(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_isGreaterThan_null() { + Minutes test5 = Minutes.of(5); + test5.isGreaterThan(null); + } + + //----------------------------------------------------------------------- + public void test_isLessThan() { + Minutes test5 = Minutes.of(5); + Minutes test6 = Minutes.of(6); + assertEquals(false, test5.isLessThan(test5)); + assertEquals(true, test5.isLessThan(test6)); + assertEquals(false, test6.isLessThan(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_isLessThan_null() { + Minutes test5 = Minutes.of(5); + test5.isLessThan(null); + } + + //----------------------------------------------------------------------- + public void test_equals() { + Minutes test5 = Minutes.of(5); + Minutes test6 = Minutes.of(6); + assertEquals(true, test5.equals(test5)); + assertEquals(false, test5.equals(test6)); + assertEquals(false, test6.equals(test5)); + } + + public void test_equals_null() { + Minutes test5 = Minutes.of(5); + assertEquals(false, test5.equals(null)); + } + + public void test_equals_otherClass() { + Minutes test5 = Minutes.of(5); + assertEquals(false, test5.equals("")); + } + + //----------------------------------------------------------------------- + public void test_hashCode() { + Minutes test5 = Minutes.of(5); + Minutes test6 = Minutes.of(6); + assertEquals(true, test5.hashCode() == test5.hashCode()); + assertEquals(false, test5.hashCode() == test6.hashCode()); + } + + //----------------------------------------------------------------------- + public void test_getUnit() { + TemporalUnit unit = Minutes.of(5).getUnit(); + assertNotNull(unit); + assertEquals(unit, MINUTES); + } + + //----------------------------------------------------------------------- + public void test_plus() { + Minutes test5 = Minutes.of(5); + assertEquals(Minutes.of(5), test5.plus(0)); + assertEquals(Minutes.of(7), test5.plus(2)); + assertEquals(Minutes.of(3), test5.plus(-2)); + assertEquals(Minutes.of(Integer.MAX_VALUE), Minutes.of(Integer.MAX_VALUE - 1).plus(1)); + assertEquals(Minutes.of(Integer.MIN_VALUE), Minutes.of(Integer.MIN_VALUE + 1).plus(-1)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_overflowTooBig() { + Minutes.of(Integer.MAX_VALUE - 1).plus(2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_overflowTooSmall() { + Minutes.of(Integer.MIN_VALUE + 1).plus(-2); + } + + //----------------------------------------------------------------------- + public void test_plus_Minutes() { + Minutes test5 = Minutes.of(5); + assertEquals(Minutes.of(5), test5.plus(Minutes.of(0))); + assertEquals(Minutes.of(7), test5.plus(Minutes.of(2))); + assertEquals(Minutes.of(3), test5.plus(Minutes.of(-2))); + assertEquals(Minutes.of(Integer.MAX_VALUE), + Minutes.of(Integer.MAX_VALUE - 1).plus(Minutes.of(1))); + assertEquals(Minutes.of(Integer.MIN_VALUE), + Minutes.of(Integer.MIN_VALUE + 1).plus(Minutes.of(-1))); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_Minutes_overflowTooBig() { + Minutes.of(Integer.MAX_VALUE - 1).plus(Minutes.of(2)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_Minutes_overflowTooSmall() { + Minutes.of(Integer.MIN_VALUE + 1).plus(Minutes.of(-2)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_plus_Minutes_null() { + Minutes.of(Integer.MIN_VALUE + 1).plus(null); + } + + //----------------------------------------------------------------------- + public void test_minus() { + Minutes test5 = Minutes.of(5); + assertEquals(Minutes.of(5), test5.minus(0)); + assertEquals(Minutes.of(3), test5.minus(2)); + assertEquals(Minutes.of(7), test5.minus(-2)); + assertEquals(Minutes.of(Integer.MAX_VALUE), Minutes.of(Integer.MAX_VALUE - 1).minus(-1)); + assertEquals(Minutes.of(Integer.MIN_VALUE), Minutes.of(Integer.MIN_VALUE + 1).minus(1)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_overflowTooBig() { + Minutes.of(Integer.MAX_VALUE - 1).minus(-2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_overflowTooSmall() { + Minutes.of(Integer.MIN_VALUE + 1).minus(2); + } + + //----------------------------------------------------------------------- + public void test_minus_Minutes() { + Minutes test5 = Minutes.of(5); + assertEquals(Minutes.of(5), test5.minus(Minutes.of(0))); + assertEquals(Minutes.of(3), test5.minus(Minutes.of(2))); + assertEquals(Minutes.of(7), test5.minus(Minutes.of(-2))); + assertEquals(Minutes.of(Integer.MAX_VALUE), + Minutes.of(Integer.MAX_VALUE - 1).minus(Minutes.of(-1))); + assertEquals(Minutes.of(Integer.MIN_VALUE), + Minutes.of(Integer.MIN_VALUE + 1).minus(Minutes.of(1))); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_Minutes_overflowTooBig() { + Minutes.of(Integer.MAX_VALUE - 1).minus(Minutes.of(-2)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_Minutes_overflowTooSmall() { + Minutes.of(Integer.MIN_VALUE + 1).minus(Minutes.of(2)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_minus_Minutes_null() { + Minutes.of(Integer.MIN_VALUE + 1).minus(null); + } + + //----------------------------------------------------------------------- + public void test_multipliedBy() { + Minutes test5 = Minutes.of(5); + assertEquals(Minutes.of(0), test5.multipliedBy(0)); + assertEquals(Minutes.of(5), test5.multipliedBy(1)); + assertEquals(Minutes.of(10), test5.multipliedBy(2)); + assertEquals(Minutes.of(15), test5.multipliedBy(3)); + assertEquals(Minutes.of(-15), test5.multipliedBy(-3)); + } + + public void test_multipliedBy_negate() { + Minutes test5 = Minutes.of(5); + assertEquals(Minutes.of(-15), test5.multipliedBy(-3)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_multipliedBy_overflowTooBig() { + Minutes.of(Integer.MAX_VALUE / 2 + 1).multipliedBy(2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_multipliedBy_overflowTooSmall() { + Minutes.of(Integer.MIN_VALUE / 2 - 1).multipliedBy(2); + } + + //----------------------------------------------------------------------- + public void test_dividedBy() { + Minutes test12 = Minutes.of(12); + assertEquals(Minutes.of(12), test12.dividedBy(1)); + assertEquals(Minutes.of(6), test12.dividedBy(2)); + assertEquals(Minutes.of(4), test12.dividedBy(3)); + assertEquals(Minutes.of(3), test12.dividedBy(4)); + assertEquals(Minutes.of(2), test12.dividedBy(5)); + assertEquals(Minutes.of(2), test12.dividedBy(6)); + assertEquals(Minutes.of(-4), test12.dividedBy(-3)); + } + + public void test_dividedBy_negate() { + Minutes test12 = Minutes.of(12); + assertEquals(Minutes.of(-4), test12.dividedBy(-3)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_dividedBy_divideByZero() { + Minutes.of(1).dividedBy(0); + } + + //----------------------------------------------------------------------- + public void test_negated() { + assertEquals(Minutes.of(0), Minutes.of(0).negated()); + assertEquals(Minutes.of(-12), Minutes.of(12).negated()); + assertEquals(Minutes.of(12), Minutes.of(-12).negated()); + assertEquals(Minutes.of(-Integer.MAX_VALUE), Minutes.of(Integer.MAX_VALUE).negated()); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_negated_overflow() { + Minutes.of(Integer.MIN_VALUE).negated(); + } + + //----------------------------------------------------------------------- + public void test_toString() { + Minutes test5 = Minutes.of(5); + assertEquals("PT5M", test5.toString()); + Minutes testM1 = Minutes.of(-1); + assertEquals("PT-1M", testM1.toString()); + } + +} diff --git a/src/test/java/org/threeten/extra/TestMonths.java b/src/test/java/org/threeten/extra/TestMonths.java new file mode 100644 index 0000000..6da2c1e --- /dev/null +++ b/src/test/java/org/threeten/extra/TestMonths.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +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 static org.threeten.bp.temporal.ChronoUnit.MONTHS; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import org.testng.annotations.Test; +import org.threeten.bp.temporal.TemporalUnit; + +/** + * Test class. + */ +@Test +public class TestMonths { + + //----------------------------------------------------------------------- + public void test_isSerializable() { + assertTrue(Serializable.class.isAssignableFrom(Months.class)); + } + + //----------------------------------------------------------------------- + public void test_factoryZeroSingleton() { + assertSame(Months.ZERO, Months.of(0)); + assertSame(Months.ZERO, Months.of(0)); + assertEquals(0, Months.ZERO.getAmount()); + } + + //----------------------------------------------------------------------- + public void test_factoryGetMonths() { + assertEquals(1, Months.of(1).getAmount()); + assertEquals(2, Months.of(2).getAmount()); + assertEquals(Integer.MAX_VALUE, Months.of(Integer.MAX_VALUE).getAmount()); + assertEquals(-1, Months.of(-1).getAmount()); + assertEquals(-2, Months.of(-2).getAmount()); + assertEquals(Integer.MIN_VALUE, Months.of(Integer.MIN_VALUE).getAmount()); + } + + //----------------------------------------------------------------------- + public void test_deserializationSingleton() throws Exception { + Months orginal = Months.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); + Months ser = (Months) in.readObject(); + assertSame(Months.ZERO, ser); + } + + //----------------------------------------------------------------------- + public void test_compareTo() { + Months test5 = Months.of(5); + Months test6 = Months.of(6); + assertEquals(0, test5.compareTo(test5)); + assertEquals(-1, test5.compareTo(test6)); + assertEquals(1, test6.compareTo(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_compareTo_null() { + Months test5 = Months.of(5); + test5.compareTo(null); + } + + //----------------------------------------------------------------------- + public void test_isGreaterThan() { + Months test5 = Months.of(5); + Months test6 = Months.of(6); + assertEquals(false, test5.isGreaterThan(test5)); + assertEquals(false, test5.isGreaterThan(test6)); + assertEquals(true, test6.isGreaterThan(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_isGreaterThan_null() { + Months test5 = Months.of(5); + test5.isGreaterThan(null); + } + + //----------------------------------------------------------------------- + public void test_isLessThan() { + Months test5 = Months.of(5); + Months test6 = Months.of(6); + assertEquals(false, test5.isLessThan(test5)); + assertEquals(true, test5.isLessThan(test6)); + assertEquals(false, test6.isLessThan(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_isLessThan_null() { + Months test5 = Months.of(5); + test5.isLessThan(null); + } + + //----------------------------------------------------------------------- + public void test_equals() { + Months test5 = Months.of(5); + Months test6 = Months.of(6); + assertEquals(true, test5.equals(test5)); + assertEquals(false, test5.equals(test6)); + assertEquals(false, test6.equals(test5)); + } + + public void test_equals_null() { + Months test5 = Months.of(5); + assertEquals(false, test5.equals(null)); + } + + public void test_equals_otherClass() { + Months test5 = Months.of(5); + assertEquals(false, test5.equals("")); + } + + //----------------------------------------------------------------------- + public void test_hashCode() { + Months test5 = Months.of(5); + Months test6 = Months.of(6); + assertEquals(true, test5.hashCode() == test5.hashCode()); + assertEquals(false, test5.hashCode() == test6.hashCode()); + } + + //----------------------------------------------------------------------- + public void test_getUnit() { + TemporalUnit unit = Months.of(5).getUnit(); + assertNotNull(unit); + assertEquals(unit, MONTHS); + } + + //----------------------------------------------------------------------- + public void test_plus() { + Months test5 = Months.of(5); + assertEquals(Months.of(5), test5.plus(0)); + assertEquals(Months.of(7), test5.plus(2)); + assertEquals(Months.of(3), test5.plus(-2)); + assertEquals(Months.of(Integer.MAX_VALUE), Months.of(Integer.MAX_VALUE - 1).plus(1)); + assertEquals(Months.of(Integer.MIN_VALUE), Months.of(Integer.MIN_VALUE + 1).plus(-1)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_overflowTooBig() { + Months.of(Integer.MAX_VALUE - 1).plus(2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_overflowTooSmall() { + Months.of(Integer.MIN_VALUE + 1).plus(-2); + } + + //----------------------------------------------------------------------- + public void test_plus_Months() { + Months test5 = Months.of(5); + assertEquals(Months.of(5), test5.plus(Months.of(0))); + assertEquals(Months.of(7), test5.plus(Months.of(2))); + assertEquals(Months.of(3), test5.plus(Months.of(-2))); + assertEquals(Months.of(Integer.MAX_VALUE), + Months.of(Integer.MAX_VALUE - 1).plus(Months.of(1))); + assertEquals(Months.of(Integer.MIN_VALUE), + Months.of(Integer.MIN_VALUE + 1).plus(Months.of(-1))); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_Months_overflowTooBig() { + Months.of(Integer.MAX_VALUE - 1).plus(Months.of(2)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_Months_overflowTooSmall() { + Months.of(Integer.MIN_VALUE + 1).plus(Months.of(-2)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_plus_Months_null() { + Months.of(Integer.MIN_VALUE + 1).plus(null); + } + + //----------------------------------------------------------------------- + public void test_minus() { + Months test5 = Months.of(5); + assertEquals(Months.of(5), test5.minus(0)); + assertEquals(Months.of(3), test5.minus(2)); + assertEquals(Months.of(7), test5.minus(-2)); + assertEquals(Months.of(Integer.MAX_VALUE), Months.of(Integer.MAX_VALUE - 1).minus(-1)); + assertEquals(Months.of(Integer.MIN_VALUE), Months.of(Integer.MIN_VALUE + 1).minus(1)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_overflowTooBig() { + Months.of(Integer.MAX_VALUE - 1).minus(-2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_overflowTooSmall() { + Months.of(Integer.MIN_VALUE + 1).minus(2); + } + + //----------------------------------------------------------------------- + public void test_minus_Months() { + Months test5 = Months.of(5); + assertEquals(Months.of(5), test5.minus(Months.of(0))); + assertEquals(Months.of(3), test5.minus(Months.of(2))); + assertEquals(Months.of(7), test5.minus(Months.of(-2))); + assertEquals(Months.of(Integer.MAX_VALUE), + Months.of(Integer.MAX_VALUE - 1).minus(Months.of(-1))); + assertEquals(Months.of(Integer.MIN_VALUE), + Months.of(Integer.MIN_VALUE + 1).minus(Months.of(1))); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_Months_overflowTooBig() { + Months.of(Integer.MAX_VALUE - 1).minus(Months.of(-2)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_Months_overflowTooSmall() { + Months.of(Integer.MIN_VALUE + 1).minus(Months.of(2)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_minus_Months_null() { + Months.of(Integer.MIN_VALUE + 1).minus(null); + } + + //----------------------------------------------------------------------- + public void test_multipliedBy() { + Months test5 = Months.of(5); + assertEquals(Months.of(0), test5.multipliedBy(0)); + assertEquals(Months.of(5), test5.multipliedBy(1)); + assertEquals(Months.of(10), test5.multipliedBy(2)); + assertEquals(Months.of(15), test5.multipliedBy(3)); + assertEquals(Months.of(-15), test5.multipliedBy(-3)); + } + + public void test_multipliedBy_negate() { + Months test5 = Months.of(5); + assertEquals(Months.of(-15), test5.multipliedBy(-3)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_multipliedBy_overflowTooBig() { + Months.of(Integer.MAX_VALUE / 2 + 1).multipliedBy(2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_multipliedBy_overflowTooSmall() { + Months.of(Integer.MIN_VALUE / 2 - 1).multipliedBy(2); + } + + //----------------------------------------------------------------------- + public void test_dividedBy() { + Months test12 = Months.of(12); + assertEquals(Months.of(12), test12.dividedBy(1)); + assertEquals(Months.of(6), test12.dividedBy(2)); + assertEquals(Months.of(4), test12.dividedBy(3)); + assertEquals(Months.of(3), test12.dividedBy(4)); + assertEquals(Months.of(2), test12.dividedBy(5)); + assertEquals(Months.of(2), test12.dividedBy(6)); + assertEquals(Months.of(-4), test12.dividedBy(-3)); + } + + public void test_dividedBy_negate() { + Months test12 = Months.of(12); + assertEquals(Months.of(-4), test12.dividedBy(-3)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_dividedBy_divideByZero() { + Months.of(1).dividedBy(0); + } + + //----------------------------------------------------------------------- + public void test_negated() { + assertEquals(Months.of(0), Months.of(0).negated()); + assertEquals(Months.of(-12), Months.of(12).negated()); + assertEquals(Months.of(12), Months.of(-12).negated()); + assertEquals(Months.of(-Integer.MAX_VALUE), Months.of(Integer.MAX_VALUE).negated()); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_negated_overflow() { + Months.of(Integer.MIN_VALUE).negated(); + } + + //----------------------------------------------------------------------- + public void test_toString() { + Months test5 = Months.of(5); + assertEquals("P5M", test5.toString()); + Months testM1 = Months.of(-1); + assertEquals("P-1M", testM1.toString()); + } + +} diff --git a/src/test/java/org/threeten/extra/TestQuarterOfYear.java b/src/test/java/org/threeten/extra/TestQuarterOfYear.java new file mode 100644 index 0000000..6427bed --- /dev/null +++ b/src/test/java/org/threeten/extra/TestQuarterOfYear.java @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.io.Serializable; +import java.util.Locale; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import org.threeten.bp.DateTimeException; +import org.threeten.bp.LocalDate; +import org.threeten.bp.LocalDateTime; +import org.threeten.bp.LocalTime; +import org.threeten.bp.Month; +import org.threeten.bp.format.TextStyle; +import org.threeten.bp.temporal.ChronoField; +import org.threeten.bp.temporal.IsoFields; +import org.threeten.bp.temporal.TemporalAccessor; +import org.threeten.bp.temporal.TemporalField; + +/** + * Test QuarterOfYear. + */ +@Test +public class TestQuarterOfYear { + + @BeforeMethod + public void setUp() { + } + + //----------------------------------------------------------------------- + @Test(groups={"implementation"}) + public void test_interfaces() { + assertTrue(Enum.class.isAssignableFrom(QuarterOfYear.class)); + assertTrue(Serializable.class.isAssignableFrom(QuarterOfYear.class)); + assertTrue(Comparable.class.isAssignableFrom(QuarterOfYear.class)); + } + + //----------------------------------------------------------------------- + // of(int) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_of_int_singleton() { + for (int i = 1; i <= 4; i++) { + QuarterOfYear test = QuarterOfYear.of(i); + assertEquals(test.getValue(), i); + assertSame(QuarterOfYear.of(i), test); + } + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_of_int_valueTooLow() { + QuarterOfYear.of(0); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_of_int_valueTooHigh() { + QuarterOfYear.of(5); + } + + //----------------------------------------------------------------------- + // ofMonth(Month) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_getQuarterOfYear() { + assertEquals(QuarterOfYear.ofMonth(Month.JANUARY), QuarterOfYear.Q1); + assertEquals(QuarterOfYear.ofMonth(Month.FEBRUARY), QuarterOfYear.Q1); + assertEquals(QuarterOfYear.ofMonth(Month.MARCH), QuarterOfYear.Q1); + assertEquals(QuarterOfYear.ofMonth(Month.APRIL), QuarterOfYear.Q2); + assertEquals(QuarterOfYear.ofMonth(Month.MAY), QuarterOfYear.Q2); + assertEquals(QuarterOfYear.ofMonth(Month.JUNE), QuarterOfYear.Q2); + assertEquals(QuarterOfYear.ofMonth(Month.JULY), QuarterOfYear.Q3); + assertEquals(QuarterOfYear.ofMonth(Month.AUGUST), QuarterOfYear.Q3); + assertEquals(QuarterOfYear.ofMonth(Month.SEPTEMBER), QuarterOfYear.Q3); + assertEquals(QuarterOfYear.ofMonth(Month.OCTOBER), QuarterOfYear.Q4); + assertEquals(QuarterOfYear.ofMonth(Month.NOVEMBER), QuarterOfYear.Q4); + assertEquals(QuarterOfYear.ofMonth(Month.DECEMBER), QuarterOfYear.Q4); + } + + //----------------------------------------------------------------------- + // from(TemporalAccessor) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_from_TemporalAccessor() { + assertEquals(QuarterOfYear.from(LocalDate.of(2011, 6, 6)), QuarterOfYear.Q2); + assertEquals(QuarterOfYear.from(LocalDateTime.of(2012, 2, 3, 12, 30)), QuarterOfYear.Q1); + } + + @Test(expectedExceptions=DateTimeException.class, groups={"tck"}) + public void test_from_TemporalAccessor_invalid_noDerive() { + QuarterOfYear.from(LocalTime.of(12, 30)); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_from_TemporalAccessor_null() { + QuarterOfYear.from((TemporalAccessor) null); + } + + //----------------------------------------------------------------------- + // getDisplayName() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_getDisplayName() { + assertEquals(QuarterOfYear.Q1.getDisplayName(TextStyle.SHORT, Locale.US), "1"); + } + + @Test(expectedExceptions = NullPointerException.class, groups={"tck"}) + public void test_getDisplayName_nullStyle() { + QuarterOfYear.Q1.getDisplayName(null, Locale.US); + } + + @Test(expectedExceptions = NullPointerException.class, groups={"tck"}) + public void test_getDisplayName_nullLocale() { + QuarterOfYear.Q1.getDisplayName(TextStyle.FULL, null); + } + + //----------------------------------------------------------------------- + // get(TemporalField) + //----------------------------------------------------------------------- + @DataProvider(name="invalidFields") + Object[][] data_invalidFields() { + return new Object[][] { + {ChronoField.NANO_OF_DAY}, + {ChronoField.HOUR_OF_DAY}, + {ChronoField.DAY_OF_MONTH}, + {ChronoField.MONTH_OF_YEAR}, + {ChronoField.INSTANT_SECONDS}, + {MockFieldNoValue.INSTANCE}, + }; + } + + @Test(groups={"tck"}) + public void test_get_DateTimeField() { + assertEquals(QuarterOfYear.Q1.getLong(IsoFields.QUARTER_OF_YEAR), 1); + assertEquals(QuarterOfYear.Q2.getLong(IsoFields.QUARTER_OF_YEAR), 2); + assertEquals(QuarterOfYear.Q3.getLong(IsoFields.QUARTER_OF_YEAR), 3); + assertEquals(QuarterOfYear.Q4.getLong(IsoFields.QUARTER_OF_YEAR), 4); + } + + @Test(dataProvider="invalidFields", expectedExceptions=DateTimeException.class, groups={"tck"} ) + public void test_get_DateTimeField_invalidField(TemporalField field) { + QuarterOfYear.Q1.getLong(field); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"} ) + public void test_get_DateTimeField_null() { + QuarterOfYear.Q1.getLong((TemporalField) null); + } + + //----------------------------------------------------------------------- + // plus(long), plus(long,unit) + //----------------------------------------------------------------------- + @DataProvider(name="plus") + Object[][] data_plus() { + return new Object[][] { + {1, -5, 4}, + {1, -4, 1}, + {1, -3, 2}, + {1, -2, 3}, + {1, -1, 4}, + {1, 0, 1}, + {1, 1, 2}, + {1, 2, 3}, + {1, 3, 4}, + {1, 4, 1}, + {1, 5, 2}, + }; + } + + @Test(dataProvider="plus", groups={"tck"}) + public void test_plus_long(int base, long amount, int expected) { + assertEquals(QuarterOfYear.of(base).plus(amount), QuarterOfYear.of(expected)); + } + + //----------------------------------------------------------------------- + // minus(long), minus(long,unit) + //----------------------------------------------------------------------- + @DataProvider(name="minus") + Object[][] data_minus() { + return new Object[][] { + {1, -5, 2}, + {1, -4, 1}, + {1, -3, 4}, + {1, -2, 3}, + {1, -1, 2}, + {1, 0, 1}, + {1, 1, 4}, + {1, 2, 3}, + {1, 3, 2}, + {1, 4, 1}, + {1, 5, 4}, + }; + } + + @Test(dataProvider="minus", groups={"tck"}) + public void test_minus_long(int base, long amount, int expected) { + assertEquals(QuarterOfYear.of(base).minus(amount), QuarterOfYear.of(expected)); + } + + //----------------------------------------------------------------------- + // firstMonth() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_firstMonth() { + assertEquals(QuarterOfYear.Q1.firstMonth(), Month.JANUARY); + assertEquals(QuarterOfYear.Q2.firstMonth(), Month.APRIL); + assertEquals(QuarterOfYear.Q3.firstMonth(), Month.JULY); + assertEquals(QuarterOfYear.Q4.firstMonth(), Month.OCTOBER); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString() { + assertEquals(QuarterOfYear.Q1.toString(), "Q1"); + assertEquals(QuarterOfYear.Q2.toString(), "Q2"); + assertEquals(QuarterOfYear.Q3.toString(), "Q3"); + assertEquals(QuarterOfYear.Q4.toString(), "Q4"); + } + + //----------------------------------------------------------------------- + // generated methods + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_enum() { + assertEquals(QuarterOfYear.valueOf("Q4"), QuarterOfYear.Q4); + assertEquals(QuarterOfYear.values()[0], QuarterOfYear.Q1); + } + +} diff --git a/src/test/java/org/threeten/extra/TestSeconds.java b/src/test/java/org/threeten/extra/TestSeconds.java new file mode 100644 index 0000000..4d5f571 --- /dev/null +++ b/src/test/java/org/threeten/extra/TestSeconds.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +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 static org.threeten.bp.temporal.ChronoUnit.SECONDS; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import org.testng.annotations.Test; +import org.threeten.bp.temporal.TemporalUnit; + +/** + * Test class. + */ +@Test +public class TestSeconds { + + //----------------------------------------------------------------------- + public void test_isSerializable() { + assertTrue(Serializable.class.isAssignableFrom(Seconds.class)); + } + + //----------------------------------------------------------------------- + public void test_factoryZeroSingleton() { + assertSame(Seconds.ZERO, Seconds.of(0)); + assertSame(Seconds.ZERO, Seconds.of(0)); + assertEquals(0, Seconds.ZERO.getAmount()); + } + + //----------------------------------------------------------------------- + public void test_factoryGetSeconds() { + assertEquals(1, Seconds.of(1).getAmount()); + assertEquals(2, Seconds.of(2).getAmount()); + assertEquals(Integer.MAX_VALUE, Seconds.of(Integer.MAX_VALUE).getAmount()); + assertEquals(-1, Seconds.of(-1).getAmount()); + assertEquals(-2, Seconds.of(-2).getAmount()); + assertEquals(Integer.MIN_VALUE, Seconds.of(Integer.MIN_VALUE).getAmount()); + } + + //----------------------------------------------------------------------- + public void test_deserializationSingleton() throws Exception { + Seconds orginal = Seconds.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); + Seconds ser = (Seconds) in.readObject(); + assertSame(Seconds.ZERO, ser); + } + + //----------------------------------------------------------------------- + public void test_compareTo() { + Seconds test5 = Seconds.of(5); + Seconds test6 = Seconds.of(6); + assertEquals(0, test5.compareTo(test5)); + assertEquals(-1, test5.compareTo(test6)); + assertEquals(1, test6.compareTo(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_compareTo_null() { + Seconds test5 = Seconds.of(5); + test5.compareTo(null); + } + + //----------------------------------------------------------------------- + public void test_isGreaterThan() { + Seconds test5 = Seconds.of(5); + Seconds test6 = Seconds.of(6); + assertEquals(false, test5.isGreaterThan(test5)); + assertEquals(false, test5.isGreaterThan(test6)); + assertEquals(true, test6.isGreaterThan(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_isGreaterThan_null() { + Seconds test5 = Seconds.of(5); + test5.isGreaterThan(null); + } + + //----------------------------------------------------------------------- + public void test_isLessThan() { + Seconds test5 = Seconds.of(5); + Seconds test6 = Seconds.of(6); + assertEquals(false, test5.isLessThan(test5)); + assertEquals(true, test5.isLessThan(test6)); + assertEquals(false, test6.isLessThan(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_isLessThan_null() { + Seconds test5 = Seconds.of(5); + test5.isLessThan(null); + } + + //----------------------------------------------------------------------- + public void test_equals() { + Seconds test5 = Seconds.of(5); + Seconds test6 = Seconds.of(6); + assertEquals(true, test5.equals(test5)); + assertEquals(false, test5.equals(test6)); + assertEquals(false, test6.equals(test5)); + } + + public void test_equals_null() { + Seconds test5 = Seconds.of(5); + assertEquals(false, test5.equals(null)); + } + + public void test_equals_otherClass() { + Seconds test5 = Seconds.of(5); + assertEquals(false, test5.equals("")); + } + + //----------------------------------------------------------------------- + public void test_hashCode() { + Seconds test5 = Seconds.of(5); + Seconds test6 = Seconds.of(6); + assertEquals(true, test5.hashCode() == test5.hashCode()); + assertEquals(false, test5.hashCode() == test6.hashCode()); + } + + //----------------------------------------------------------------------- + public void test_getUnit() { + TemporalUnit unit = Seconds.of(5).getUnit(); + assertNotNull(unit); + assertEquals(unit, SECONDS); + } + + //----------------------------------------------------------------------- + public void test_plus() { + Seconds test5 = Seconds.of(5); + assertEquals(Seconds.of(5), test5.plus(0)); + assertEquals(Seconds.of(7), test5.plus(2)); + assertEquals(Seconds.of(3), test5.plus(-2)); + assertEquals(Seconds.of(Integer.MAX_VALUE), Seconds.of(Integer.MAX_VALUE - 1).plus(1)); + assertEquals(Seconds.of(Integer.MIN_VALUE), Seconds.of(Integer.MIN_VALUE + 1).plus(-1)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_overflowTooBig() { + Seconds.of(Integer.MAX_VALUE - 1).plus(2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_overflowTooSmall() { + Seconds.of(Integer.MIN_VALUE + 1).plus(-2); + } + + //----------------------------------------------------------------------- + public void test_plus_Seconds() { + Seconds test5 = Seconds.of(5); + assertEquals(Seconds.of(5), test5.plus(Seconds.of(0))); + assertEquals(Seconds.of(7), test5.plus(Seconds.of(2))); + assertEquals(Seconds.of(3), test5.plus(Seconds.of(-2))); + assertEquals(Seconds.of(Integer.MAX_VALUE), + Seconds.of(Integer.MAX_VALUE - 1).plus(Seconds.of(1))); + assertEquals(Seconds.of(Integer.MIN_VALUE), + Seconds.of(Integer.MIN_VALUE + 1).plus(Seconds.of(-1))); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_Seconds_overflowTooBig() { + Seconds.of(Integer.MAX_VALUE - 1).plus(Seconds.of(2)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_Seconds_overflowTooSmall() { + Seconds.of(Integer.MIN_VALUE + 1).plus(Seconds.of(-2)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_plus_Seconds_null() { + Seconds.of(Integer.MIN_VALUE + 1).plus(null); + } + + //----------------------------------------------------------------------- + public void test_minus() { + Seconds test5 = Seconds.of(5); + assertEquals(Seconds.of(5), test5.minus(0)); + assertEquals(Seconds.of(3), test5.minus(2)); + assertEquals(Seconds.of(7), test5.minus(-2)); + assertEquals(Seconds.of(Integer.MAX_VALUE), Seconds.of(Integer.MAX_VALUE - 1).minus(-1)); + assertEquals(Seconds.of(Integer.MIN_VALUE), Seconds.of(Integer.MIN_VALUE + 1).minus(1)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_overflowTooBig() { + Seconds.of(Integer.MAX_VALUE - 1).minus(-2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_overflowTooSmall() { + Seconds.of(Integer.MIN_VALUE + 1).minus(2); + } + + //----------------------------------------------------------------------- + public void test_minus_Seconds() { + Seconds test5 = Seconds.of(5); + assertEquals(Seconds.of(5), test5.minus(Seconds.of(0))); + assertEquals(Seconds.of(3), test5.minus(Seconds.of(2))); + assertEquals(Seconds.of(7), test5.minus(Seconds.of(-2))); + assertEquals(Seconds.of(Integer.MAX_VALUE), + Seconds.of(Integer.MAX_VALUE - 1).minus(Seconds.of(-1))); + assertEquals(Seconds.of(Integer.MIN_VALUE), + Seconds.of(Integer.MIN_VALUE + 1).minus(Seconds.of(1))); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_Seconds_overflowTooBig() { + Seconds.of(Integer.MAX_VALUE - 1).minus(Seconds.of(-2)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_Seconds_overflowTooSmall() { + Seconds.of(Integer.MIN_VALUE + 1).minus(Seconds.of(2)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_minus_Seconds_null() { + Seconds.of(Integer.MIN_VALUE + 1).minus(null); + } + + //----------------------------------------------------------------------- + public void test_multipliedBy() { + Seconds test5 = Seconds.of(5); + assertEquals(Seconds.of(0), test5.multipliedBy(0)); + assertEquals(Seconds.of(5), test5.multipliedBy(1)); + assertEquals(Seconds.of(10), test5.multipliedBy(2)); + assertEquals(Seconds.of(15), test5.multipliedBy(3)); + assertEquals(Seconds.of(-15), test5.multipliedBy(-3)); + } + + public void test_multipliedBy_negate() { + Seconds test5 = Seconds.of(5); + assertEquals(Seconds.of(-15), test5.multipliedBy(-3)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_multipliedBy_overflowTooBig() { + Seconds.of(Integer.MAX_VALUE / 2 + 1).multipliedBy(2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_multipliedBy_overflowTooSmall() { + Seconds.of(Integer.MIN_VALUE / 2 - 1).multipliedBy(2); + } + + //----------------------------------------------------------------------- + public void test_dividedBy() { + Seconds test12 = Seconds.of(12); + assertEquals(Seconds.of(12), test12.dividedBy(1)); + assertEquals(Seconds.of(6), test12.dividedBy(2)); + assertEquals(Seconds.of(4), test12.dividedBy(3)); + assertEquals(Seconds.of(3), test12.dividedBy(4)); + assertEquals(Seconds.of(2), test12.dividedBy(5)); + assertEquals(Seconds.of(2), test12.dividedBy(6)); + assertEquals(Seconds.of(-4), test12.dividedBy(-3)); + } + + public void test_dividedBy_negate() { + Seconds test12 = Seconds.of(12); + assertEquals(Seconds.of(-4), test12.dividedBy(-3)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_dividedBy_divideByZero() { + Seconds.of(1).dividedBy(0); + } + + //----------------------------------------------------------------------- + public void test_negated() { + assertEquals(Seconds.of(0), Seconds.of(0).negated()); + assertEquals(Seconds.of(-12), Seconds.of(12).negated()); + assertEquals(Seconds.of(12), Seconds.of(-12).negated()); + assertEquals(Seconds.of(-Integer.MAX_VALUE), Seconds.of(Integer.MAX_VALUE).negated()); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_negated_overflow() { + Seconds.of(Integer.MIN_VALUE).negated(); + } + + //----------------------------------------------------------------------- + public void test_toString() { + Seconds test5 = Seconds.of(5); + assertEquals("PT5S", test5.toString()); + Seconds testM1 = Seconds.of(-1); + assertEquals("PT-1S", testM1.toString()); + } + +} diff --git a/src/test/java/org/threeten/extra/TestWeekendRules.java b/src/test/java/org/threeten/extra/TestWeekendRules.java new file mode 100644 index 0000000..46ee9c4 --- /dev/null +++ b/src/test/java/org/threeten/extra/TestWeekendRules.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +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 static org.threeten.bp.DayOfWeek.MONDAY; +import static org.threeten.bp.DayOfWeek.SATURDAY; +import static org.threeten.bp.DayOfWeek.SUNDAY; + +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 org.testng.annotations.Test; +import org.threeten.bp.LocalDate; +import org.threeten.bp.Month; +import org.threeten.bp.temporal.Temporal; +import org.threeten.bp.temporal.TemporalAdjuster; + +/** + * Test WeekendRules. + */ +@Test +public class TestWeekendRules { + + //----------------------------------------------------------------------- + // nextNonWeekendDay() + //----------------------------------------------------------------------- + public void test_nextNonWeekendDay_serialization() throws IOException, ClassNotFoundException { + TemporalAdjuster nextNonWeekendDay = WeekendRules.nextNonWeekendDay(); + assertTrue(nextNonWeekendDay instanceof Serializable); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(nextNonWeekendDay); + oos.close(); + + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())); + assertSame(ois.readObject(), nextNonWeekendDay); + } + + public void factory_nextNonWeekendDay() { + assertNotNull(WeekendRules.nextNonWeekendDay()); + assertSame(WeekendRules.nextNonWeekendDay(), WeekendRules.nextNonWeekendDay()); + } + + public void test_nextNonWeekendDay() { + for (Month month : Month.values()) { + for (int i = 1; i <= month.length(false); i++) { + LocalDate date = LocalDate.of(2007, month, i); + LocalDate test = (LocalDate) WeekendRules.nextNonWeekendDay().adjustInto(date); + assertTrue(test.isAfter(date)); + assertFalse(test.getDayOfWeek().equals(SATURDAY)); + assertFalse(test.getDayOfWeek().equals(SUNDAY)); + + switch (date.getDayOfWeek()) { + case FRIDAY: + case SATURDAY: + assertEquals(test.getDayOfWeek(), MONDAY); + break; + default: + assertEquals(date.getDayOfWeek().plus(1), test.getDayOfWeek()); + } + + if (test.getYear() == 2007) { + int dayDiff = test.getDayOfYear() - date.getDayOfYear(); + + switch (date.getDayOfWeek()) { + case FRIDAY: + assertEquals(dayDiff, 3); + break; + case SATURDAY: + assertEquals(dayDiff, 2); + break; + default: + assertEquals(dayDiff, 1); + } + } else { + assertEquals(test.getYear(), 2008); + assertEquals(test.getMonth(), Month.JANUARY); + assertEquals(test.getDayOfMonth(), 1); + } + } + } + } + + public void test_nextNonWeekendDay_yearChange() { + LocalDate friday = LocalDate.of(2010, Month.DECEMBER, 31); + Temporal test = WeekendRules.nextNonWeekendDay().adjustInto(friday); + assertEquals(LocalDate.of(2011, Month.JANUARY, 3), test); + + LocalDate saturday = LocalDate.of(2011, Month.DECEMBER, 31); + test = WeekendRules.nextNonWeekendDay().adjustInto(saturday); + assertEquals(LocalDate.of(2012, Month.JANUARY, 2), test); + } + +} diff --git a/src/test/java/org/threeten/extra/TestWeeks.java b/src/test/java/org/threeten/extra/TestWeeks.java new file mode 100644 index 0000000..1850164 --- /dev/null +++ b/src/test/java/org/threeten/extra/TestWeeks.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +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 static org.threeten.bp.temporal.ChronoUnit.WEEKS; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import org.testng.annotations.Test; +import org.threeten.bp.temporal.TemporalUnit; + +/** + * Test class. + */ +@Test +public class TestWeeks { + + //----------------------------------------------------------------------- + public void test_isSerializable() { + assertTrue(Serializable.class.isAssignableFrom(Weeks.class)); + } + + //----------------------------------------------------------------------- + public void test_factoryZeroSingleton() { + assertSame(Weeks.ZERO, Weeks.of(0)); + assertSame(Weeks.ZERO, Weeks.of(0)); + assertEquals(0, Weeks.ZERO.getAmount()); + } + + //----------------------------------------------------------------------- + public void test_factoryGetWeeks() { + assertEquals(1, Weeks.of(1).getAmount()); + assertEquals(2, Weeks.of(2).getAmount()); + assertEquals(Integer.MAX_VALUE, Weeks.of(Integer.MAX_VALUE).getAmount()); + assertEquals(-1, Weeks.of(-1).getAmount()); + assertEquals(-2, Weeks.of(-2).getAmount()); + assertEquals(Integer.MIN_VALUE, Weeks.of(Integer.MIN_VALUE).getAmount()); + } + + //----------------------------------------------------------------------- + public void test_deserializationSingleton() throws Exception { + Weeks orginal = Weeks.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); + Weeks ser = (Weeks) in.readObject(); + assertSame(Weeks.ZERO, ser); + } + + //----------------------------------------------------------------------- + public void test_compareTo() { + Weeks test5 = Weeks.of(5); + Weeks test6 = Weeks.of(6); + assertEquals(0, test5.compareTo(test5)); + assertEquals(-1, test5.compareTo(test6)); + assertEquals(1, test6.compareTo(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_compareTo_null() { + Weeks test5 = Weeks.of(5); + test5.compareTo(null); + } + + //----------------------------------------------------------------------- + public void test_isGreaterThan() { + Weeks test5 = Weeks.of(5); + Weeks test6 = Weeks.of(6); + assertEquals(false, test5.isGreaterThan(test5)); + assertEquals(false, test5.isGreaterThan(test6)); + assertEquals(true, test6.isGreaterThan(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_isGreaterThan_null() { + Weeks test5 = Weeks.of(5); + test5.isGreaterThan(null); + } + + //----------------------------------------------------------------------- + public void test_isLessThan() { + Weeks test5 = Weeks.of(5); + Weeks test6 = Weeks.of(6); + assertEquals(false, test5.isLessThan(test5)); + assertEquals(true, test5.isLessThan(test6)); + assertEquals(false, test6.isLessThan(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_isLessThan_null() { + Weeks test5 = Weeks.of(5); + test5.isLessThan(null); + } + + //----------------------------------------------------------------------- + public void test_equals() { + Weeks test5 = Weeks.of(5); + Weeks test6 = Weeks.of(6); + assertEquals(true, test5.equals(test5)); + assertEquals(false, test5.equals(test6)); + assertEquals(false, test6.equals(test5)); + } + + public void test_equals_null() { + Weeks test5 = Weeks.of(5); + assertEquals(false, test5.equals(null)); + } + + public void test_equals_otherClass() { + Weeks test5 = Weeks.of(5); + assertEquals(false, test5.equals("")); + } + + //----------------------------------------------------------------------- + public void test_hashCode() { + Weeks test5 = Weeks.of(5); + Weeks test6 = Weeks.of(6); + assertEquals(true, test5.hashCode() == test5.hashCode()); + assertEquals(false, test5.hashCode() == test6.hashCode()); + } + + //----------------------------------------------------------------------- + public void test_getUnit() { + TemporalUnit unit = Weeks.of(5).getUnit(); + assertNotNull(unit); + assertEquals(unit, WEEKS); + } + + //----------------------------------------------------------------------- + public void test_plus() { + Weeks test5 = Weeks.of(5); + assertEquals(Weeks.of(5), test5.plus(0)); + assertEquals(Weeks.of(7), test5.plus(2)); + assertEquals(Weeks.of(3), test5.plus(-2)); + assertEquals(Weeks.of(Integer.MAX_VALUE), Weeks.of(Integer.MAX_VALUE - 1).plus(1)); + assertEquals(Weeks.of(Integer.MIN_VALUE), Weeks.of(Integer.MIN_VALUE + 1).plus(-1)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_overflowTooBig() { + Weeks.of(Integer.MAX_VALUE - 1).plus(2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_overflowTooSmall() { + Weeks.of(Integer.MIN_VALUE + 1).plus(-2); + } + + //----------------------------------------------------------------------- + public void test_plus_Weeks() { + Weeks test5 = Weeks.of(5); + assertEquals(Weeks.of(5), test5.plus(Weeks.of(0))); + assertEquals(Weeks.of(7), test5.plus(Weeks.of(2))); + assertEquals(Weeks.of(3), test5.plus(Weeks.of(-2))); + assertEquals(Weeks.of(Integer.MAX_VALUE), + Weeks.of(Integer.MAX_VALUE - 1).plus(Weeks.of(1))); + assertEquals(Weeks.of(Integer.MIN_VALUE), + Weeks.of(Integer.MIN_VALUE + 1).plus(Weeks.of(-1))); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_Weeks_overflowTooBig() { + Weeks.of(Integer.MAX_VALUE - 1).plus(Weeks.of(2)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_Weeks_overflowTooSmall() { + Weeks.of(Integer.MIN_VALUE + 1).plus(Weeks.of(-2)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_plus_Weeks_null() { + Weeks.of(Integer.MIN_VALUE + 1).plus(null); + } + + //----------------------------------------------------------------------- + public void test_minus() { + Weeks test5 = Weeks.of(5); + assertEquals(Weeks.of(5), test5.minus(0)); + assertEquals(Weeks.of(3), test5.minus(2)); + assertEquals(Weeks.of(7), test5.minus(-2)); + assertEquals(Weeks.of(Integer.MAX_VALUE), Weeks.of(Integer.MAX_VALUE - 1).minus(-1)); + assertEquals(Weeks.of(Integer.MIN_VALUE), Weeks.of(Integer.MIN_VALUE + 1).minus(1)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_overflowTooBig() { + Weeks.of(Integer.MAX_VALUE - 1).minus(-2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_overflowTooSmall() { + Weeks.of(Integer.MIN_VALUE + 1).minus(2); + } + + //----------------------------------------------------------------------- + public void test_minus_Weeks() { + Weeks test5 = Weeks.of(5); + assertEquals(Weeks.of(5), test5.minus(Weeks.of(0))); + assertEquals(Weeks.of(3), test5.minus(Weeks.of(2))); + assertEquals(Weeks.of(7), test5.minus(Weeks.of(-2))); + assertEquals(Weeks.of(Integer.MAX_VALUE), + Weeks.of(Integer.MAX_VALUE - 1).minus(Weeks.of(-1))); + assertEquals(Weeks.of(Integer.MIN_VALUE), + Weeks.of(Integer.MIN_VALUE + 1).minus(Weeks.of(1))); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_Weeks_overflowTooBig() { + Weeks.of(Integer.MAX_VALUE - 1).minus(Weeks.of(-2)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_Weeks_overflowTooSmall() { + Weeks.of(Integer.MIN_VALUE + 1).minus(Weeks.of(2)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_minus_Weeks_null() { + Weeks.of(Integer.MIN_VALUE + 1).minus(null); + } + + //----------------------------------------------------------------------- + public void test_multipliedBy() { + Weeks test5 = Weeks.of(5); + assertEquals(Weeks.of(0), test5.multipliedBy(0)); + assertEquals(Weeks.of(5), test5.multipliedBy(1)); + assertEquals(Weeks.of(10), test5.multipliedBy(2)); + assertEquals(Weeks.of(15), test5.multipliedBy(3)); + assertEquals(Weeks.of(-15), test5.multipliedBy(-3)); + } + + public void test_multipliedBy_negate() { + Weeks test5 = Weeks.of(5); + assertEquals(Weeks.of(-15), test5.multipliedBy(-3)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_multipliedBy_overflowTooBig() { + Weeks.of(Integer.MAX_VALUE / 2 + 1).multipliedBy(2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_multipliedBy_overflowTooSmall() { + Weeks.of(Integer.MIN_VALUE / 2 - 1).multipliedBy(2); + } + + //----------------------------------------------------------------------- + public void test_dividedBy() { + Weeks test12 = Weeks.of(12); + assertEquals(Weeks.of(12), test12.dividedBy(1)); + assertEquals(Weeks.of(6), test12.dividedBy(2)); + assertEquals(Weeks.of(4), test12.dividedBy(3)); + assertEquals(Weeks.of(3), test12.dividedBy(4)); + assertEquals(Weeks.of(2), test12.dividedBy(5)); + assertEquals(Weeks.of(2), test12.dividedBy(6)); + assertEquals(Weeks.of(-4), test12.dividedBy(-3)); + } + + public void test_dividedBy_negate() { + Weeks test12 = Weeks.of(12); + assertEquals(Weeks.of(-4), test12.dividedBy(-3)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_dividedBy_divideByZero() { + Weeks.of(1).dividedBy(0); + } + + //----------------------------------------------------------------------- + public void test_negated() { + assertEquals(Weeks.of(0), Weeks.of(0).negated()); + assertEquals(Weeks.of(-12), Weeks.of(12).negated()); + assertEquals(Weeks.of(12), Weeks.of(-12).negated()); + assertEquals(Weeks.of(-Integer.MAX_VALUE), Weeks.of(Integer.MAX_VALUE).negated()); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_negated_overflow() { + Weeks.of(Integer.MIN_VALUE).negated(); + } + + //----------------------------------------------------------------------- + public void test_toString() { + Weeks test5 = Weeks.of(5); + assertEquals("P5W", test5.toString()); + Weeks testM1 = Weeks.of(-1); + assertEquals("P-1W", testM1.toString()); + } + +} diff --git a/src/test/java/org/threeten/extra/TestYears.java b/src/test/java/org/threeten/extra/TestYears.java new file mode 100644 index 0000000..2c283e9 --- /dev/null +++ b/src/test/java/org/threeten/extra/TestYears.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra; + +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 static org.threeten.bp.temporal.ChronoUnit.YEARS; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import org.testng.annotations.Test; +import org.threeten.bp.temporal.TemporalUnit; + +/** + * Test class. + */ +@Test +public class TestYears { + + //----------------------------------------------------------------------- + public void test_isSerializable() { + assertTrue(Serializable.class.isAssignableFrom(Years.class)); + } + + //----------------------------------------------------------------------- + public void test_factoryZeroSingleton() { + assertSame(Years.ZERO, Years.of(0)); + assertSame(Years.ZERO, Years.of(0)); + assertEquals(0, Years.ZERO.getAmount()); + } + + //----------------------------------------------------------------------- + public void test_factoryGetYears() { + assertEquals(1, Years.of(1).getAmount()); + assertEquals(2, Years.of(2).getAmount()); + assertEquals(Integer.MAX_VALUE, Years.of(Integer.MAX_VALUE).getAmount()); + assertEquals(-1, Years.of(-1).getAmount()); + assertEquals(-2, Years.of(-2).getAmount()); + assertEquals(Integer.MIN_VALUE, Years.of(Integer.MIN_VALUE).getAmount()); + } + + //----------------------------------------------------------------------- + public void test_deserializationSingleton() throws Exception { + Years orginal = Years.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); + Years ser = (Years) in.readObject(); + assertSame(Years.ZERO, ser); + } + + //----------------------------------------------------------------------- + public void test_compareTo() { + Years test5 = Years.of(5); + Years test6 = Years.of(6); + assertEquals(0, test5.compareTo(test5)); + assertEquals(-1, test5.compareTo(test6)); + assertEquals(1, test6.compareTo(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_compareTo_null() { + Years test5 = Years.of(5); + test5.compareTo(null); + } + + //----------------------------------------------------------------------- + public void test_isGreaterThan() { + Years test5 = Years.of(5); + Years test6 = Years.of(6); + assertEquals(false, test5.isGreaterThan(test5)); + assertEquals(false, test5.isGreaterThan(test6)); + assertEquals(true, test6.isGreaterThan(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_isGreaterThan_null() { + Years test5 = Years.of(5); + test5.isGreaterThan(null); + } + + //----------------------------------------------------------------------- + public void test_isLessThan() { + Years test5 = Years.of(5); + Years test6 = Years.of(6); + assertEquals(false, test5.isLessThan(test5)); + assertEquals(true, test5.isLessThan(test6)); + assertEquals(false, test6.isLessThan(test5)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_isLessThan_null() { + Years test5 = Years.of(5); + test5.isLessThan(null); + } + + //----------------------------------------------------------------------- + public void test_equals() { + Years test5 = Years.of(5); + Years test6 = Years.of(6); + assertEquals(true, test5.equals(test5)); + assertEquals(false, test5.equals(test6)); + assertEquals(false, test6.equals(test5)); + } + + public void test_equals_null() { + Years test5 = Years.of(5); + assertEquals(false, test5.equals(null)); + } + + public void test_equals_otherClass() { + Years test5 = Years.of(5); + assertEquals(false, test5.equals("")); + } + + //----------------------------------------------------------------------- + public void test_hashCode() { + Years test5 = Years.of(5); + Years test6 = Years.of(6); + assertEquals(true, test5.hashCode() == test5.hashCode()); + assertEquals(false, test5.hashCode() == test6.hashCode()); + } + + //----------------------------------------------------------------------- + public void test_getUnit() { + TemporalUnit unit = Years.of(5).getUnit(); + assertNotNull(unit); + assertEquals(unit, YEARS); + } + + //----------------------------------------------------------------------- + public void test_plus() { + Years test5 = Years.of(5); + assertEquals(Years.of(5), test5.plus(0)); + assertEquals(Years.of(7), test5.plus(2)); + assertEquals(Years.of(3), test5.plus(-2)); + assertEquals(Years.of(Integer.MAX_VALUE), Years.of(Integer.MAX_VALUE - 1).plus(1)); + assertEquals(Years.of(Integer.MIN_VALUE), Years.of(Integer.MIN_VALUE + 1).plus(-1)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_overflowTooBig() { + Years.of(Integer.MAX_VALUE - 1).plus(2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_overflowTooSmall() { + Years.of(Integer.MIN_VALUE + 1).plus(-2); + } + + //----------------------------------------------------------------------- + public void test_plus_Years() { + Years test5 = Years.of(5); + assertEquals(Years.of(5), test5.plus(Years.of(0))); + assertEquals(Years.of(7), test5.plus(Years.of(2))); + assertEquals(Years.of(3), test5.plus(Years.of(-2))); + assertEquals(Years.of(Integer.MAX_VALUE), + Years.of(Integer.MAX_VALUE - 1).plus(Years.of(1))); + assertEquals(Years.of(Integer.MIN_VALUE), + Years.of(Integer.MIN_VALUE + 1).plus(Years.of(-1))); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_Years_overflowTooBig() { + Years.of(Integer.MAX_VALUE - 1).plus(Years.of(2)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_plus_Years_overflowTooSmall() { + Years.of(Integer.MIN_VALUE + 1).plus(Years.of(-2)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_plus_Years_null() { + Years.of(Integer.MIN_VALUE + 1).plus(null); + } + + //----------------------------------------------------------------------- + public void test_minus() { + Years test5 = Years.of(5); + assertEquals(Years.of(5), test5.minus(0)); + assertEquals(Years.of(3), test5.minus(2)); + assertEquals(Years.of(7), test5.minus(-2)); + assertEquals(Years.of(Integer.MAX_VALUE), Years.of(Integer.MAX_VALUE - 1).minus(-1)); + assertEquals(Years.of(Integer.MIN_VALUE), Years.of(Integer.MIN_VALUE + 1).minus(1)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_overflowTooBig() { + Years.of(Integer.MAX_VALUE - 1).minus(-2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_overflowTooSmall() { + Years.of(Integer.MIN_VALUE + 1).minus(2); + } + + //----------------------------------------------------------------------- + public void test_minus_Years() { + Years test5 = Years.of(5); + assertEquals(Years.of(5), test5.minus(Years.of(0))); + assertEquals(Years.of(3), test5.minus(Years.of(2))); + assertEquals(Years.of(7), test5.minus(Years.of(-2))); + assertEquals(Years.of(Integer.MAX_VALUE), + Years.of(Integer.MAX_VALUE - 1).minus(Years.of(-1))); + assertEquals(Years.of(Integer.MIN_VALUE), + Years.of(Integer.MIN_VALUE + 1).minus(Years.of(1))); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_Years_overflowTooBig() { + Years.of(Integer.MAX_VALUE - 1).minus(Years.of(-2)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_minus_Years_overflowTooSmall() { + Years.of(Integer.MIN_VALUE + 1).minus(Years.of(2)); + } + + @Test(expectedExceptions = {NullPointerException.class}) + public void test_minus_Years_null() { + Years.of(Integer.MIN_VALUE + 1).minus(null); + } + + //----------------------------------------------------------------------- + public void test_multipliedBy() { + Years test5 = Years.of(5); + assertEquals(Years.of(0), test5.multipliedBy(0)); + assertEquals(Years.of(5), test5.multipliedBy(1)); + assertEquals(Years.of(10), test5.multipliedBy(2)); + assertEquals(Years.of(15), test5.multipliedBy(3)); + assertEquals(Years.of(-15), test5.multipliedBy(-3)); + } + + public void test_multipliedBy_negate() { + Years test5 = Years.of(5); + assertEquals(Years.of(-15), test5.multipliedBy(-3)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_multipliedBy_overflowTooBig() { + Years.of(Integer.MAX_VALUE / 2 + 1).multipliedBy(2); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_multipliedBy_overflowTooSmall() { + Years.of(Integer.MIN_VALUE / 2 - 1).multipliedBy(2); + } + + //----------------------------------------------------------------------- + public void test_dividedBy() { + Years test12 = Years.of(12); + assertEquals(Years.of(12), test12.dividedBy(1)); + assertEquals(Years.of(6), test12.dividedBy(2)); + assertEquals(Years.of(4), test12.dividedBy(3)); + assertEquals(Years.of(3), test12.dividedBy(4)); + assertEquals(Years.of(2), test12.dividedBy(5)); + assertEquals(Years.of(2), test12.dividedBy(6)); + assertEquals(Years.of(-4), test12.dividedBy(-3)); + } + + public void test_dividedBy_negate() { + Years test12 = Years.of(12); + assertEquals(Years.of(-4), test12.dividedBy(-3)); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_dividedBy_divideByZero() { + Years.of(1).dividedBy(0); + } + + //----------------------------------------------------------------------- + public void test_negated() { + assertEquals(Years.of(0), Years.of(0).negated()); + assertEquals(Years.of(-12), Years.of(12).negated()); + assertEquals(Years.of(12), Years.of(-12).negated()); + assertEquals(Years.of(-Integer.MAX_VALUE), Years.of(Integer.MAX_VALUE).negated()); + } + + @Test(expectedExceptions = {ArithmeticException.class}) + public void test_negated_overflow() { + Years.of(Integer.MIN_VALUE).negated(); + } + + //----------------------------------------------------------------------- + public void test_toString() { + Years test5 = Years.of(5); + assertEquals("P5Y", test5.toString()); + Years testM1 = Years.of(-1); + assertEquals("P-1Y", testM1.toString()); + } + +} diff --git a/src/test/java/org/threeten/extra/chrono/TestCopticChrono.java b/src/test/java/org/threeten/extra/chrono/TestCopticChrono.java new file mode 100644 index 0000000..302b9d6 --- /dev/null +++ b/src/test/java/org/threeten/extra/chrono/TestCopticChrono.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra.chrono; + +import static org.testng.Assert.assertEquals; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import org.threeten.bp.DateTimeException; +import org.threeten.bp.LocalDate; +import org.threeten.bp.LocalDateTime; +import org.threeten.bp.Month; +import org.threeten.bp.chrono.ChronoLocalDate; +import org.threeten.bp.chrono.Chronology; +import org.threeten.bp.temporal.TemporalAdjusters; + +/** + * Test. + */ +@Test +public class TestCopticChrono { + + //----------------------------------------------------------------------- + // Chrono.ofName("Coptic") Lookup by name + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_chrono_byName() { + Chronology Coptic = Chronology.of("Coptic"); + Assert.assertNotNull(Coptic, "The Coptic calendar could not be found byName"); + Assert.assertEquals(Coptic.getId(), "Coptic", "Name mismatch"); + } + + //----------------------------------------------------------------------- + // creation, toLocalDate() + //----------------------------------------------------------------------- + @DataProvider(name="samples") + Object[][] data_samples() { + return new Object[][] { + {CopticChronology.INSTANCE.date(1, 1, 1), LocalDate.of(284, 8, 29)}, + {CopticChronology.INSTANCE.date(1, 1, 2), LocalDate.of(284, 8, 30)}, + {CopticChronology.INSTANCE.date(1, 1, 3), LocalDate.of(284, 8, 31)}, + + {CopticChronology.INSTANCE.date(2, 1, 1), LocalDate.of(285, 8, 29)}, + {CopticChronology.INSTANCE.date(3, 1, 1), LocalDate.of(286, 8, 29)}, + {CopticChronology.INSTANCE.date(3, 13, 6), LocalDate.of(287, 8, 29)}, + {CopticChronology.INSTANCE.date(4, 1, 1), LocalDate.of(287, 8, 30)}, + {CopticChronology.INSTANCE.date(4, 7, 3), LocalDate.of(288, 2, 28)}, + {CopticChronology.INSTANCE.date(4, 7, 4), LocalDate.of(288, 2, 29)}, + {CopticChronology.INSTANCE.date(5, 1, 1), LocalDate.of(288, 8, 29)}, + {CopticChronology.INSTANCE.date(1662, 3, 3), LocalDate.of(1945, 11, 12)}, + {CopticChronology.INSTANCE.date(1728, 10, 28), LocalDate.of(2012, 7, 5)}, + {CopticChronology.INSTANCE.date(1728, 10, 29), LocalDate.of(2012, 7, 6)}, + }; + } + + @Test(dataProvider="samples", groups={"tck"}) + public void test_toLocalDate(ChronoLocalDate coptic, LocalDate iso) { + assertEquals(LocalDate.from(coptic), iso); + } + + @Test(dataProvider="samples", groups={"tck"}) + public void test_fromCalendrical(ChronoLocalDate coptic, LocalDate iso) { + assertEquals(CopticChronology.INSTANCE.date(iso), coptic); + } + + @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, 31}, + {1728, 1, 32}, + + {1728, 12, -1}, + {1728, 12, 0}, + {1728, 12, 31}, + {1728, 12, 32}, + + {1728, 13, -1}, + {1728, 13, 0}, + {1728, 13, 6}, + {1728, 13, 7}, + + {1727, 13, -1}, + {1727, 13, 0}, + {1727, 13, 7}, + {1727, 13, 8}, + }; + } + + @Test(dataProvider="badDates", groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_badDates(int year, int month, int dom) { + CopticChronology.INSTANCE.date(year, month, dom); + } + + //----------------------------------------------------------------------- + // with(WithAdjuster) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjust1() { + ChronoLocalDate base = CopticChronology.INSTANCE.date(1728, 10, 29); + ChronoLocalDate test = base.with(TemporalAdjusters.lastDayOfMonth()); + assertEquals(test, CopticChronology.INSTANCE.date(1728, 10, 30)); + } + + @Test(groups={"tck"}) + public void test_adjust2() { + ChronoLocalDate base = CopticChronology.INSTANCE.date(1728, 13, 2); + ChronoLocalDate test = base.with(TemporalAdjusters.lastDayOfMonth()); + assertEquals(test, CopticChronology.INSTANCE.date(1728, 13, 5)); + } + + //----------------------------------------------------------------------- + // CopticDate.with(Local*) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_adjust_toLocalDate() { + ChronoLocalDate coptic = CopticChronology.INSTANCE.date(1726, 1, 4); + ChronoLocalDate test = coptic.with(LocalDate.of(2012, 7, 6)); + assertEquals(test, CopticChronology.INSTANCE.date(1728, 10, 29)); + } + + @Test(groups={"tck"}, expectedExceptions=DateTimeException.class) + public void test_adjust_toMonth() { + ChronoLocalDate coptic = CopticChronology.INSTANCE.date(1726, 1, 4); + coptic.with(Month.APRIL); + } + + //----------------------------------------------------------------------- + // LocalDate.with(CopticDate) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_LocalDate_adjustToCopticDate() { + ChronoLocalDate coptic = CopticChronology.INSTANCE.date(1728, 10, 29); + LocalDate test = LocalDate.MIN.with(coptic); + assertEquals(test, LocalDate.of(2012, 7, 6)); + } + + @Test(groups={"tck"}) + public void test_LocalDateTime_adjustToCopticDate() { + ChronoLocalDate coptic = CopticChronology.INSTANCE.date(1728, 10, 29); + LocalDateTime test = LocalDateTime.MIN.with(coptic); + assertEquals(test, LocalDateTime.of(2012, 7, 6, 0, 0)); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + @DataProvider(name="toString") + Object[][] data_toString() { + return new Object[][] { + {CopticChronology.INSTANCE.date(1, 1, 1), "Coptic AM 1-01-01"}, + {CopticChronology.INSTANCE.date(1728, 10, 28), "Coptic AM 1728-10-28"}, + {CopticChronology.INSTANCE.date(1728, 10, 29), "Coptic AM 1728-10-29"}, + {CopticChronology.INSTANCE.date(1727, 13, 5), "Coptic AM 1727-13-05"}, + {CopticChronology.INSTANCE.date(1727, 13, 6), "Coptic AM 1727-13-06"}, + }; + } + + @Test(dataProvider="toString", groups={"tck"}) + public void test_toString(ChronoLocalDate coptic, String expected) { + assertEquals(coptic.toString(), expected); + } + +} diff --git a/src/test/java/org/threeten/extra/scale/MockUTCRulesAlwaysLeap.java b/src/test/java/org/threeten/extra/scale/MockUTCRulesAlwaysLeap.java new file mode 100644 index 0000000..0b77572 --- /dev/null +++ b/src/test/java/org/threeten/extra/scale/MockUTCRulesAlwaysLeap.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra.scale; + + + +/** + * Mock rules that always returns a leap second. + */ +public class MockUTCRulesAlwaysLeap extends UTCRules { + + @Override + public String getName() { + return "Mock"; + } + + @Override + public int getLeapSecondAdjustment(long mjDay) { + return 1; // always leap + } + + @Override + public int getTAIOffset(long mjDay) { + return (int) mjDay; + } + + @Override + public long[] getLeapSecondDates() { + return new long[0]; + } + + @Override + public TAIInstant convertToTAI(UTCInstant utcInstant) { + return null; + } + + @Override + public UTCInstant convertToUTC(TAIInstant taiInstant) { + return null; + } + +} diff --git a/src/test/java/org/threeten/extra/scale/MockUTCRulesLeapOn1000.java b/src/test/java/org/threeten/extra/scale/MockUTCRulesLeapOn1000.java new file mode 100644 index 0000000..5f5af57 --- /dev/null +++ b/src/test/java/org/threeten/extra/scale/MockUTCRulesLeapOn1000.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra.scale; + + + +/** + * Mock rules that returns a leap second on day 1000. + */ +public class MockUTCRulesLeapOn1000 extends UTCRules { + + @Override + public String getName() { + return "Mock1000"; + } + + @Override + public int getLeapSecondAdjustment(long mjDay) { + return (mjDay == 1000 ? 1 : 0); + } + + @Override + public int getTAIOffset(long mjDay) { + return (mjDay <= 1000 ? 10 : 11); + } + + @Override + public long[] getLeapSecondDates() { + return new long[] {1000}; + } + + @Override + public TAIInstant convertToTAI(UTCInstant utcInstant) { + return null; + } + + @Override + public UTCInstant convertToUTC(TAIInstant taiInstant) { + return null; + } + +} diff --git a/src/test/java/org/threeten/extra/scale/TestTAIInstant.java b/src/test/java/org/threeten/extra/scale/TestTAIInstant.java new file mode 100644 index 0000000..7d12326 --- /dev/null +++ b/src/test/java/org/threeten/extra/scale/TestTAIInstant.java @@ -0,0 +1,889 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra.scale; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import org.threeten.bp.Duration; +import org.threeten.bp.Instant; +import org.threeten.bp.format.DateTimeParseException; + +/** + * Test TAIInstant. + */ +@Test +public class TestTAIInstant { + + //----------------------------------------------------------------------- + @Test(groups={"implementation"}) + public void test_interfaces() { + assertTrue(Serializable.class.isAssignableFrom(Duration.class)); + assertTrue(Comparable.class.isAssignableFrom(Duration.class)); + } + + //----------------------------------------------------------------------- + // serialization + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_deserialization() throws Exception { + TAIInstant orginal = TAIInstant.ofTAISeconds(2, 3); + 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); + TAIInstant ser = (TAIInstant) in.readObject(); + assertEquals(TAIInstant.ofTAISeconds(2, 3), ser); + } + +// //----------------------------------------------------------------------- +// // nowClock() +// //----------------------------------------------------------------------- +// @Test(expectedExceptions=NullPointerException.class) +// public void now_Clock_nullClock() { +// TAIInstant.now(null); +// } +// +// public void now_TimeSource_allSecsInDay_utc() { +// for (int i = 0; i < (2 * 24 * 60 * 60); i++) { +// TAIInstant expected = TAIInstant.ofEpochSecond(i).plusNanos(123456789L); +// TimeSource clock = TimeSource.fixed(expected); +// TAIInstant test = TAIInstant.now(clock); +// assertEquals(test, expected); +// } +// } +// +// public void now_TimeSource_allSecsInDay_beforeEpoch() { +// for (int i =-1; i >= -(24 * 60 * 60); i--) { +// TAIInstant expected = TAIInstant.ofEpochSecond(i).plusNanos(123456789L); +// TimeSource clock = TimeSource.fixed(expected); +// TAIInstant test = TAIInstant.now(clock); +// assertEquals(test, expected); +// } +// } +// +// //----------------------------------------------------------------------- +// // nowSystemClock() +// //----------------------------------------------------------------------- +// public void nowSystemClock() { +// TAIInstant expected = TAIInstant.now(TimeSource.system()); +// TAIInstant test = TAIInstant.nowSystemClock(); +// BigInteger diff = test.toEpochNano().subtract(expected.toEpochNano()).abs(); +// if (diff.compareTo(BigInteger.valueOf(100000000)) >= 0) { +// // may be date change +// expected = TAIInstant.now(TimeSource.system()); +// test = TAIInstant.nowSystemClock(); +// diff = test.toEpochNano().subtract(expected.toEpochNano()).abs(); +// } +// assertTrue(diff.compareTo(BigInteger.valueOf(100000000)) < 0); // less than 0.1 secs +// } + + //----------------------------------------------------------------------- + // ofTAISeconds(long,long) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_ofTAISecondslong_long() { + for (long i = -2; i <= 2; i++) { + for (int j = 0; j < 10; j++) { + TAIInstant t = TAIInstant.ofTAISeconds(i, j); + assertEquals(t.getTAISeconds(), i); + assertEquals(t.getNano(), j); + } + for (int j = -10; j < 0; j++) { + TAIInstant t = TAIInstant.ofTAISeconds(i, j); + assertEquals(t.getTAISeconds(), i - 1); + assertEquals(t.getNano(), j + 1000000000); + } + for (int j = 999999990; j < 1000000000; j++) { + TAIInstant t = TAIInstant.ofTAISeconds(i, j); + assertEquals(t.getTAISeconds(), i); + assertEquals(t.getNano(), j); + } + } + } + + @Test(groups={"tck"}) + public void factory_ofTAISeconds_long_long_nanosNegativeAdjusted() { + TAIInstant test = TAIInstant.ofTAISeconds(2L, -1); + assertEquals(test.getTAISeconds(), 1); + assertEquals(test.getNano(), 999999999); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void factory_ofTAISeconds_long_long_tooBig() { + TAIInstant.ofTAISeconds(Long.MAX_VALUE, 1000000000); + } + + //----------------------------------------------------------------------- + // of(Instant) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_Instant() { + TAIInstant test = TAIInstant.of(Instant.ofEpochSecond(0, 2)); + assertEquals(test.getTAISeconds(), (40587L - 36204) * 24 * 60 * 60 + 10); //((1970 - 1958) * 365 + 3) * 24 * 60 * 60 + 10); + assertEquals(test.getNano(), 2); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_Instant_null() { + TAIInstant.of((Instant) null); + } + + //----------------------------------------------------------------------- + // of(UTCInstant) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_UTCInstant() { + for (int i = -1000; i < 1000; i++) { + for (int j = 0; j < 10; j++) { + TAIInstant test = TAIInstant.of(UTCInstant.ofModifiedJulianDay(36204 + i, j * 1000000000L + 2L)); + assertEquals(test.getTAISeconds(), i * 24 * 60 * 60 + j + 10); + assertEquals(test.getNano(), 2); + } + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_UTCInstant_null() { + TAIInstant.of((UTCInstant) null); + } + + //----------------------------------------------------------------------- + // parse(String) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_parse_String() { + for (int i = -1000; i < 1000; i++) { + for (int j = 900000000; j < 990000000; j += 10000000) { + String str = i + "." + j + "s(TAI)"; + TAIInstant test = TAIInstant.parse(str); + assertEquals(test.getTAISeconds(), i); + assertEquals(test.getNano(), j); + } + } + } + + @DataProvider(name="BadParse") + Object[][] provider_badParse() { + return new Object[][] { + {"A.123456789s(TAI)"}, + {"123.12345678As(TAI)"}, + {"123.123456789"}, + {"123.123456789s"}, + {"+123.123456789s(TAI)"}, + {"-123.123s(TAI)"}, + }; + } + @Test(dataProvider="BadParse", expectedExceptions=DateTimeParseException.class, groups={"tck"}) + public void factory_parse_String_invalid(String str) { + TAIInstant.parse(str); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_parse_String_null() { + TAIInstant.parse((String) null); + } + + //----------------------------------------------------------------------- + // withTAISeconds() + //----------------------------------------------------------------------- + @DataProvider(name="withTAISeconds") + Object[][] provider_withTAISeconds() { + return new Object[][] { + {0L, 12345L, 1L, 1L, 12345L}, + {0L, 12345L, -1L, -1L, 12345L}, + {7L, 12345L, 2L, 2L, 12345L}, + {7L, 12345L, -2L, -2L, 12345L}, + {-99L, 12345L, 3L, 3L, 12345L}, + {-99L, 12345L, -3L, -3L, 12345L}, + }; + } + + @Test(dataProvider="withTAISeconds", groups={"tck"}) + public void test_withTAISeconds(long tai, long nanos, long newTai, Long expectedTai, Long expectedNanos) { + TAIInstant i = TAIInstant.ofTAISeconds(tai, nanos).withTAISeconds(newTai); + assertEquals(i.getTAISeconds(), expectedTai.longValue()); + assertEquals(i.getNano(), expectedNanos.longValue()); + } + + //----------------------------------------------------------------------- + // withNano() + //----------------------------------------------------------------------- + @DataProvider(name="withNanoOfSecond") + Object[][] provider_withNano() { + return new Object[][] { + {0L, 12345L, 1, 0L, 1L}, + {7L, 12345L, 2, 7L, 2L}, + {-99L, 12345L, 3, -99L, 3L}, + {-99L, 12345L, 999999999, -99L, 999999999L}, + {-99L, 12345L, -1, null, null}, + {-99L, 12345L, 1000000000, null, null}, + }; + } + + @Test(dataProvider="withNanoOfSecond", groups={"tck"}) + public void test_withNano(long tai, long nanos, int newNano, Long expectedTai, Long expectedNanos) { + TAIInstant i = TAIInstant.ofTAISeconds(tai, nanos); + if (expectedTai != null) { + i = i.withNano(newNano); + assertEquals(i.getTAISeconds(), expectedTai.longValue()); + assertEquals(i.getNano(), expectedNanos.longValue()); + } else { + try { + i = i.withNano(newNano); + fail(); + } catch (IllegalArgumentException ex) { + // expected + } + } + } + + //----------------------------------------------------------------------- + // plus(Duration) + //----------------------------------------------------------------------- + @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 test_plus(long seconds, int nanos, long plusSeconds, int plusNanos, long expectedSeconds, int expectedNanoOfSecond) { + TAIInstant i = TAIInstant.ofTAISeconds(seconds, nanos).plus(Duration.ofSeconds(plusSeconds, plusNanos)); + assertEquals(i.getTAISeconds(), expectedSeconds); + assertEquals(i.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_plus_overflowTooBig() { + TAIInstant i = TAIInstant.ofTAISeconds(Long.MAX_VALUE, 999999999); + i.plus(Duration.ofSeconds(0, 1)); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_plus_overflowTooSmall() { + TAIInstant i = TAIInstant.ofTAISeconds(Long.MIN_VALUE, 0); + i.plus(Duration.ofSeconds(-1, 999999999)); + } + + //----------------------------------------------------------------------- + // minus(Duration) + //----------------------------------------------------------------------- + @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 test_minus(long seconds, int nanos, long minusSeconds, int minusNanos, long expectedSeconds, int expectedNanoOfSecond) { + TAIInstant i = TAIInstant.ofTAISeconds(seconds, nanos).minus(Duration.ofSeconds(minusSeconds, minusNanos)); + assertEquals(i.getTAISeconds(), expectedSeconds); + assertEquals(i.getNano(), expectedNanoOfSecond); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_minus_overflowTooSmall() { + TAIInstant i = TAIInstant.ofTAISeconds(Long.MIN_VALUE, 0); + i.minus(Duration.ofSeconds(0, 1)); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_minus_overflowTooBig() { + TAIInstant i = TAIInstant.ofTAISeconds(Long.MAX_VALUE, 999999999); + i.minus(Duration.ofSeconds(-1, 999999999)); + } + + //----------------------------------------------------------------------- + // durationUntil() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_durationUntil_fifteenSeconds() { + TAIInstant tai1 = TAIInstant.ofTAISeconds(10, 0); + TAIInstant tai2 = TAIInstant.ofTAISeconds(25, 0); + Duration test = tai1.durationUntil(tai2); + assertEquals(test.getSeconds(), 15); + assertEquals(test.getNano(), 0); + } + + @Test(groups={"tck"}) + public void test_durationUntil_twoNanos() { + TAIInstant tai1 = TAIInstant.ofTAISeconds(4, 5); + TAIInstant tai2 = TAIInstant.ofTAISeconds(4, 7); + Duration test = tai1.durationUntil(tai2); + assertEquals(test.getSeconds(), 0); + assertEquals(test.getNano(), 2); + } + + @Test(groups={"tck"}) + public void test_durationUntil_twoNanosNegative() { + TAIInstant tai1 = TAIInstant.ofTAISeconds(4, 9); + TAIInstant tai2 = TAIInstant.ofTAISeconds(4, 7); + Duration test = tai1.durationUntil(tai2); + assertEquals(test.getSeconds(), -1); + assertEquals(test.getNano(), 999999998); + } + + //----------------------------------------------------------------------- + // toUTCInstant() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toUTCInstant() { + for (int i = -1000; i < 1000; i++) { + for (int j = 0; j < 10; j++) { + UTCInstant expected = UTCInstant.ofModifiedJulianDay(36204 + i, j * 1000000000L + 2L); + TAIInstant test = TAIInstant.ofTAISeconds(i * 24 * 60 * 60 + j + 10, 2); + assertEquals(test.toUTCInstant(), expected); + } + } + } + + //----------------------------------------------------------------------- + // toInstant() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toInstant() { + for (int i = -1000; i < 1000; i++) { + for (int j = 0; j < 10; j++) { + Instant expected = Instant.ofEpochSecond(-378691200L + i * 24 * 60 * 60 + j).plusNanos(2); + TAIInstant test = TAIInstant.ofTAISeconds(i * 24 * 60 * 60 + j + 10, 2); + assertEquals(test.toInstant(), expected, "Loop " + i + " " + j); + } + } + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_comparisons() { + doTest_comparisons_TAIInstant( + TAIInstant.ofTAISeconds(-2L, 0), + TAIInstant.ofTAISeconds(-2L, 999999998), + TAIInstant.ofTAISeconds(-2L, 999999999), + TAIInstant.ofTAISeconds(-1L, 0), + TAIInstant.ofTAISeconds(-1L, 1), + TAIInstant.ofTAISeconds(-1L, 999999998), + TAIInstant.ofTAISeconds(-1L, 999999999), + TAIInstant.ofTAISeconds(0L, 0), + TAIInstant.ofTAISeconds(0L, 1), + TAIInstant.ofTAISeconds(0L, 2), + TAIInstant.ofTAISeconds(0L, 999999999), + TAIInstant.ofTAISeconds(1L, 0), + TAIInstant.ofTAISeconds(2L, 0) + ); + } + + void doTest_comparisons_TAIInstant(TAIInstant... instants) { + for (int i = 0; i < instants.length; i++) { + TAIInstant a = instants[i]; + for (int j = 0; j < instants.length; j++) { + TAIInstant b = instants[j]; + if (i < j) { + assertEquals(a.compareTo(b) < 0, true, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else if (i > j) { + assertEquals(a.compareTo(b) > 0, true, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else { + assertEquals(a.compareTo(b), 0, a + " <=> " + b); + assertEquals(a.equals(b), true, a + " <=> " + b); + } + } + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_compareTo_ObjectNull() { + TAIInstant a = TAIInstant.ofTAISeconds(0L, 0); + a.compareTo(null); + } + + @Test(expectedExceptions=ClassCastException.class, groups={"tck"}) + @SuppressWarnings({"unchecked", "rawtypes"}) + public void test_compareToNonTAIInstant() { + Comparable c = TAIInstant.ofTAISeconds(0L, 2); + c.compareTo(new Object()); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_equals() { + TAIInstant test5a = TAIInstant.ofTAISeconds(5L, 20); + TAIInstant test5b = TAIInstant.ofTAISeconds(5L, 20); + TAIInstant test5n = TAIInstant.ofTAISeconds(5L, 30); + TAIInstant test6 = TAIInstant.ofTAISeconds(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() { + TAIInstant test5 = TAIInstant.ofTAISeconds(5L, 20); + assertEquals(test5.equals(null), false); + } + + @Test(groups={"tck"}) + public void test_equals_otherClass() { + TAIInstant test5 = TAIInstant.ofTAISeconds(5L, 20); + assertEquals(test5.equals(""), false); + } + + //----------------------------------------------------------------------- + // hashCode() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_hashCode() { + TAIInstant test5a = TAIInstant.ofTAISeconds(5L, 20); + TAIInstant test5b = TAIInstant.ofTAISeconds(5L, 20); + TAIInstant test5n = TAIInstant.ofTAISeconds(5L, 30); + TAIInstant test6 = TAIInstant.ofTAISeconds(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() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString_standard() { + TAIInstant t = TAIInstant.ofTAISeconds(123L, 123456789); + assertEquals(t.toString(), "123.123456789s(TAI)"); + } + + @Test(groups={"tck"}) + public void test_toString_negative() { + TAIInstant t = TAIInstant.ofTAISeconds(-123L, 123456789); + assertEquals(t.toString(), "-123.123456789s(TAI)"); + } + + @Test(groups={"tck"}) + public void test_toString_zeroDecimal() { + TAIInstant t = TAIInstant.ofTAISeconds(0L, 567); + assertEquals(t.toString(), "0.000000567s(TAI)"); + } + +} diff --git a/src/test/java/org/threeten/extra/scale/TestUTCInstant.java b/src/test/java/org/threeten/extra/scale/TestUTCInstant.java new file mode 100644 index 0000000..3664a12 --- /dev/null +++ b/src/test/java/org/threeten/extra/scale/TestUTCInstant.java @@ -0,0 +1,682 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra.scale; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import org.threeten.bp.Duration; +import org.threeten.bp.Instant; +import org.threeten.bp.LocalDate; +import org.threeten.bp.temporal.JulianFields; + +/** + * Test UTCInstant. + */ +@Test +public class TestUTCInstant { + + private static final long SECS_PER_DAY = 24L * 60 * 60; + private static final long NANOS_PER_SEC = 1000000000L; + + static { + UTCRules.registerSystemLeapSecond(LocalDate.of(1972, 6, 30).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1972, 12, 31).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1973, 12, 31).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1974, 12, 31).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1975, 12, 31).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1976, 12, 31).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1977, 12, 31).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1978, 12, 31).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1979, 12, 31).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + + UTCRules.registerSystemLeapSecond(LocalDate.of(1981, 6, 30).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1982, 6, 30).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1983, 6, 30).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1985, 6, 30).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1987, 12, 31).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1989, 12, 31).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1990, 12, 31).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + + UTCRules.registerSystemLeapSecond(LocalDate.of(1992, 6, 30).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1993, 6, 30).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1994, 6, 30).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1995, 12, 31).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1997, 6, 30).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(1998, 12, 31).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + + UTCRules.registerSystemLeapSecond(LocalDate.of(2005, 12, 31).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(2008, 12, 31).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + UTCRules.registerSystemLeapSecond(LocalDate.of(2012, 6, 30).getLong(JulianFields.MODIFIED_JULIAN_DAY), 1); + } + + static void dummyStaticMethod() { + // forces data above to be loaded + } + + //----------------------------------------------------------------------- + @Test(groups={"implementation"}) + public void test_interfaces() { + assertTrue(Serializable.class.isAssignableFrom(Duration.class)); + assertTrue(Comparable.class.isAssignableFrom(Duration.class)); + } + + //----------------------------------------------------------------------- + // serialization + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_deserialization() throws Exception { + UTCInstant orginal = UTCInstant.ofModifiedJulianDay(2, 3); + 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); + UTCInstant ser = (UTCInstant) in.readObject(); + assertEquals(UTCInstant.ofModifiedJulianDay(2, 3), ser); + } + +// //----------------------------------------------------------------------- +// // nowClock() +// //----------------------------------------------------------------------- +// @Test(expectedExceptions=NullPointerException.class) +// public void now_Clock_nullClock() { +// TAIInstant.now(null); +// } +// +// public void now_TimeSource_allSecsInDay_utc() { +// for (int i = 0; i < (2 * 24 * 60 * 60); i++) { +// TAIInstant expected = TAIInstant.ofEpochSecond(i).plusNanos(123456789L); +// TimeSource clock = TimeSource.fixed(expected); +// TAIInstant test = TAIInstant.now(clock); +// assertEquals(test, expected); +// } +// } +// +// public void now_TimeSource_allSecsInDay_beforeEpoch() { +// for (int i =-1; i >= -(24 * 60 * 60); i--) { +// TAIInstant expected = TAIInstant.ofEpochSecond(i).plusNanos(123456789L); +// TimeSource clock = TimeSource.fixed(expected); +// TAIInstant test = TAIInstant.now(clock); +// assertEquals(test, expected); +// } +// } +// +// //----------------------------------------------------------------------- +// // nowSystemClock() +// //----------------------------------------------------------------------- +// public void nowSystemClock() { +// TAIInstant expected = TAIInstant.now(TimeSource.system()); +// TAIInstant test = TAIInstant.nowSystemClock(); +// BigInteger diff = test.toEpochNano().subtract(expected.toEpochNano()).abs(); +// if (diff.compareTo(BigInteger.valueOf(100000000)) >= 0) { +// // may be date change +// expected = TAIInstant.now(TimeSource.system()); +// test = TAIInstant.nowSystemClock(); +// diff = test.toEpochNano().subtract(expected.toEpochNano()).abs(); +// } +// assertTrue(diff.compareTo(BigInteger.valueOf(100000000)) < 0); // less than 0.1 secs +// } + + //----------------------------------------------------------------------- + // ofModififiedJulianDay(long,long) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_ofModifiedJulianDay_long_long() { + for (long i = -2; i <= 2; i++) { + for (int j = 0; j < 10; j++) { + UTCInstant t = UTCInstant.ofModifiedJulianDay(i, j); + assertEquals(t.getModifiedJulianDay(), i); + assertEquals(t.getNanoOfDay(), j); + assertEquals(t.getRules(), UTCRules.system()); + assertEquals(t.isLeapSecond(), false); + } + } + } + + @Test(groups={"tck"}) + public void factory_ofModifiedJulianDay_long_long_setupLeap() { + MockUTCRulesAlwaysLeap mockRules = new MockUTCRulesAlwaysLeap(); + UTCInstant t = UTCInstant.ofModifiedJulianDay(41683 - 1, SECS_PER_DAY * NANOS_PER_SEC + 2, mockRules); + assertEquals(t.getModifiedJulianDay(), 41683 - 1); + assertEquals(t.getNanoOfDay(), SECS_PER_DAY * NANOS_PER_SEC + 2); + assertEquals(t.getRules(), mockRules); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void factory_ofModifiedJulianDay_long_long_nanosNegative() { + UTCInstant.ofModifiedJulianDay(2L, -1); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void factory_ofModifiedJulianDay_long_long_nanosTooBigNotLeapDay() { + UTCInstant.ofModifiedJulianDay(2L, SECS_PER_DAY * NANOS_PER_SEC); + } + + //----------------------------------------------------------------------- + // ofModififiedJulianDay(long,long,Rules) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_ofModifiedJulianDay_long_long_Rules() { + MockUTCRulesAlwaysLeap mockRules = new MockUTCRulesAlwaysLeap(); + for (long i = -2; i <= 2; i++) { + for (int j = 0; j < 10; j++) { + UTCInstant t = UTCInstant.ofModifiedJulianDay(i, j, mockRules); + assertEquals(t.getModifiedJulianDay(), i); + assertEquals(t.getNanoOfDay(), j); + assertEquals(t.getRules(), mockRules); + assertEquals(t.isLeapSecond(), false); + } + } + } + + @Test(groups={"tck"}) + public void factory_ofModifiedJulianDay_long_long_Rules_setupLeap() { + MockUTCRulesAlwaysLeap mockRules = new MockUTCRulesAlwaysLeap(); + UTCInstant t = UTCInstant.ofModifiedJulianDay(0, SECS_PER_DAY * NANOS_PER_SEC + 2, mockRules); + assertEquals(t.getModifiedJulianDay(), 0); + assertEquals(t.getNanoOfDay(), SECS_PER_DAY * NANOS_PER_SEC + 2); + assertEquals(t.getRules(), mockRules); + assertEquals(t.isLeapSecond(), true); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void factory_ofModifiedJulianDay_long_long_Rules_nanosNegative() { + MockUTCRulesAlwaysLeap mockRules = new MockUTCRulesAlwaysLeap(); + UTCInstant.ofModifiedJulianDay(2L, -1, mockRules); + } + + @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"}) + public void factory_ofModifiedJulianDay_long_long_Rules_nanosTooBigNotDoubleLeapDay() { + MockUTCRulesAlwaysLeap mockRules = new MockUTCRulesAlwaysLeap(); + UTCInstant.ofModifiedJulianDay(2L, (SECS_PER_DAY + 1) * NANOS_PER_SEC, mockRules); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_ofModifiedJulianDay_long_long_Rules_null() { + UTCInstant.ofModifiedJulianDay(0, 0, (UTCRules) null); + } + + //----------------------------------------------------------------------- + // of(Instant) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_Instant() { + UTCInstant test = UTCInstant.of(Instant.ofEpochSecond(0, 2)); // 1970-01-01 + assertEquals(test.getModifiedJulianDay(), 40587); + assertEquals(test.getNanoOfDay(), 2); + assertEquals(test.getRules(), UTCRules.system()); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_Instant_null() { + UTCInstant.of((Instant) null); + } + + //----------------------------------------------------------------------- + // of(Instant, LeapSecondRules) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_Instant_Rules() { + MockUTCRulesAlwaysLeap mockRules = new MockUTCRulesAlwaysLeap(); + UTCInstant test = UTCInstant.of(Instant.ofEpochSecond(0, 2), mockRules); // 1970-01-01 + assertEquals(test.getModifiedJulianDay(), 40587); + assertEquals(test.getNanoOfDay(), 2); + assertEquals(test.getRules(), mockRules); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_Instant_Rules_null() { + UTCInstant.of(Instant.ofEpochSecond(0, 2), (UTCRules) null); + } + + //----------------------------------------------------------------------- + // of(TAIInstant) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_TAIInstant() { + for (int i = -1000; i < 1000; i++) { + for (int j = 0; j < 10; j++) { + UTCInstant expected = UTCInstant.ofModifiedJulianDay(36204 + i, j * NANOS_PER_SEC + 2L); + TAIInstant tai = TAIInstant.ofTAISeconds(i * SECS_PER_DAY + j + 10, 2); + assertEquals(UTCInstant.of(tai), expected); + } + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_TAIInstant_null() { + UTCInstant.of((TAIInstant) null); + } + + //----------------------------------------------------------------------- + // of(TAIInstant, LeapSecondRules) + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void factory_of_TAIInstant_Rules() { + TAIInstant tai = TAIInstant.ofTAISeconds(2 * SECS_PER_DAY + 10, 2); + UTCInstant test = UTCInstant.of(tai, UTCRules.system()); + assertEquals(test.getModifiedJulianDay(), 36204 + 2); + assertEquals(test.getNanoOfDay(), 2); + assertEquals(test.getRules(), UTCRules.system()); + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void factory_of_TAIInstant_Rules_null() { + UTCInstant.of(TAIInstant.ofTAISeconds(0, 2), (UTCRules) null); + } + + //----------------------------------------------------------------------- + // withModifiedJulianDay() + //----------------------------------------------------------------------- + @DataProvider(name="WithModifiedJulianDay") + Object[][] provider_withModifiedJulianDay() { + return new Object[][] { + {0L, 12345L, 1L, 1L, 12345L}, + {0L, 12345L, -1L, -1L, 12345L}, + {7L, 12345L, 2L, 2L, 12345L}, + {7L, 12345L, -2L, -2L, 12345L}, + {-99L, 12345L, 3L, 3L, 12345L}, + {-99L, 12345L, -3L, -3L, 12345L}, + {1000L, NANOS_PER_SEC * SECS_PER_DAY, 999L, null, null}, + {1000L, NANOS_PER_SEC * SECS_PER_DAY, 1000L, 1000L, NANOS_PER_SEC * SECS_PER_DAY}, + {1000L, NANOS_PER_SEC * SECS_PER_DAY, 1001L, null, null}, + }; + } + + @Test(dataProvider="WithModifiedJulianDay") + public void test_withModifiedJulianDay(long mjd, long nanos, long newMjd, Long expectedMjd, Long expectedNanos) { + UTCInstant i = UTCInstant.ofModifiedJulianDay(mjd, nanos, new MockUTCRulesLeapOn1000()); + if (expectedMjd != null) { + i = i.withModifiedJulianDay(newMjd); + assertEquals(i.getModifiedJulianDay(), expectedMjd.longValue()); + assertEquals(i.getNanoOfDay(), expectedNanos.longValue()); + } else { + try { + i = i.withModifiedJulianDay(newMjd); + fail(); + } catch (IllegalArgumentException ex) { + // expected + } + } + } + + //----------------------------------------------------------------------- + // withNanoOfDay() + //----------------------------------------------------------------------- + @DataProvider(name="WithNanoOfDay") + Object[][] provider_withNanoOfDay() { + return new Object[][] { + {0L, 12345L, 1L, 0L, 1L}, + {0L, 12345L, -1L, null, null}, + {7L, 12345L, 2L, 7L, 2L}, + {-99L, 12345L, 3L, -99L, 3L}, + {1000L, NANOS_PER_SEC * SECS_PER_DAY, NANOS_PER_SEC * SECS_PER_DAY - 1, 1000L, NANOS_PER_SEC * SECS_PER_DAY - 1}, + }; + } + + @Test(dataProvider="WithNanoOfDay", groups={"tck"}) + public void test_withNanoOfDay(long mjd, long nanos, long newNanoOfDay, Long expectedMjd, Long expectedNanos) { + UTCInstant i = UTCInstant.ofModifiedJulianDay(mjd, nanos, new MockUTCRulesLeapOn1000()); + if (expectedMjd != null) { + i = i.withNanoOfDay(newNanoOfDay); + assertEquals(i.getModifiedJulianDay(), expectedMjd.longValue()); + assertEquals(i.getNanoOfDay(), expectedNanos.longValue()); + } else { + try { + i = i.withNanoOfDay(newNanoOfDay); + fail(); + } catch (IllegalArgumentException ex) { + // expected + } + } + } + + //----------------------------------------------------------------------- + // plus(Duration) + //----------------------------------------------------------------------- + @DataProvider(name="Plus") + Object[][] provider_plus() { + return new Object[][] { + {0, 0, -2 * SECS_PER_DAY, 5, -2, 5}, + {0, 0, -1 * SECS_PER_DAY, 1, -1, 1}, + {0, 0, -1 * SECS_PER_DAY, 0, -1, 0}, + {0, 0, 0, -2, -1, SECS_PER_DAY * NANOS_PER_SEC - 2}, + {0, 0, 0, -1, -1, SECS_PER_DAY * NANOS_PER_SEC - 1}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, 0, 1}, + {0, 0, 0, 2, 0, 2}, + {0, 0, 1, 0, 0, 1 * NANOS_PER_SEC}, + {0, 0, 2, 0, 0, 2 * NANOS_PER_SEC}, + {0, 0, 3, 333333333, 0, 3 * NANOS_PER_SEC + 333333333}, + {0, 0, 1 * SECS_PER_DAY, 0, 1, 0}, + {0, 0, 1 * SECS_PER_DAY, 1, 1, 1}, + {0, 0, 2 * SECS_PER_DAY, 5, 2, 5}, + + {1, 0, -2 * SECS_PER_DAY, 5, -1, 5}, + {1, 0, -1 * SECS_PER_DAY, 1, 0, 1}, + {1, 0, -1 * SECS_PER_DAY, 0, 0, 0}, + {1, 0, 0, -2, 0, SECS_PER_DAY * NANOS_PER_SEC - 2}, + {1, 0, 0, -1, 0, SECS_PER_DAY * NANOS_PER_SEC - 1}, + {1, 0, 0, 0, 1, 0}, + {1, 0, 0, 1, 1, 1}, + {1, 0, 0, 2, 1, 2}, + {1, 0, 1, 0, 1, 1 * NANOS_PER_SEC}, + {1, 0, 2, 0, 1, 2 * NANOS_PER_SEC}, + {1, 0, 3, 333333333, 1, 3 * NANOS_PER_SEC + 333333333}, + {1, 0, 1 * SECS_PER_DAY, 0, 2, 0}, + {1, 0, 1 * SECS_PER_DAY, 1, 2, 1}, + {1, 0, 2 * SECS_PER_DAY, 5, 3, 5}, + }; + } + + @Test(dataProvider="Plus", groups={"tck"}) + public void test_plus(long mjd, long nanos, long plusSeconds, int plusNanos, long expectedMjd, long expectedNanos) { + UTCInstant i = UTCInstant.ofModifiedJulianDay(mjd, nanos).plus(Duration.ofSeconds(plusSeconds, plusNanos)); + assertEquals(i.getModifiedJulianDay(), expectedMjd); + assertEquals(i.getNanoOfDay(), expectedNanos); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_plus_overflowTooBig() { + UTCInstant i = UTCInstant.ofModifiedJulianDay(Long.MAX_VALUE, SECS_PER_DAY * NANOS_PER_SEC - 1); + i.plus(Duration.ofNanos(1)); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_plus_overflowTooSmall() { + UTCInstant i = UTCInstant.ofModifiedJulianDay(Long.MIN_VALUE, 0); + i.plus(Duration.ofNanos(-1)); + } + + //----------------------------------------------------------------------- + // minus(Duration) + //----------------------------------------------------------------------- + @DataProvider(name="Minus") + Object[][] provider_minus() { + return new Object[][] { + {0, 0, 2 * SECS_PER_DAY, -5, -2, 5}, + {0, 0, 1 * SECS_PER_DAY, -1, -1, 1}, + {0, 0, 1 * SECS_PER_DAY, 0, -1, 0}, + {0, 0, 0, 2, -1, SECS_PER_DAY * NANOS_PER_SEC - 2}, + {0, 0, 0, 1, -1, SECS_PER_DAY * NANOS_PER_SEC - 1}, + {0, 0, 0, 0, 0, 0}, + {0, 0, 0, -1, 0, 1}, + {0, 0, 0, -2, 0, 2}, + {0, 0, -1, 0, 0, 1 * NANOS_PER_SEC}, + {0, 0, -2, 0, 0, 2 * NANOS_PER_SEC}, + {0, 0, -3, -333333333, 0, 3 * NANOS_PER_SEC + 333333333}, + {0, 0, -1 * SECS_PER_DAY, 0, 1, 0}, + {0, 0, -1 * SECS_PER_DAY, -1, 1, 1}, + {0, 0, -2 * SECS_PER_DAY, -5, 2, 5}, + + {1, 0, 2 * SECS_PER_DAY, -5, -1, 5}, + {1, 0, 1 * SECS_PER_DAY, -1, 0, 1}, + {1, 0, 1 * SECS_PER_DAY, 0, 0, 0}, + {1, 0, 0, 2, 0, SECS_PER_DAY * NANOS_PER_SEC - 2}, + {1, 0, 0, 1, 0, SECS_PER_DAY * NANOS_PER_SEC - 1}, + {1, 0, 0, 0, 1, 0}, + {1, 0, 0, -1, 1, 1}, + {1, 0, 0, -2, 1, 2}, + {1, 0, -1, 0, 1, 1 * NANOS_PER_SEC}, + {1, 0, -2, 0, 1, 2 * NANOS_PER_SEC}, + {1, 0, -3, -333333333, 1, 3 * NANOS_PER_SEC + 333333333}, + {1, 0, -1 * SECS_PER_DAY, 0, 2, 0}, + {1, 0, -1 * SECS_PER_DAY, -1, 2, 1}, + {1, 0, -2 * SECS_PER_DAY, -5, 3, 5}, + }; + } + + @Test(dataProvider="Minus", groups={"tck"}) + public void test_minus(long mjd, long nanos, long minusSeconds, int minusNanos, long expectedMjd, long expectedNanos) { + UTCInstant i = UTCInstant.ofModifiedJulianDay(mjd, nanos).minus(Duration.ofSeconds(minusSeconds, minusNanos)); + assertEquals(i.getModifiedJulianDay(), expectedMjd); + assertEquals(i.getNanoOfDay(), expectedNanos); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_minus_overflowTooSmall() { + UTCInstant i = UTCInstant.ofModifiedJulianDay(Long.MIN_VALUE, 0); + i.minus(Duration.ofNanos(1)); + } + + @Test(expectedExceptions=ArithmeticException.class, groups={"tck"}) + public void test_minus_overflowTooBig() { + UTCInstant i = UTCInstant.ofModifiedJulianDay(Long.MAX_VALUE, SECS_PER_DAY * NANOS_PER_SEC - 1); + i.minus(Duration.ofNanos(-1)); + } + + //----------------------------------------------------------------------- + // durationUntil() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_durationUntil_oneDayNoLeap() { + UTCInstant utc1 = UTCInstant.ofModifiedJulianDay(41681, 0); // 1972-12-30 + UTCInstant utc2 = UTCInstant.ofModifiedJulianDay(41682, 0); // 1972-12-31 + Duration test = utc1.durationUntil(utc2); + assertEquals(test.getSeconds(), 86400); + assertEquals(test.getNano(), 0); + } + + @Test(groups={"tck"}) + public void test_durationUntil_oneDayLeap() { + UTCInstant utc1 = UTCInstant.ofModifiedJulianDay(41682, 0); // 1972-12-31 + UTCInstant utc2 = UTCInstant.ofModifiedJulianDay(41683, 0); // 1973-01-01 + Duration test = utc1.durationUntil(utc2); + assertEquals(test.getSeconds(), 86401); + assertEquals(test.getNano(), 0); + } + + @Test(groups={"tck"}) + public void test_durationUntil_oneDayLeapNegative() { + UTCInstant utc1 = UTCInstant.ofModifiedJulianDay(41683, 0); // 1973-01-01 + UTCInstant utc2 = UTCInstant.ofModifiedJulianDay(41682, 0); // 1972-12-31 + Duration test = utc1.durationUntil(utc2); + assertEquals(test.getSeconds(), -86401); + assertEquals(test.getNano(), 0); + } + + //----------------------------------------------------------------------- + // toTAIInstant() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toTAIInstant() { + for (int i = -1000; i < 1000; i++) { + for (int j = 0; j < 10; j++) { + UTCInstant utc = UTCInstant.ofModifiedJulianDay(36204 + i, j * NANOS_PER_SEC + 2L); + TAIInstant test = utc.toTAIInstant(); + assertEquals(test.getTAISeconds(), i * SECS_PER_DAY + j + 10); + assertEquals(test.getNano(), 2); + } + } + } + + //----------------------------------------------------------------------- + // toInstant() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toInstant() { + for (int i = -1000; i < 1000; i++) { + for (int j = 0; j < 10; j++) { + Instant expected = Instant.ofEpochSecond(315532800 + i * SECS_PER_DAY + j).plusNanos(2); + UTCInstant test = UTCInstant.ofModifiedJulianDay(44239 + i, j * NANOS_PER_SEC + 2); + assertEquals(test.toInstant(), expected, "Loop " + i + " " + j); + } + } + } + + //----------------------------------------------------------------------- + // compareTo() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_comparisons() { + doTest_comparisons_UTCInstant( + UTCInstant.ofModifiedJulianDay(-2L, 0), + UTCInstant.ofModifiedJulianDay(-2L, SECS_PER_DAY * NANOS_PER_SEC - 2), + UTCInstant.ofModifiedJulianDay(-2L, SECS_PER_DAY * NANOS_PER_SEC - 1), + UTCInstant.ofModifiedJulianDay(-1L, 0), + UTCInstant.ofModifiedJulianDay(-1L, 1), + UTCInstant.ofModifiedJulianDay(-1L, SECS_PER_DAY * NANOS_PER_SEC - 2), + UTCInstant.ofModifiedJulianDay(-1L, SECS_PER_DAY * NANOS_PER_SEC - 1), + UTCInstant.ofModifiedJulianDay(0L, 0), + UTCInstant.ofModifiedJulianDay(0L, 1), + UTCInstant.ofModifiedJulianDay(0L, 2), + UTCInstant.ofModifiedJulianDay(0L, SECS_PER_DAY * NANOS_PER_SEC - 1), + UTCInstant.ofModifiedJulianDay(1L, 0), + UTCInstant.ofModifiedJulianDay(2L, 0) + ); + } + + void doTest_comparisons_UTCInstant(UTCInstant... instants) { + for (int i = 0; i < instants.length; i++) { + UTCInstant a = instants[i]; + for (int j = 0; j < instants.length; j++) { + UTCInstant b = instants[j]; + if (i < j) { + assertEquals(a.compareTo(b), -1, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else if (i > j) { + assertEquals(a.compareTo(b), 1, a + " <=> " + b); + assertEquals(a.equals(b), false, a + " <=> " + b); + } else { + assertEquals(a.compareTo(b), 0, a + " <=> " + b); + assertEquals(a.equals(b), true, a + " <=> " + b); + } + } + } + } + + @Test(expectedExceptions=NullPointerException.class, groups={"tck"}) + public void test_compareTo_ObjectNull() { + UTCInstant a = UTCInstant.ofModifiedJulianDay(0L, 0); + a.compareTo(null); + } + + @Test(expectedExceptions=ClassCastException.class, groups={"tck"}) + @SuppressWarnings({"unchecked", "rawtypes"}) + public void test_compareToNonUTCInstant() { + Comparable c = UTCInstant.ofModifiedJulianDay(0L, 2); + c.compareTo(new Object()); + } + + //----------------------------------------------------------------------- + // equals() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_equals() { + UTCInstant test5a = UTCInstant.ofModifiedJulianDay(5L, 20); + UTCInstant test5b = UTCInstant.ofModifiedJulianDay(5L, 20); + UTCInstant test5n = UTCInstant.ofModifiedJulianDay(5L, 30); + UTCInstant test6 = UTCInstant.ofModifiedJulianDay(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() { + UTCInstant test5 = UTCInstant.ofModifiedJulianDay(5L, 20); + assertEquals(test5.equals(null), false); + } + + @Test(groups={"tck"}) + public void test_equals_otherClass() { + UTCInstant test5 = UTCInstant.ofModifiedJulianDay(5L, 20); + assertEquals(test5.equals(""), false); + } + + //----------------------------------------------------------------------- + // hashCode() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_hashCode() { + UTCInstant test5a = UTCInstant.ofModifiedJulianDay(5L, 20); + UTCInstant test5b = UTCInstant.ofModifiedJulianDay(5L, 20); + UTCInstant test5n = UTCInstant.ofModifiedJulianDay(5L, 30); + UTCInstant test6 = UTCInstant.ofModifiedJulianDay(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() + //----------------------------------------------------------------------- + @Test(groups={"tck"}) + public void test_toString() { + assertEquals(UTCInstant.ofModifiedJulianDay(40587, 0).toString(), "1970-01-01T00:00:00.000000000(UTC)"); + assertEquals(UTCInstant.ofModifiedJulianDay(40588, 1).toString(), "1970-01-02T00:00:00.000000001(UTC)"); + assertEquals(UTCInstant.ofModifiedJulianDay(40618, 999999999).toString(), "1970-02-01T00:00:00.999999999(UTC)"); + assertEquals(UTCInstant.ofModifiedJulianDay(40619, 1000000000).toString(), "1970-02-02T00:00:01.000000000(UTC)"); + assertEquals(UTCInstant.ofModifiedJulianDay(40620, 60L * 1000000000L).toString(), "1970-02-03T00:01:00.000000000(UTC)"); + assertEquals(UTCInstant.ofModifiedJulianDay(40621, 60L * 60L * 1000000000L).toString(), "1970-02-04T01:00:00.000000000(UTC)"); + } + + @Test(groups={"tck"}) + public void test_toString_leap() { + assertEquals(UTCInstant.ofModifiedJulianDay(41682, 24L * 60L * 60L * 1000000000L - 1000000000L).toString(), "1972-12-31T23:59:59.000000000(UTC)"); + assertEquals(UTCInstant.ofModifiedJulianDay(41682, 24L * 60L * 60L * 1000000000L).toString(), "1972-12-31T23:59:60.000000000(UTC)"); + assertEquals(UTCInstant.ofModifiedJulianDay(41683, 0).toString(), "1973-01-01T00:00:00.000000000(UTC)"); + } + +} diff --git a/src/test/java/org/threeten/extra/scale/TestUTCRules.java b/src/test/java/org/threeten/extra/scale/TestUTCRules.java new file mode 100644 index 0000000..95018b2 --- /dev/null +++ b/src/test/java/org/threeten/extra/scale/TestUTCRules.java @@ -0,0 +1,529 @@ +/* + * Copyright (c) 2007-2013, 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 org.threeten.extra.scale; + +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.util.Arrays; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import org.threeten.bp.Duration; +import org.threeten.bp.Instant; +import org.threeten.bp.OffsetDateTime; +import org.threeten.bp.ZoneOffset; + +/** + * Test SystemLeapSecondRules. + */ +@Test +public class TestUTCRules { + + SystemUTCRules rules = (SystemUTCRules) UTCRules.system(); + + static { + TestUTCInstant.dummyStaticMethod(); + } + + //----------------------------------------------------------------------- + public void test_interfaces() { + assertTrue(Serializable.class.isAssignableFrom(Duration.class)); + } + + //----------------------------------------------------------------------- + // serialize + //----------------------------------------------------------------------- + public void test_serialize() throws Exception { + SystemUTCRules test = SystemUTCRules.INSTANCE; // use real rules, not our hacked copy + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(test); + oos.close(); + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())); + assertSame(ois.readObject(), test); + } + + //----------------------------------------------------------------------- + // getName() + //----------------------------------------------------------------------- + public void test_getName() { + assertEquals(rules.getName(), "System"); + } + + //----------------------------------------------------------------------- + // getLeapSecond() + //----------------------------------------------------------------------- + @DataProvider(name="LeapSeconds") + Object[][] leapSeconds() { + return new Object[][] { + {-1, 0, 10, "1858-11-16"}, + {0, 0, 10, "1858-11-17"}, + {1, 0, 10, "1858-11-18"}, + + {41316, 0, 10, "1971-12-31"}, + {41317, 0, 10, "1972-01-01"}, + {41318, 0, 10, "1972-01-02"}, + + {41497, 0, 10, "1972-06-29"}, + {41498, 1, 10, "1972-06-30"}, + {41499, 0, 11, "1972-07-01"}, + {41500, 0, 11, "1972-07-02"}, + + {41681, 0, 11, "1972-12-30"}, + {41682, 1, 11, "1972-12-31"}, + {41683, 0, 12, "1973-01-01"}, + {41684, 0, 12, "1973-01-02"}, + + {42046, 0, 12, "1973-12-30"}, + {42047, 1, 12, "1973-12-31"}, + {42048, 0, 13, "1974-01-01"}, + {42049, 0, 13, "1974-01-02"}, + + {42411, 0, 13, "1974-12-30"}, + {42412, 1, 13, "1974-12-31"}, + {42413, 0, 14, "1975-01-01"}, + {42414, 0, 14, "1975-01-02"}, + + {42776, 0, 14, "1975-12-30"}, + {42777, 1, 14, "1975-12-31"}, + {42778, 0, 15, "1976-01-01"}, + {42779, 0, 15, "1976-01-02"}, + + {43142, 0, 15, "1976-12-30"}, + {43143, 1, 15, "1976-12-31"}, + {43144, 0, 16, "1977-01-01"}, + {43145, 0, 16, "1977-01-02"}, + + {43507, 0, 16, "1977-12-30"}, + {43508, 1, 16, "1977-12-31"}, + {43509, 0, 17, "1978-01-01"}, + {43510, 0, 17, "1978-01-02"}, + + {43872, 0, 17, "1978-12-30"}, + {43873, 1, 17, "1978-12-31"}, + {43874, 0, 18, "1979-01-01"}, + {43875, 0, 18, "1979-01-02"}, + + {44237, 0, 18, "1979-12-30"}, + {44238, 1, 18, "1979-12-31"}, + {44239, 0, 19, "1980-01-01"}, + {44240, 0, 19, "1980-01-02"}, + + {44784, 0, 19, "1981-06-29"}, + {44785, 1, 19, "1981-06-30"}, + {44786, 0, 20, "1981-07-01"}, + {44787, 0, 20, "1981-07-02"}, + + {45149, 0, 20, "1982-06-29"}, + {45150, 1, 20, "1982-06-30"}, + {45151, 0, 21, "1982-07-01"}, + {45152, 0, 21, "1982-07-02"}, + + {45514, 0, 21, "1983-06-29"}, + {45515, 1, 21, "1983-06-30"}, + {45516, 0, 22, "1983-07-01"}, + {45517, 0, 22, "1983-07-02"}, + + {46245, 0, 22, "1985-06-29"}, + {46246, 1, 22, "1985-06-30"}, + {46247, 0, 23, "1985-07-01"}, + {46248, 0, 23, "1985-07-02"}, + + {47159, 0, 23, "1987-12-30"}, + {47160, 1, 23, "1987-12-31"}, + {47161, 0, 24, "1988-01-01"}, + {47162, 0, 24, "1988-01-02"}, + + {47890, 0, 24, "1989-12-30"}, + {47891, 1, 24, "1989-12-31"}, + {47892, 0, 25, "1990-01-01"}, + {47893, 0, 25, "1990-01-02"}, + + {48255, 0, 25, "1990-12-30"}, + {48256, 1, 25, "1990-12-31"}, + {48257, 0, 26, "1991-01-01"}, + {48258, 0, 26, "1991-01-02"}, + + {48802, 0, 26, "1992-06-29"}, + {48803, 1, 26, "1992-06-30"}, + {48804, 0, 27, "1992-07-01"}, + {48805, 0, 27, "1992-07-02"}, + + {49167, 0, 27, "1993-06-29"}, + {49168, 1, 27, "1993-06-30"}, + {49169, 0, 28, "1993-07-01"}, + {49170, 0, 28, "1993-07-02"}, + + {49532, 0, 28, "1994-06-29"}, + {49533, 1, 28, "1994-06-30"}, + {49534, 0, 29, "1994-07-01"}, + {49535, 0, 29, "1994-07-02"}, + + {50081, 0, 29, "1995-12-30"}, + {50082, 1, 29, "1995-12-31"}, + {50083, 0, 30, "1996-01-01"}, + {50084, 0, 30, "1996-01-02"}, + + {50628, 0, 30, "1997-06-29"}, + {50629, 1, 30, "1997-06-30"}, + {50630, 0, 31, "1997-07-01"}, + {50631, 0, 31, "1997-07-02"}, + + {51177, 0, 31, "1998-12-30"}, + {51178, 1, 31, "1998-12-31"}, + {51179, 0, 32, "1999-01-01"}, + {51180, 0, 32, "1999-01-02"}, + + {53734, 0, 32, "2005-12-30"}, + {53735, 1, 32, "2005-12-31"}, + {53736, 0, 33, "2006-01-01"}, + {53737, 0, 33, "2006-01-02"}, + + {54830, 0, 33, "2008-12-30"}, + {54831, 1, 33, "2008-12-31"}, + {54832, 0, 34, "2009-01-01"}, + {54833, 0, 34, "2009-01-02"}, + }; + } + + @Test(dataProvider="LeapSeconds") + public void test_leapSeconds(long mjd, int adjust, int offset, String checkDate) { +// assertEquals(mjd, LocalDate.parse(checkDate).toModifiedJulianDay(), "Invalid test"); + + assertEquals(rules.getLeapSecondAdjustment(mjd), adjust); + assertEquals(rules.getTAIOffset(mjd), offset); + if (adjust != 0) { + long[] leaps = rules.getLeapSecondDates(); + Arrays.sort(leaps); + assertEquals(Arrays.binarySearch(leaps, mjd) >= 0, true); + } + } + + //----------------------------------------------------------------------- + // convertToUTC(TAIInstant)/convertToTAI(UTCInstant) + //----------------------------------------------------------------------- +// private static final int CURRENT_TAI_OFFSET = 34; // change this as leap secs added + private static final long SECS_PER_DAY = 24 * 60 * 60L; + private static final long NANOS_PER_SEC = 1000000000L; + private static final long MJD_1800 = -21504L; + private static final long MJD_1900 = 15020L; + private static final long MJD_1958 = 36204L; + private static final long MJD_1980 = 44239L; + private static final long MJD_2100 = 88069L; + private static final long TAI_SECS_UTC1800 = (MJD_1800 - MJD_1958) * SECS_PER_DAY + 10; + private static final long TAI_SECS_UTC1900 = (MJD_1900 - MJD_1958) * SECS_PER_DAY + 10; + private static final long TAI_SECS_UTC1958 = 10; + private static final long TAI_SECS_UTC1980 = (MJD_1980 - MJD_1958) * SECS_PER_DAY + 19; +// private static final long TAI_SECS_UTC2100 = (MJD_2100 - MJD_1958) * SECS_PER_DAY + CURRENT_TAI_OFFSET; +// private static final long TAI_SECS_UTC2100_EXTRA_NEGATIVE_LEAP = (MJD_2100 - MJD_1958) * SECS_PER_DAY + CURRENT_TAI_OFFSET - 1; +// private static final long TAI_SECS_UTC2100_EXTRA_DOUBLE_LEAP = (MJD_2100 - MJD_1958) * SECS_PER_DAY + CURRENT_TAI_OFFSET + 2; + + public void test_convertToUTC_TAIInstant_startUtcPeriod() { + TAIInstant tai = TAIInstant.ofTAISeconds(TAI_SECS_UTC1980, 0); // 1980-01-01 (19 leap secs added) + UTCInstant expected = UTCInstant.ofModifiedJulianDay(MJD_1980, 0, rules); + for (int i = -10; i < 10; i++) { + Duration duration = Duration.ofNanos(i); + assertEquals(rules.convertToUTC(tai.plus(duration)), expected.plus(duration)); + assertEquals(rules.convertToTAI(expected.plus(duration)), tai.plus(duration)); // check reverse + } + for (int i = -10; i < 10; i++) { + Duration duration = Duration.ofSeconds(i); + assertEquals(rules.convertToUTC(tai.plus(duration)), expected.plus(duration)); + assertEquals(rules.convertToTAI(expected.plus(duration)), tai.plus(duration)); // check reverse + } + } + + public void test_convertToUTC_TAIInstant_furtherAfterLeap() { + TAIInstant tai = TAIInstant.ofTAISeconds(TAI_SECS_UTC1980 + 1, 0); // 1980-01-01 (19 leap secs added) + UTCInstant expected = UTCInstant.ofModifiedJulianDay(MJD_1980, NANOS_PER_SEC, rules); + assertEquals(rules.convertToUTC(tai), expected); + assertEquals(rules.convertToTAI(expected), tai); // check reverse + } + + public void test_convertToUTC_TAIInstant_justAfterLeap() { + TAIInstant tai = TAIInstant.ofTAISeconds(TAI_SECS_UTC1980, 0); // 1980-01-01 (19 leap secs added) + UTCInstant expected = UTCInstant.ofModifiedJulianDay(MJD_1980, 0, rules); + assertEquals(rules.convertToUTC(tai), expected); + assertEquals(rules.convertToTAI(expected), tai); // check reverse + } + + public void test_convertToUTC_TAIInstant_inLeap() { + TAIInstant tai = TAIInstant.ofTAISeconds(TAI_SECS_UTC1980 - 1, 0); // 1980-01-01 (1 second before 1980) + UTCInstant expected = UTCInstant.ofModifiedJulianDay(MJD_1980 - 1, SECS_PER_DAY * NANOS_PER_SEC, rules); + assertEquals(rules.convertToUTC(tai), expected); + assertEquals(rules.convertToTAI(expected), tai); // check reverse + } + + public void test_convertToUTC_TAIInstant_justBeforeLeap() { + TAIInstant tai = TAIInstant.ofTAISeconds(TAI_SECS_UTC1980 - 2, 0); // 1980-01-01 (2 seconds before 1980) + UTCInstant expected = UTCInstant.ofModifiedJulianDay(MJD_1980 - 1, (SECS_PER_DAY - 1) * NANOS_PER_SEC, rules); + assertEquals(rules.convertToUTC(tai), expected); + assertEquals(rules.convertToTAI(expected), tai); // check reverse + } + + public void test_convertToUTC_TAIInstant_1800() { + TAIInstant tai = TAIInstant.ofTAISeconds(TAI_SECS_UTC1800, 0); // 1800-01-01 + UTCInstant expected = UTCInstant.ofModifiedJulianDay(MJD_1800, 0, rules); + assertEquals(rules.convertToUTC(tai), expected); + assertEquals(rules.convertToTAI(expected), tai); // check reverse + } + + public void test_convertToUTC_TAIInstant_1900() { + TAIInstant tai = TAIInstant.ofTAISeconds(TAI_SECS_UTC1900, 0); // 1900-01-01 + UTCInstant expected = UTCInstant.ofModifiedJulianDay(MJD_1900, 0, rules); + assertEquals(rules.convertToUTC(tai), expected); + assertEquals(rules.convertToTAI(expected), tai); // check reverse + } + + public void test_convertToUTC_TAIInstant_1958() { + TAIInstant tai = TAIInstant.ofTAISeconds(TAI_SECS_UTC1958, 0); // 1958-01-01 + UTCInstant expected = UTCInstant.ofModifiedJulianDay(MJD_1958, 0, rules); + assertEquals(rules.convertToUTC(tai), expected); + assertEquals(rules.convertToTAI(expected), tai); // check reverse + } + +// public void test_convertToUTC_TAIInstant_2100() { +// TAIInstant tai = TAIInstant.ofTAISeconds(TAI_SECS_UTC2100, 0); // 2100-01-01 +// UTCInstant expected = UTCInstant.ofModifiedJulianDay(MJD_2100, 0, rules); +// assertEquals(rules.convertToUTC(tai), expected); +// assertEquals(rules.convertToTAI(expected), tai); // check reverse +// } + + @Test(expectedExceptions=NullPointerException.class) + public void test_convertToUTC_TAIInstant_null() { + rules.convertToUTC((TAIInstant) null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_convertToTAI_UTCInstant_null() { + rules.convertToTAI((UTCInstant) null); + } + + //------------------------------------------------------------------------- +// public void test_negativeLeap_justBeforeLeap() { +// rules.registerLeapSecond(MJD_2100 - 1, -1); +// TAIInstant tai = TAIInstant.ofTAISeconds(TAI_SECS_UTC2100_EXTRA_NEGATIVE_LEAP - 1, 0); // 2100-01-01 (1 second before 2100) +// UTCInstant expected = UTCInstant.ofModifiedJulianDay(MJD_2100 - 1, (SECS_PER_DAY - 2) * NANOS_PER_SEC, rules); +// assertEquals(rules.convertToUTC(tai), expected); +// assertEquals(rules.convertToTAI(expected), tai); // check reverse +// } +// +// public void test_negativeLeap_justAfterLeap() { +// rules.registerLeapSecond(MJD_2100 - 1, -1); +// TAIInstant tai = TAIInstant.ofTAISeconds(TAI_SECS_UTC2100_EXTRA_NEGATIVE_LEAP, 0); // 2100-01-01 +// UTCInstant expected = UTCInstant.ofModifiedJulianDay(MJD_2100, 0, rules); +// assertEquals(rules.convertToUTC(tai), expected); +// assertEquals(rules.convertToTAI(expected), tai); // check reverse +// } + + //----------------------------------------------------------------------- + // convertToUTC(Instant)/convertToInstant(UTCInstant) + //----------------------------------------------------------------------- + public void test_convertToInstant_justBeforeLeap() { + OffsetDateTime odt = OffsetDateTime.of(1979, 12, 31, 23, 43, 21, 0, ZoneOffset.UTC); + Instant instant = odt.toInstant(); + UTCInstant utc = UTCInstant.ofModifiedJulianDay(MJD_1980 - 1, (SECS_PER_DAY + 1 - 1000) * NANOS_PER_SEC, rules); + assertEquals(rules.convertToInstant(utc), instant); + assertEquals(rules.convertToUTC(instant), utc); + } + + public void test_convertToInstant_sls() { + for (int i = 1; i < 1000; i++) { + long utcNanos = (SECS_PER_DAY + 1 - 1000) * NANOS_PER_SEC + i * 1000000000L; + long startSls = (86401 - 1000) * NANOS_PER_SEC; + long slsNanos = (utcNanos - (utcNanos - startSls) / 1000); + OffsetDateTime odt = OffsetDateTime.of(1979, 12, 31, 0, 0, 0, + (int) (slsNanos % NANOS_PER_SEC), ZoneOffset.UTC) + .plusSeconds((int) (slsNanos / NANOS_PER_SEC)); + Instant instant = odt.toInstant(); + UTCInstant utc = UTCInstant.ofModifiedJulianDay( + MJD_1980 - 1, (SECS_PER_DAY + 1 - 1000) * NANOS_PER_SEC + i * NANOS_PER_SEC, rules); + assertEquals(rules.convertToInstant(utc), instant); + assertEquals(rules.convertToUTC(instant), utc); + } + } + + public void test_convertToInstant_slsMillis() { + for (int i = 1; i < 1000; i++) { + long utcNanos = (SECS_PER_DAY + 1 - 1000) * NANOS_PER_SEC + i * 1000000; + long startSls = (86401 - 1000) * NANOS_PER_SEC; + long slsNanos = (utcNanos - (utcNanos - startSls) / 1000); + OffsetDateTime odt = OffsetDateTime.of(1979, 12, 31, 23, 43, 21, (int) (slsNanos % NANOS_PER_SEC), ZoneOffset.UTC); + Instant instant = odt.toInstant(); + UTCInstant utc = UTCInstant.ofModifiedJulianDay( + MJD_1980 - 1, (SECS_PER_DAY + 1 - 1000) * NANOS_PER_SEC + i * 1000000, rules); + assertEquals(rules.convertToInstant(utc), instant); + assertEquals(rules.convertToUTC(instant), utc); + } + } + + public void test_convertToInstant_slsMicros() { + for (int i = 1; i < 1000; i++) { + long utcNanos = (SECS_PER_DAY + 1 - 1000) * NANOS_PER_SEC + i * 1000; + long startSls = (86401 - 1000) * NANOS_PER_SEC; + long slsNanos = (utcNanos - (utcNanos - startSls) / 1000); + OffsetDateTime odt = OffsetDateTime.of(1979, 12, 31, 23, 43, 21, (int) (slsNanos % NANOS_PER_SEC), ZoneOffset.UTC); + Instant instant = odt.toInstant(); + UTCInstant utc = UTCInstant.ofModifiedJulianDay( + MJD_1980 - 1, (SECS_PER_DAY + 1 - 1000) * NANOS_PER_SEC + i * 1000, rules); + assertEquals(rules.convertToInstant(utc), instant); + assertEquals(rules.convertToUTC(instant), utc); + } + } + + public void test_convertToInstant_slsNanos() { + for (int i = 1; i < 5005; i++) { + long utcNanos = (SECS_PER_DAY + 1 - 1000) * NANOS_PER_SEC + i; + long startSls = (86401 - 1000) * NANOS_PER_SEC; + long slsNanos = (utcNanos - (utcNanos - startSls) / 1000); + OffsetDateTime odt = OffsetDateTime.of(1979, 12, 31, 23, 43, 21, (int) (slsNanos % NANOS_PER_SEC), ZoneOffset.UTC); + Instant instant = odt.toInstant(); + UTCInstant utc = UTCInstant.ofModifiedJulianDay(MJD_1980 - 1, utcNanos, rules); + assertEquals(rules.convertToInstant(utc), instant); + // not all instants can map back to the correct UTC value + long reverseUtcNanos = startSls + ((slsNanos - startSls) * 1000L) / (1000L - 1); + assertEquals(rules.convertToUTC(instant), UTCInstant.ofModifiedJulianDay(MJD_1980 - 1, reverseUtcNanos, rules)); + } + } + + public void test_convertToInstant_justAfterLeap() { + OffsetDateTime odt = OffsetDateTime.of(1980, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + Instant instant = odt.toInstant(); + UTCInstant utc = UTCInstant.ofModifiedJulianDay(MJD_1980, 0, rules); + assertEquals(rules.convertToInstant(utc), instant); + assertEquals(rules.convertToUTC(instant), utc); + } + + public void test_convertToInstant_furtherAfterLeap() { + OffsetDateTime odt = OffsetDateTime.of(1980, 1, 1, 0, 0, 1, 0, ZoneOffset.UTC); + Instant instant = odt.toInstant(); + UTCInstant utc = UTCInstant.ofModifiedJulianDay(MJD_1980, NANOS_PER_SEC, rules); + assertEquals(rules.convertToInstant(utc), instant); + assertEquals(rules.convertToUTC(instant), utc); + } + + //----------------------------------------------------------------------- + // registerLeapSecond() + //----------------------------------------------------------------------- + public void test_registerLeapSecond_justAfterLastDate_plusOne() { + long[] dates = rules.getLeapSecondDates(); + long mjd = dates[dates.length - 1] + 1; + rules.registerLeapSecond(mjd, 1); + long[] test = rules.getLeapSecondDates(); + assertEquals(test.length, dates.length + 1); + assertEquals(test[test.length - 1], mjd); + assertEquals(rules.getLeapSecondAdjustment(mjd), 1); + } + + public void test_registerLeapSecond_justAfterLastDate_minusOne() { + long[] dates = rules.getLeapSecondDates(); + long mjd = dates[dates.length - 1] + 1; + rules.registerLeapSecond(mjd, -1); + long[] test = rules.getLeapSecondDates(); + assertEquals(test.length, dates.length + 1); + assertEquals(test[test.length - 1], mjd); + assertEquals(rules.getLeapSecondAdjustment(mjd), -1); + } + + public void test_registerLeapSecond_equalLastDate_sameLeap() { + long[] dates = rules.getLeapSecondDates(); + long mjd = dates[dates.length - 1]; + int adj = rules.getLeapSecondAdjustment(mjd); + rules.registerLeapSecond(mjd, adj); + long[] test = rules.getLeapSecondDates(); + assertEquals(Arrays.equals(test, dates), true); + assertEquals(rules.getLeapSecondAdjustment(mjd), adj); + } + + public void test_registerLeapSecond_equalEarlierDate_sameLeap() { + long[] dates = rules.getLeapSecondDates(); + long mjd = dates[dates.length - 2]; + int adj = rules.getLeapSecondAdjustment(mjd); + rules.registerLeapSecond(mjd, adj); + long[] test = rules.getLeapSecondDates(); + assertEquals(Arrays.equals(test, dates), true); + assertEquals(rules.getLeapSecondAdjustment(mjd), adj); + } + + @Test(expectedExceptions=IllegalArgumentException.class) + public void test_registerLeapSecond_equalLastDate_differentLeap() { + long[] dates = rules.getLeapSecondDates(); + long mjd = dates[dates.length - 1]; + int adj = rules.getLeapSecondAdjustment(mjd); + rules.registerLeapSecond(mjd, -adj); + } + + @Test(expectedExceptions=IllegalArgumentException.class) + public void test_registerLeapSecond_equalEarlierDate_differentLeap() { + long[] dates = rules.getLeapSecondDates(); + long mjd = dates[dates.length - 2]; + int adj = rules.getLeapSecondAdjustment(mjd); + rules.registerLeapSecond(mjd, -adj); + } + + @Test(expectedExceptions=IllegalArgumentException.class) + public void test_registerLeapSecond_beforeLastDate() { + long[] dates = rules.getLeapSecondDates(); + long mjd = dates[dates.length - 1] - 1; + rules.registerLeapSecond(mjd, 1); + } + + @Test(expectedExceptions=IllegalArgumentException.class) + public void test_registerLeapSecond_invalidAdjustment_zero() { + rules.registerLeapSecond(MJD_2100, 0); + } + + @Test(expectedExceptions=IllegalArgumentException.class) + public void test_registerLeapSecond_invalidAdjustment_minusTwo() { + rules.registerLeapSecond(MJD_2100, -2); + } + + @Test(expectedExceptions=IllegalArgumentException.class) + public void test_registerLeapSecond_invalidAdjustment_three() { + rules.registerLeapSecond(MJD_2100, 3); + } + + //----------------------------------------------------------------------- + // toString() + //----------------------------------------------------------------------- + public void test_toString() { + assertEquals(rules.toString(), "UTCRules[System]"); + } + +}