All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.threeten.extra.chrono.Symmetry010Date Maven / Gradle / Ivy

Go to download

Additional functionality that enhances JSR-310 dates and times in Java SE 8 and later

There is a newer version: 1.8.0
Show newest version
/*
 * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 *  * Neither the name of JSR-310 nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * 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.extra.chrono.Symmetry010Chronology.DAYS_0001_TO_1970;
import static org.threeten.extra.chrono.Symmetry010Chronology.DAYS_IN_MONTH;
import static org.threeten.extra.chrono.Symmetry010Chronology.DAYS_IN_MONTH_LONG;
import static org.threeten.extra.chrono.Symmetry010Chronology.DAYS_IN_QUARTER;
import static org.threeten.extra.chrono.Symmetry010Chronology.DAYS_IN_WEEK;
import static org.threeten.extra.chrono.Symmetry010Chronology.DAYS_IN_YEAR;
import static org.threeten.extra.chrono.Symmetry010Chronology.DAYS_IN_YEAR_LONG;
import static org.threeten.extra.chrono.Symmetry010Chronology.DAYS_PER_CYCLE;
import static org.threeten.extra.chrono.Symmetry010Chronology.DAY_OF_MONTH_RANGE;
import static org.threeten.extra.chrono.Symmetry010Chronology.DAY_OF_YEAR_RANGE;
import static org.threeten.extra.chrono.Symmetry010Chronology.EPOCH_DAY_RANGE;
import static org.threeten.extra.chrono.Symmetry010Chronology.ERA_RANGE;
import static org.threeten.extra.chrono.Symmetry010Chronology.INSTANCE;
import static org.threeten.extra.chrono.Symmetry010Chronology.MONTHS_IN_YEAR;
import static org.threeten.extra.chrono.Symmetry010Chronology.MONTH_OF_YEAR_RANGE;
import static org.threeten.extra.chrono.Symmetry010Chronology.WEEKS_IN_MONTH;
import static org.threeten.extra.chrono.Symmetry010Chronology.WEEKS_IN_YEAR;
import static org.threeten.extra.chrono.Symmetry010Chronology.YEAR_RANGE;

import java.io.Serializable;
import java.time.Clock;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.ChronoPeriod;
import java.time.chrono.IsoEra;
import java.time.temporal.ChronoField;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAmount;
import java.time.temporal.TemporalField;
import java.time.temporal.TemporalQuery;
import java.time.temporal.TemporalUnit;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.time.temporal.ValueRange;

/**
 * A date in the Symmetry010 calendar system.
 * 

* This date operates using the {@linkplain Symmetry010Chronology Symmetry010 calendar}. * This calendar system is a proposed reform calendar system, and is not in common use. * The Symmetry010 differs from the Gregorian in terms of month length, and the leap year rule. * Dates are aligned such that {@code 0001/01/01 (Sym010)} is {@code 0001-01-01 (ISO)}. * The alignment of January 1st happens 40 times within a 293 years cycle, skipping 5, 6, 11 or 12 years in between: * 1, 7, 18, 24, 29, 35, 46, 52, 57, 63, 74, 80, 85, 91, 103, 114, 120, 125, 131, 142, * 148, 153, 159, 170, 176, 181, 187, 198, 210, 216, 221, 227, 238, 244, 249, 255, 266, 272, 277, 283. *

* The implementation is a pure Symmetry010 calendar, as proposed by Dr. Irv Bromberg. * The year shares the 12 months with the Gregorian calendar. * The months February, May, August, November span 31 days, all other months consist of 30 days. * In leap years, December is extended with a full week, the so-called "leap week". * Thus December in a leap year has 37. * Since each month is made of full weeks, the calendar is perennial, with every date fixed always on the same weekday. * Each month starts on a Monday and ends on a Sunday; so does each year. * The 13th day of a month is always a Saturday. *

* More information is available on Wikipedia at * Symmetry010 or on the calendar's * home page. *

* *

Implementation Requirements

* This class is immutable and thread-safe. *

* This class must be treated as a value type. Do not synchronize, rely on the * identity hash code or use the distinction between equals() and ==. */ public final class Symmetry010Date extends AbstractDate implements ChronoLocalDate, Serializable { /** * Serialization version. */ private static final long serialVersionUID = -8275627894629629L; /** * The proleptic year. */ private final int prolepticYear; /** * The month of the year. */ private final int month; /** * The day of the month. */ private final int day; /** * The day of year. */ private final transient int dayOfYear; //----------------------------------------------------------------------- /** * Obtains the current {@code Symmetry010Date} from the system clock in the default time-zone. *

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

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

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

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

* This will query the specified clock to obtain the current date - today. * Using this method allows the use of an alternate clock for testing. * The alternate clock may be introduced using {@linkplain Clock dependency injection}. * * @param clock the clock to use, not null * @return the current date, not null * @throws DateTimeException if the current date cannot be obtained */ public static Symmetry010Date now(Clock clock) { LocalDate now = LocalDate.now(clock); return Symmetry010Date.ofEpochDay(now.toEpochDay()); } /** * Obtains a {@code Symmetry010Date} representing a date in the Symmetry010 calendar * system from the proleptic-year, month-of-year and day-of-month fields. *

* This returns a {@code Symmetry010Date} with the specified fields. * The day must be valid for the year and month, otherwise an exception will be thrown. * * @param prolepticYear the Symmetry010 proleptic-year * @param month the Symmetry010 month-of-year, from 1 to 12 * @param dayOfMonth the Symmetry010 day-of-month, from 1 to 30, or 1 to 31 in February, May, August, November, * or 1 to 37 in December in a Leap Year * @return the date in Symmetry010 calendar system, not null * @throws DateTimeException if the value of any field is out of range, * or if the day-of-month is invalid for the month-year */ public static Symmetry010Date of(int prolepticYear, int month, int dayOfMonth) { return create(prolepticYear, month, dayOfMonth); } //----------------------------------------------------------------------- /** * Obtains a {@code Symmetry010Date} from a temporal object. *

* This obtains a date in the Symmetry010 calendar system based on the specified temporal. * A {@code TemporalAccessor} represents an arbitrary set of date and time information, * which this factory converts to an instance of {@code Symmetry010Date}. *

* The conversion typically uses the {@link ChronoField#EPOCH_DAY EPOCH_DAY} * field, which is standardized across calendar systems. *

* This method matches the signature of the functional interface {@link TemporalQuery} * allowing it to be used as a query via method reference, {@code Symmetry010Date::from}. * * @param temporal the temporal object to convert, not null * @return the date in the Symmetry010 calendar system, not null * @throws DateTimeException if unable to convert to a {@code Symmetry010Date} */ public static Symmetry010Date from(TemporalAccessor temporal) { if (temporal instanceof Symmetry010Date) { return (Symmetry010Date) temporal; } return Symmetry010Date.ofEpochDay(temporal.getLong(ChronoField.EPOCH_DAY)); } //----------------------------------------------------------------------- /** * Obtains a {@code Symmetry010Date} representing a date in the Symmetry010 calendar * system from the proleptic-year and day-of-year fields. *

* This returns a {@code Symmetry010Date} with the specified fields. * The day must be valid for the year, otherwise an exception will be thrown. * * @param prolepticYear the Symmetry010 proleptic-year * @param dayOfYear the Symmetry010 day-of-year, from 1 to 364/371 * @return the date in Symmetry010 calendar system, not null * @throws DateTimeException if the value of any field is out of range, * or if the day-of-year is invalid for the year */ static Symmetry010Date ofYearDay(int prolepticYear, int dayOfYear) { YEAR_RANGE.checkValidValue(prolepticYear, ChronoField.YEAR_OF_ERA); DAY_OF_YEAR_RANGE.checkValidValue(dayOfYear, ChronoField.DAY_OF_YEAR); boolean leap = INSTANCE.isLeapYear(prolepticYear); if (dayOfYear > DAYS_IN_YEAR && !leap) { throw new DateTimeException("Invalid date 'DayOfYear " + dayOfYear + "' as '" + prolepticYear + "' is not a leap year"); } int offset = Math.min(dayOfYear, DAYS_IN_YEAR) - 1; int quarter = offset / DAYS_IN_QUARTER; int day = ((dayOfYear - 1) - quarter * DAYS_IN_QUARTER) + 1; int month = 1 + quarter * 3; if (day > DAYS_IN_MONTH + DAYS_IN_MONTH + 1) { month += 2; day -= DAYS_IN_MONTH + DAYS_IN_MONTH + 1; } else if (day > DAYS_IN_MONTH) { month += 1; day -= DAYS_IN_MONTH; } return new Symmetry010Date(prolepticYear, month, day); } /** * Obtains a {@code Symmetry010Date} representing a date in the Symmetry010 calendar * system from the epoch-day. * * @param epochDay the epoch day to convert based on 1970-01-01 (ISO), corresponds to 1970-01-04 (Sym010) * @return the date in Symmetry010 calendar system, not null * @throws DateTimeException if the epoch-day is out of range */ static Symmetry010Date ofEpochDay(long epochDay) { EPOCH_DAY_RANGE.checkValidValue(epochDay + 3, ChronoField.EPOCH_DAY); long zeroDay = epochDay + DAYS_0001_TO_1970 + 1; long year = 1 + ((293 * zeroDay) / DAYS_PER_CYCLE); long doy = zeroDay - (DAYS_IN_YEAR * (year - 1) + Symmetry010Chronology.getLeapYearsBefore(year) * DAYS_IN_WEEK); if (doy < 1) { year--; doy += INSTANCE.isLeapYear(year) ? DAYS_IN_YEAR_LONG : DAYS_IN_YEAR; } int diy = INSTANCE.isLeapYear(year) ? DAYS_IN_YEAR_LONG : DAYS_IN_YEAR; if (doy > diy) { doy -= diy; year++; } return ofYearDay((int) year, (int) doy); } /** * Consistency check for dates manipulations after calls to * {@link #plus(long, TemporalUnit)}, * {@link #minus(long, TemporalUnit)}, * {@link #until(AbstractDate, TemporalUnit)} or * {@link #with(TemporalField, long)}. * * @param prolepticYear the Symmetry010 proleptic-year * @param month the Symmetry010 month, from 1 to 12 * @param dayOfMonth the Symmetry010 day-of-month, from 1 to 30, or 1 to 31 in February, May, August, November, * or 1 to 37 in December in a Leap Year * @return the resolved date */ private static Symmetry010Date resolvePreviousValid(int prolepticYear, int month, int dayOfMonth) { int monthR = Math.min(month, MONTHS_IN_YEAR); int dayR = Math.min(dayOfMonth, monthR == 12 && INSTANCE.isLeapYear(prolepticYear) ? DAYS_IN_MONTH + 7 : monthR % 3 == 2 ? DAYS_IN_MONTH_LONG : DAYS_IN_MONTH); return create(prolepticYear, monthR, dayR); } //----------------------------------------------------------------------- /** * Factory method, validates the given triplet year, month and dayOfMonth. * * @param prolepticYear the Symmetry010 proleptic-year * @param month the Symmetry010 month, from 1 to 12 * @param dayOfMonth the Symmetry010 day-of-month, from 1 to 30, or 1 to 31 in February, May, August, November, * or 1 to 37 in December in a Leap Year * @return the Symmetry010 date * @throws DateTimeException if the date is invalid */ static Symmetry010Date create(int prolepticYear, int month, int dayOfMonth) { YEAR_RANGE.checkValidValue(prolepticYear, ChronoField.YEAR_OF_ERA); MONTH_OF_YEAR_RANGE.checkValidValue(month, ChronoField.MONTH_OF_YEAR); DAY_OF_MONTH_RANGE.checkValidValue(dayOfMonth, ChronoField.DAY_OF_MONTH); if (dayOfMonth > DAYS_IN_MONTH) { if (month == MONTHS_IN_YEAR) { if (!INSTANCE.isLeapYear(prolepticYear)) { throw new DateTimeException("Invalid Leap Day as '" + prolepticYear + "' is not a leap year"); } } else if (((month % 3 == 2) && dayOfMonth > DAYS_IN_MONTH_LONG) || (month % 3 != 2)) { throw new DateTimeException("Invalid date: " + prolepticYear + '/' + month + '/' + dayOfMonth); } } return new Symmetry010Date(prolepticYear, month, dayOfMonth); } //----------------------------------------------------------------------- /** * Creates an instance from validated data. * * @param prolepticYear the Symmetry010 proleptic-year * @param month the Symmetry010 month, from 1 to 12 * @param dayOfMonth the Symmetry010 day-of-month, from 1 to 30, or 1 to 31 in February, May, August, November, * or 1 to 37 in December in a Leap Year */ private Symmetry010Date(int prolepticYear, int month, int dayOfMonth) { this.prolepticYear = prolepticYear; this.month = month; this.day = dayOfMonth; this.dayOfYear = DAYS_IN_MONTH * (month - 1) + (month / 3) + dayOfMonth; } /** * Validates the object. * * @return Symmetry010Date the resolved date, not null */ private Object readResolve() { return Symmetry010Date.of(prolepticYear, month, day); } //----------------------------------------------------------------------- @Override int getProlepticYear() { return prolepticYear; } @Override int getMonth() { return month; } @Override int getDayOfMonth() { return day; } @Override int getDayOfYear() { return dayOfYear; } @Override int lengthOfYearInMonths() { return MONTHS_IN_YEAR; } @Override int getDayOfWeek() { return ((dayOfYear - 1 + getDayOfMonthOffset()) % DAYS_IN_WEEK) + 1; } long getProlepticWeek() { return prolepticYear * WEEKS_IN_YEAR + Symmetry010Chronology.getLeapYearsBefore(prolepticYear) + ((dayOfYear - 1) / DAYS_IN_WEEK) - 1; } /** * Each 1st month of a quarter (month % 3 == 1) starts on a Monday, offset is 0. * Each 2nd month of a quarter (month % 3 == 2) starts on a Wednesday, offset is 2. * Each 3rd month of a quarter (month % 3 == 0) starts on a Saturday, offset is 5. */ private static final int[] dayOfMonthOffset = {5, 0, 2}; private int getDayOfMonthOffset() { return dayOfMonthOffset[month % 3]; } /** * Checks if the date is within the leap week. * * @return true if this date is in the leap week */ public boolean isLeapWeek() { return isLeapYear() && this.dayOfYear > DAYS_IN_YEAR; } //----------------------------------------------------------------------- @Override public ValueRange range(TemporalField field) { if (field instanceof ChronoField) { if (isSupported(field)) { ChronoField f = (ChronoField) field; switch (f) { case ALIGNED_DAY_OF_WEEK_IN_MONTH: case ALIGNED_DAY_OF_WEEK_IN_YEAR: case DAY_OF_WEEK: return ValueRange.of(1, DAYS_IN_WEEK); case ALIGNED_WEEK_OF_MONTH: return ValueRange.of(1, lengthOfMonth() / DAYS_IN_WEEK); case ALIGNED_WEEK_OF_YEAR: return ValueRange.of(1, WEEKS_IN_YEAR + (isLeapYear() ? 1 : 0)); case DAY_OF_MONTH: return ValueRange.of(1, lengthOfMonth()); case DAY_OF_YEAR: return ValueRange.of(1, lengthOfYear()); case EPOCH_DAY: return EPOCH_DAY_RANGE; case ERA: return ERA_RANGE; case MONTH_OF_YEAR: return MONTH_OF_YEAR_RANGE; default: break; } } else { throw new UnsupportedTemporalTypeException("Unsupported field: " + field); } } return super.range(field); } @Override ValueRange rangeAlignedWeekOfMonth() { // never invoked return ValueRange.of(1, WEEKS_IN_MONTH); } @Override Symmetry010Date resolvePrevious(int newYear, int newMonth, int dayOfMonth) { return resolvePreviousValid(newYear, newMonth, dayOfMonth); } //----------------------------------------------------------------------- /** * Gets the chronology of this date, which is the Symmetry010 calendar system. *

* The {@code Chronology} represents the calendar system in use. * The era and other fields in {@link ChronoField} are defined by the chronology. * * @return the Symmetry010 chronology, not null */ @Override public Symmetry010Chronology getChronology() { return INSTANCE; } /** * Gets the era applicable at this date. *

* The Symmetry454 calendar system uses {@link IsoEra}. * * @return the era applicable at this date, not null */ @Override public IsoEra getEra() { return (prolepticYear >= 1 ? IsoEra.CE : IsoEra.BCE); } /** * Returns the length of the month represented by this date. *

* This returns the length of the month in days. * Month lengths do not match those of the ISO calendar system. *

* Most months have 30 days, except for February, May, August, November each have 31 days. * December in a leap year has 37 days. * * @return the length of the month in days */ @Override public int lengthOfMonth() { return this.isLeapYear() && this.month == MONTHS_IN_YEAR ? DAYS_IN_MONTH + 7 : this.month % 3 == 2 ? DAYS_IN_MONTH_LONG : DAYS_IN_MONTH; } /** * Returns the length of the year represented by this date. *

* This returns the length of the year in days. * Year lengths do NOT match those of the ISO calendar system. * * @return the length of the year in days: 364 or 371 */ @Override public int lengthOfYear() { return isLeapYear() ? DAYS_IN_YEAR_LONG : DAYS_IN_YEAR; } //------------------------------------------------------------------------- @Override public Symmetry010Date with(TemporalAdjuster adjuster) { return (Symmetry010Date) adjuster.adjustInto(this); } @Override public Symmetry010Date with(TemporalField field, long newValue) { if (field instanceof ChronoField) { if (newValue == 0) { return this; } ChronoField f = (ChronoField) field; getChronology().range(f).checkValidValue(newValue, f); int nval = (int) newValue; switch (f) { case DAY_OF_MONTH: return create(prolepticYear, month, nval); case DAY_OF_WEEK: int week = (this.dayOfYear - 1) / 7; int yd = 7 * week + nval; return ofYearDay(prolepticYear, yd); default: break; } } return (Symmetry010Date) super.with(field, newValue); } @Override Symmetry010Date withDayOfYear(int value) { return ofYearDay(prolepticYear, value); } //----------------------------------------------------------------------- @Override public Symmetry010Date plus(TemporalAmount amount) { return (Symmetry010Date) amount.addTo(this); } @Override public Symmetry010Date plus(long amountToAdd, TemporalUnit unit) { return (Symmetry010Date) super.plus(amountToAdd, unit); } @Override public Symmetry010Date minus(TemporalAmount amount) { return (Symmetry010Date) amount.subtractFrom(this); } @Override public Symmetry010Date minus(long amountToSubtract, TemporalUnit unit) { return (Symmetry010Date) super.minus(amountToSubtract, unit); } //------------------------------------------------------------------------- @Override // for covariant return type @SuppressWarnings("unchecked") public ChronoLocalDateTime atTime(LocalTime localTime) { return (ChronoLocalDateTime) super.atTime(localTime); } @Override public long until(Temporal endExclusive, TemporalUnit unit) { return until(Symmetry010Date.from(endExclusive), unit); } /** * Get the number of years from this date to the given day. * * @param end The end date. * @return The number of years from this date to the given day. */ long yearsUntil(Symmetry010Date end) { long startYear = this.prolepticYear * 512L + this.getDayOfYear(); long endYear = end.prolepticYear * 512L + end.getDayOfYear(); return (endYear - startYear) / 512L; } @Override public ChronoPeriod until(ChronoLocalDate endDateExclusive) { Symmetry010Date end = Symmetry010Date.from(endDateExclusive); int years = Math.toIntExact(yearsUntil(end)); // Get to the same "whole" year. Symmetry010Date sameYearEnd = (Symmetry010Date) plusYears(years); int months = (int) sameYearEnd.monthsUntil(end); int days = (int) sameYearEnd.plusMonths(months).daysUntil(end); return getChronology().period(years, months, days); } @Override long weeksUntil(AbstractDate end) { Symmetry010Date endDate = Symmetry010Date.from(end); long startWeek = this.getProlepticWeek() * 8L + this.getDayOfWeek(); long endWeek = endDate.getProlepticWeek() * 8L + endDate.getDayOfWeek(); return (endWeek - startWeek) / 8L; } @Override long monthsUntil(AbstractDate end) { Symmetry010Date date = Symmetry010Date.from(end); long monthStart = this.getProlepticMonth() * 64L + this.getDayOfMonth(); long monthEnd = date.getProlepticMonth() * 64L + date.getDayOfMonth(); return (monthEnd - monthStart) / 64L; } //----------------------------------------------------------------------- @Override public long toEpochDay() { long epochDay = (long) (this.prolepticYear - 1) * DAYS_IN_YEAR + Symmetry010Chronology.getLeapYearsBefore(this.prolepticYear) * DAYS_IN_WEEK + this.dayOfYear - DAYS_0001_TO_1970 - 1; return epochDay; } /** * Display the date in human-readable format. * * @return the string representation */ @Override public String toString() { StringBuilder buf = new StringBuilder(30); return buf.append(getChronology().toString()) .append(' ') .append(getEra()) .append(' ') .append(getYearOfEra()) .append(this.month < 10 && this.month > 0 ? "/0" : '/') .append(this.month) .append(this.day < 10 ? "/0" : '/') .append(this.day) .toString(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy