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

org.joda.time.chrono.GJChronology Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 *  Copyright 2001-2014 Stephen Colebourne
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.joda.time.chrono;

import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;

import org.joda.time.Chronology;
import org.joda.time.DateTimeField;
import org.joda.time.DateTimeUtils;
import org.joda.time.DateTimeZone;
import org.joda.time.DurationField;
import org.joda.time.IllegalFieldValueException;
import org.joda.time.Instant;
import org.joda.time.LocalDate;
import org.joda.time.ReadableInstant;
import org.joda.time.ReadablePartial;
import org.joda.time.field.BaseDateTimeField;
import org.joda.time.field.DecoratedDurationField;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;

/**
 * Implements the Gregorian/Julian calendar system which is the calendar system
 * used in most of the world. Wherever possible, it is recommended to use the
 * {@link ISOChronology} instead.
 * 

* The Gregorian calendar replaced the Julian calendar, and the point in time * when this chronology switches can be controlled using the second parameter * of the getInstance method. By default this cutover is set to the date the * Gregorian calendar was first instituted, October 15, 1582. *

* Before this date, this chronology uses the proleptic Julian calendar * (proleptic means extending indefinitely). The Julian calendar has leap years * every four years, whereas the Gregorian has special rules for 100 and 400 * years. A meaningful result will thus be obtained for all input values. * However before 8 CE, Julian leap years were irregular, and before 45 BCE * there was no Julian calendar. *

* This chronology differs from * {@link java.util.GregorianCalendar GregorianCalendar} in that years * in BCE are returned correctly. Thus year 1 BCE is returned as -1 instead of 1. * The yearOfEra field produces results compatible with GregorianCalendar. *

* The Julian calendar does not have a year zero, and so year -1 is followed by * year 1. If the Gregorian cutover date is specified at or before year -1 * (Julian), year zero is defined. In other words, the proleptic Gregorian * chronology used by this class has a year zero. *

* To create a pure proleptic Julian chronology, use {@link JulianChronology}, * and to create a pure proleptic Gregorian chronology, use * {@link GregorianChronology}. *

* GJChronology is thread-safe and immutable. * * @author Brian S O'Neill * @author Stephen Colebourne * @since 1.0 */ public final class GJChronology extends AssembledChronology { /** Serialization lock */ private static final long serialVersionUID = -2545574827706931671L; /** * Convert a datetime from one chronology to another. */ private static long convertByYear(long instant, Chronology from, Chronology to) { return to.getDateTimeMillis (from.year().get(instant), from.monthOfYear().get(instant), from.dayOfMonth().get(instant), from.millisOfDay().get(instant)); } /** * Convert a datetime from one chronology to another. */ private static long convertByWeekyear(final long instant, Chronology from, Chronology to) { long newInstant; newInstant = to.weekyear().set(0, from.weekyear().get(instant)); newInstant = to.weekOfWeekyear().set(newInstant, from.weekOfWeekyear().get(instant)); newInstant = to.dayOfWeek().set(newInstant, from.dayOfWeek().get(instant)); newInstant = to.millisOfDay().set(newInstant, from.millisOfDay().get(instant)); return newInstant; } /** * The default GregorianJulian cutover point. */ static final Instant DEFAULT_CUTOVER = new Instant(-12219292800000L); /** Cache of zone to chronology list */ private static final ConcurrentHashMap cCache = new ConcurrentHashMap(); /** * Factory method returns instances of the default GJ cutover * chronology. This uses a cutover date of October 15, 1582 (Gregorian) * 00:00:00 UTC. For this value, October 4, 1582 (Julian) is followed by * October 15, 1582 (Gregorian). * *

The first day of the week is designated to be * {@link org.joda.time.DateTimeConstants#MONDAY Monday}, * and the minimum days in the first week of the year is 4. * *

The time zone of the returned instance is UTC. */ public static GJChronology getInstanceUTC() { return getInstance(DateTimeZone.UTC, DEFAULT_CUTOVER, 4); } /** * Factory method returns instances of the default GJ cutover * chronology. This uses a cutover date of October 15, 1582 (Gregorian) * 00:00:00 UTC. For this value, October 4, 1582 (Julian) is followed by * October 15, 1582 (Gregorian). * *

The first day of the week is designated to be * {@link org.joda.time.DateTimeConstants#MONDAY Monday}, * and the minimum days in the first week of the year is 4. * *

The returned chronology is in the default time zone. */ public static GJChronology getInstance() { return getInstance(DateTimeZone.getDefault(), DEFAULT_CUTOVER, 4); } /** * Factory method returns instances of the GJ cutover chronology. This uses * a cutover date of October 15, 1582 (Gregorian) 00:00:00 UTC. For this * value, October 4, 1582 (Julian) is followed by October 15, 1582 * (Gregorian). * *

The first day of the week is designated to be * {@link org.joda.time.DateTimeConstants#MONDAY Monday}, * and the minimum days in the first week of the year is 4. * * @param zone the time zone to use, null is default */ public static GJChronology getInstance(DateTimeZone zone) { return getInstance(zone, DEFAULT_CUTOVER, 4); } /** * Factory method returns instances of the GJ cutover chronology. Any * cutover date may be specified. * *

The first day of the week is designated to be * {@link org.joda.time.DateTimeConstants#MONDAY Monday}, * and the minimum days in the first week of the year is 4. * * @param zone the time zone to use, null is default * @param gregorianCutover the cutover to use, null means default */ public static GJChronology getInstance( DateTimeZone zone, ReadableInstant gregorianCutover) { return getInstance(zone, gregorianCutover, 4); } /** * Factory method returns instances of the GJ cutover chronology. Any * cutover date may be specified. * * @param zone the time zone to use, null is default * @param gregorianCutover the cutover to use, null means default * @param minDaysInFirstWeek minimum number of days in first week of the year; default is 4 */ public static GJChronology getInstance( DateTimeZone zone, ReadableInstant gregorianCutover, int minDaysInFirstWeek) { zone = DateTimeUtils.getZone(zone); Instant cutoverInstant; if (gregorianCutover == null) { cutoverInstant = DEFAULT_CUTOVER; } else { cutoverInstant = gregorianCutover.toInstant(); LocalDate cutoverDate = new LocalDate(cutoverInstant.getMillis(), GregorianChronology.getInstance(zone)); if (cutoverDate.getYear() <= 0) { throw new IllegalArgumentException("Cutover too early. Must be on or after 0001-01-01."); } } GJCacheKey cacheKey = new GJCacheKey(zone, cutoverInstant, minDaysInFirstWeek); GJChronology chrono = cCache.get(cacheKey); if (chrono == null) { if (zone == DateTimeZone.UTC) { chrono = new GJChronology (JulianChronology.getInstance(zone, minDaysInFirstWeek), GregorianChronology.getInstance(zone, minDaysInFirstWeek), cutoverInstant); } else { chrono = getInstance(DateTimeZone.UTC, cutoverInstant, minDaysInFirstWeek); chrono = new GJChronology (ZonedChronology.getInstance(chrono, zone), chrono.iJulianChronology, chrono.iGregorianChronology, chrono.iCutoverInstant); } GJChronology oldChrono = cCache.putIfAbsent(cacheKey, chrono); if (oldChrono != null) { chrono = oldChrono; } } return chrono; } /** * Factory method returns instances of the GJ cutover chronology. Any * cutover date may be specified. * * @param zone the time zone to use, null is default * @param gregorianCutover the cutover to use * @param minDaysInFirstWeek minimum number of days in first week of the year; default is 4 */ public static GJChronology getInstance( DateTimeZone zone, long gregorianCutover, int minDaysInFirstWeek) { Instant cutoverInstant; if (gregorianCutover == DEFAULT_CUTOVER.getMillis()) { cutoverInstant = null; } else { cutoverInstant = new Instant(gregorianCutover); } return getInstance(zone, cutoverInstant, minDaysInFirstWeek); } //----------------------------------------------------------------------- private JulianChronology iJulianChronology; private GregorianChronology iGregorianChronology; private Instant iCutoverInstant; private long iCutoverMillis; private long iGapDuration; /** * @param julian chronology used before the cutover instant * @param gregorian chronology used at and after the cutover instant * @param cutoverInstant instant when the gregorian chronology began */ private GJChronology(JulianChronology julian, GregorianChronology gregorian, Instant cutoverInstant) { super(null, new Object[] {julian, gregorian, cutoverInstant}); } /** * Called when applying a time zone. */ private GJChronology(Chronology base, JulianChronology julian, GregorianChronology gregorian, Instant cutoverInstant) { super(base, new Object[] {julian, gregorian, cutoverInstant}); } /** * Serialization singleton */ private Object readResolve() { return getInstance(getZone(), iCutoverInstant, getMinimumDaysInFirstWeek()); } public DateTimeZone getZone() { Chronology base; if ((base = getBase()) != null) { return base.getZone(); } return DateTimeZone.UTC; } // Conversion //----------------------------------------------------------------------- /** * Gets the Chronology in the UTC time zone. * * @return the chronology in UTC */ public Chronology withUTC() { return withZone(DateTimeZone.UTC); } /** * Gets the Chronology in a specific time zone. * * @param zone the zone to get the chronology in, null is default * @return the chronology */ public Chronology withZone(DateTimeZone zone) { if (zone == null) { zone = DateTimeZone.getDefault(); } if (zone == getZone()) { return this; } return getInstance(zone, iCutoverInstant, getMinimumDaysInFirstWeek()); } public long getDateTimeMillis(int year, int monthOfYear, int dayOfMonth, int millisOfDay) throws IllegalArgumentException { Chronology base; if ((base = getBase()) != null) { return base.getDateTimeMillis(year, monthOfYear, dayOfMonth, millisOfDay); } // Assume date is Gregorian. long instant = iGregorianChronology.getDateTimeMillis (year, monthOfYear, dayOfMonth, millisOfDay); if (instant < iCutoverMillis) { // Maybe it's Julian. instant = iJulianChronology.getDateTimeMillis (year, monthOfYear, dayOfMonth, millisOfDay); if (instant >= iCutoverMillis) { // Okay, it's in the illegal cutover gap. throw new IllegalArgumentException("Specified date does not exist"); } } return instant; } public long getDateTimeMillis(int year, int monthOfYear, int dayOfMonth, int hourOfDay, int minuteOfHour, int secondOfMinute, int millisOfSecond) throws IllegalArgumentException { Chronology base; if ((base = getBase()) != null) { return base.getDateTimeMillis (year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond); } // Assume date is Gregorian. long instant; try { instant = iGregorianChronology.getDateTimeMillis (year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond); } catch (IllegalFieldValueException ex) { if (monthOfYear != 2 || dayOfMonth != 29) { throw ex; } instant = iGregorianChronology.getDateTimeMillis (year, monthOfYear, 28, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond); if (instant >= iCutoverMillis) { throw ex; } } if (instant < iCutoverMillis) { // Maybe it's Julian. instant = iJulianChronology.getDateTimeMillis (year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond); if (instant >= iCutoverMillis) { // Okay, it's in the illegal cutover gap. throw new IllegalArgumentException("Specified date does not exist"); } } return instant; } /** * Gets the cutover instant between Gregorian and Julian chronologies. * @return the cutover instant */ public Instant getGregorianCutover() { return iCutoverInstant; } /** * Gets the minimum days needed for a week to be the first week in a year. * * @return the minimum days */ public int getMinimumDaysInFirstWeek() { return iGregorianChronology.getMinimumDaysInFirstWeek(); } //----------------------------------------------------------------------- /** * Checks if this chronology instance equals another. * * @param obj the object to compare to * @return true if equal * @since 1.6 */ public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof GJChronology) { GJChronology chrono = (GJChronology) obj; return iCutoverMillis == chrono.iCutoverMillis && getMinimumDaysInFirstWeek() == chrono.getMinimumDaysInFirstWeek() && getZone().equals(chrono.getZone()); } return false; } /** * A suitable hash code for the chronology. * * @return the hash code * @since 1.6 */ public int hashCode() { return "GJ".hashCode() * 11 + getZone().hashCode() + getMinimumDaysInFirstWeek() + iCutoverInstant.hashCode(); } // Output //----------------------------------------------------------------------- /** * Gets a debugging toString. * * @return a debugging string */ public String toString() { StringBuffer sb = new StringBuffer(60); sb.append("GJChronology"); sb.append('['); sb.append(getZone().getID()); if (iCutoverMillis != DEFAULT_CUTOVER.getMillis()) { sb.append(",cutover="); DateTimeFormatter printer; if (withUTC().dayOfYear().remainder(iCutoverMillis) == 0) { printer = ISODateTimeFormat.date(); } else { printer = ISODateTimeFormat.dateTime(); } printer.withChronology(withUTC()).printTo(sb, iCutoverMillis); } if (getMinimumDaysInFirstWeek() != 4) { sb.append(",mdfw="); sb.append(getMinimumDaysInFirstWeek()); } sb.append(']'); return sb.toString(); } protected void assemble(Fields fields) { Object[] params = (Object[])getParam(); JulianChronology julian = (JulianChronology)params[0]; GregorianChronology gregorian = (GregorianChronology)params[1]; Instant cutoverInstant = (Instant)params[2]; iCutoverMillis = cutoverInstant.getMillis(); iJulianChronology = julian; iGregorianChronology = gregorian; iCutoverInstant = cutoverInstant; if (getBase() != null) { return; } if (julian.getMinimumDaysInFirstWeek() != gregorian.getMinimumDaysInFirstWeek()) { throw new IllegalArgumentException(); } // Compute difference between the chronologies at the cutover instant iGapDuration = iCutoverMillis - julianToGregorianByYear(iCutoverMillis); // Begin field definitions. // First just copy all the Gregorian fields and then override those // that need special attention. fields.copyFieldsFrom(gregorian); // Assuming cutover is at midnight, all time of day fields can be // gregorian since they are unaffected by cutover. // Verify assumption. if (gregorian.millisOfDay().get(iCutoverMillis) == 0) { // Cutover is sometime in the day, so cutover fields are required // for time of day. fields.millisOfSecond = new CutoverField(julian.millisOfSecond(), fields.millisOfSecond, iCutoverMillis); fields.millisOfDay = new CutoverField(julian.millisOfDay(), fields.millisOfDay, iCutoverMillis); fields.secondOfMinute = new CutoverField(julian.secondOfMinute(), fields.secondOfMinute, iCutoverMillis); fields.secondOfDay = new CutoverField(julian.secondOfDay(), fields.secondOfDay, iCutoverMillis); fields.minuteOfHour = new CutoverField(julian.minuteOfHour(), fields.minuteOfHour, iCutoverMillis); fields.minuteOfDay = new CutoverField(julian.minuteOfDay(), fields.minuteOfDay, iCutoverMillis); fields.hourOfDay = new CutoverField(julian.hourOfDay(), fields.hourOfDay, iCutoverMillis); fields.hourOfHalfday = new CutoverField(julian.hourOfHalfday(), fields.hourOfHalfday, iCutoverMillis); fields.clockhourOfDay = new CutoverField(julian.clockhourOfDay(), fields.clockhourOfDay, iCutoverMillis); fields.clockhourOfHalfday = new CutoverField(julian.clockhourOfHalfday(), fields.clockhourOfHalfday, iCutoverMillis); fields.halfdayOfDay = new CutoverField(julian.halfdayOfDay(), fields.halfdayOfDay, iCutoverMillis); } // These fields just require basic cutover support. { fields.era = new CutoverField(julian.era(), fields.era, iCutoverMillis); } // These fields are special because they have imprecise durations. The // family of addition methods need special attention. Override affected // duration fields as well. { fields.year = new ImpreciseCutoverField( julian.year(), fields.year, iCutoverMillis); fields.years = fields.year.getDurationField(); fields.yearOfEra = new ImpreciseCutoverField( julian.yearOfEra(), fields.yearOfEra, fields.years, iCutoverMillis); fields.centuryOfEra = new ImpreciseCutoverField( julian.centuryOfEra(), fields.centuryOfEra, iCutoverMillis); fields.centuries = fields.centuryOfEra.getDurationField(); fields.yearOfCentury = new ImpreciseCutoverField( julian.yearOfCentury(), fields.yearOfCentury, fields.years, fields.centuries, iCutoverMillis); fields.monthOfYear = new ImpreciseCutoverField( julian.monthOfYear(), fields.monthOfYear, null, fields.years, iCutoverMillis); fields.months = fields.monthOfYear.getDurationField(); fields.weekyear = new ImpreciseCutoverField( julian.weekyear(), fields.weekyear, null, iCutoverMillis, true); fields.weekyears = fields.weekyear.getDurationField(); fields.weekyearOfCentury = new ImpreciseCutoverField( julian.weekyearOfCentury(), fields.weekyearOfCentury, fields.weekyears, fields.centuries, iCutoverMillis); } // DayOfYear and weekOfWeekyear require special handling since cutover // year has fewer days and weeks. Extend the cutover to the start of // the next year or weekyear. This keeps the sequence unbroken during // the cutover year. { long cutover = gregorian.year().roundCeiling(iCutoverMillis); fields.dayOfYear = new CutoverField( julian.dayOfYear(), fields.dayOfYear, fields.years, cutover, false); } { long cutover = gregorian.weekyear().roundCeiling(iCutoverMillis); fields.weekOfWeekyear = new CutoverField( julian.weekOfWeekyear(), fields.weekOfWeekyear, fields.weekyears, cutover, true); } // These fields require basic cutover support, except they must link to // imprecise durations. { CutoverField cf = new CutoverField (julian.dayOfMonth(), fields.dayOfMonth, iCutoverMillis); cf.iRangeDurationField = fields.months; fields.dayOfMonth = cf; } } long julianToGregorianByYear(long instant) { return convertByYear(instant, iJulianChronology, iGregorianChronology); } long gregorianToJulianByYear(long instant) { return convertByYear(instant, iGregorianChronology, iJulianChronology); } long julianToGregorianByWeekyear(long instant) { return convertByWeekyear(instant, iJulianChronology, iGregorianChronology); } long gregorianToJulianByWeekyear(long instant) { return convertByWeekyear(instant, iGregorianChronology, iJulianChronology); } //----------------------------------------------------------------------- /** * This basic cutover field adjusts calls to 'get' and 'set' methods, and * assumes that calls to add and addWrapField are unaffected by the cutover. */ private class CutoverField extends BaseDateTimeField { @SuppressWarnings("unused") private static final long serialVersionUID = 3528501219481026402L; final DateTimeField iJulianField; final DateTimeField iGregorianField; final long iCutover; final boolean iConvertByWeekyear; protected DurationField iDurationField; protected DurationField iRangeDurationField; /** * @param julianField field from the chronology used before the cutover instant * @param gregorianField field from the chronology used at and after the cutover * @param cutoverMillis the millis of the cutover */ CutoverField(DateTimeField julianField, DateTimeField gregorianField, long cutoverMillis) { this(julianField, gregorianField, cutoverMillis, false); } /** * @param julianField field from the chronology used before the cutover instant * @param gregorianField field from the chronology used at and after the cutover * @param cutoverMillis the millis of the cutover * @param convertByWeekyear */ CutoverField(DateTimeField julianField, DateTimeField gregorianField, long cutoverMillis, boolean convertByWeekyear) { this(julianField, gregorianField, null, cutoverMillis, convertByWeekyear); } /** * @param julianField field from the chronology used before the cutover instant * @param gregorianField field from the chronology used at and after the cutover * @param rangeField the range field * @param cutoverMillis the millis of the cutover * @param convertByWeekyear */ CutoverField(DateTimeField julianField, DateTimeField gregorianField, DurationField rangeField, long cutoverMillis, boolean convertByWeekyear) { super(gregorianField.getType()); iJulianField = julianField; iGregorianField = gregorianField; iCutover = cutoverMillis; iConvertByWeekyear = convertByWeekyear; // Although average length of Julian and Gregorian years differ, // use the Gregorian duration field because it is more accurate. iDurationField = gregorianField.getDurationField(); if (rangeField == null) { rangeField = gregorianField.getRangeDurationField(); if (rangeField == null) { rangeField = julianField.getRangeDurationField(); } } iRangeDurationField = rangeField; } public boolean isLenient() { return false; } public int get(long instant) { if (instant >= iCutover) { return iGregorianField.get(instant); } else { return iJulianField.get(instant); } } public String getAsText(long instant, Locale locale) { if (instant >= iCutover) { return iGregorianField.getAsText(instant, locale); } else { return iJulianField.getAsText(instant, locale); } } public String getAsText(int fieldValue, Locale locale) { return iGregorianField.getAsText(fieldValue, locale); } public String getAsShortText(long instant, Locale locale) { if (instant >= iCutover) { return iGregorianField.getAsShortText(instant, locale); } else { return iJulianField.getAsShortText(instant, locale); } } public String getAsShortText(int fieldValue, Locale locale) { return iGregorianField.getAsShortText(fieldValue, locale); } public long add(long instant, int value) { return iGregorianField.add(instant, value); } public long add(long instant, long value) { return iGregorianField.add(instant, value); } public int[] add(ReadablePartial partial, int fieldIndex, int[] values, int valueToAdd) { // overridden as superclass algorithm can't handle // 2004-02-29 + 48 months -> 2008-02-29 type dates if (valueToAdd == 0) { return values; } if (DateTimeUtils.isContiguous(partial)) { long instant = 0L; for (int i = 0, isize = partial.size(); i < isize; i++) { instant = partial.getFieldType(i).getField(GJChronology.this).set(instant, values[i]); } instant = add(instant, valueToAdd); return GJChronology.this.get(partial, instant); } else { return super.add(partial, fieldIndex, values, valueToAdd); } } public int getDifference(long minuendInstant, long subtrahendInstant) { return iGregorianField.getDifference(minuendInstant, subtrahendInstant); } public long getDifferenceAsLong(long minuendInstant, long subtrahendInstant) { return iGregorianField.getDifferenceAsLong(minuendInstant, subtrahendInstant); } public long set(long instant, int value) { if (instant >= iCutover) { instant = iGregorianField.set(instant, value); if (instant < iCutover) { // Only adjust if gap fully crossed. if (instant + iGapDuration < iCutover) { instant = gregorianToJulian(instant); } // Verify that new value stuck. if (get(instant) != value) { throw new IllegalFieldValueException (iGregorianField.getType(), Integer.valueOf(value), null, null); } } } else { instant = iJulianField.set(instant, value); if (instant >= iCutover) { // Only adjust if gap fully crossed. if (instant - iGapDuration >= iCutover) { instant = julianToGregorian(instant); } // Verify that new value stuck. if (get(instant) != value) { throw new IllegalFieldValueException (iJulianField.getType(), Integer.valueOf(value), null, null); } } } return instant; } public long set(long instant, String text, Locale locale) { if (instant >= iCutover) { instant = iGregorianField.set(instant, text, locale); if (instant < iCutover) { // Only adjust if gap fully crossed. if (instant + iGapDuration < iCutover) { instant = gregorianToJulian(instant); } // Cannot verify that new value stuck because set may be lenient. } } else { instant = iJulianField.set(instant, text, locale); if (instant >= iCutover) { // Only adjust if gap fully crossed. if (instant - iGapDuration >= iCutover) { instant = julianToGregorian(instant); } // Cannot verify that new value stuck because set may be lenient. } } return instant; } public DurationField getDurationField() { return iDurationField; } public DurationField getRangeDurationField() { return iRangeDurationField; } public boolean isLeap(long instant) { if (instant >= iCutover) { return iGregorianField.isLeap(instant); } else { return iJulianField.isLeap(instant); } } public int getLeapAmount(long instant) { if (instant >= iCutover) { return iGregorianField.getLeapAmount(instant); } else { return iJulianField.getLeapAmount(instant); } } public DurationField getLeapDurationField() { return iGregorianField.getLeapDurationField(); } public int getMinimumValue() { // For all precise fields, the Julian and Gregorian limits are // identical. Choose Julian to tighten up the year limits. return iJulianField.getMinimumValue(); } public int getMinimumValue(ReadablePartial partial) { return iJulianField.getMinimumValue(partial); } public int getMinimumValue(ReadablePartial partial, int[] values) { return iJulianField.getMinimumValue(partial, values); } public int getMinimumValue(long instant) { if (instant < iCutover) { return iJulianField.getMinimumValue(instant); } int min = iGregorianField.getMinimumValue(instant); // Because the cutover may reduce the length of this field, verify // the minimum by setting it. instant = iGregorianField.set(instant, min); if (instant < iCutover) { min = iGregorianField.get(iCutover); } return min; } public int getMaximumValue() { // For all precise fields, the Julian and Gregorian limits are // identical. return iGregorianField.getMaximumValue(); } public int getMaximumValue(long instant) { if (instant >= iCutover) { return iGregorianField.getMaximumValue(instant); } int max = iJulianField.getMaximumValue(instant); // Because the cutover may reduce the length of this field, verify // the maximum by setting it. instant = iJulianField.set(instant, max); if (instant >= iCutover) { max = iJulianField.get(iJulianField.add(iCutover, -1)); } return max; } public int getMaximumValue(ReadablePartial partial) { long instant = GJChronology.getInstanceUTC().set(partial, 0L); return getMaximumValue(instant); } public int getMaximumValue(ReadablePartial partial, int[] values) { Chronology chrono = GJChronology.getInstanceUTC(); long instant = 0L; for (int i = 0, isize = partial.size(); i < isize; i++) { DateTimeField field = partial.getFieldType(i).getField(chrono); if (values[i] <= field.getMaximumValue(instant)) { instant = field.set(instant, values[i]); } } return getMaximumValue(instant); } public long roundFloor(long instant) { if (instant >= iCutover) { instant = iGregorianField.roundFloor(instant); if (instant < iCutover) { // Only adjust if gap fully crossed. if (instant + iGapDuration < iCutover) { instant = gregorianToJulian(instant); } } } else { instant = iJulianField.roundFloor(instant); } return instant; } public long roundCeiling(long instant) { if (instant >= iCutover) { instant = iGregorianField.roundCeiling(instant); } else { instant = iJulianField.roundCeiling(instant); if (instant >= iCutover) { // Only adjust if gap fully crossed. if (instant - iGapDuration >= iCutover) { instant = julianToGregorian(instant); } } } return instant; } public int getMaximumTextLength(Locale locale) { return Math.max(iJulianField.getMaximumTextLength(locale), iGregorianField.getMaximumTextLength(locale)); } public int getMaximumShortTextLength(Locale locale) { return Math.max(iJulianField.getMaximumShortTextLength(locale), iGregorianField.getMaximumShortTextLength(locale)); } protected long julianToGregorian(long instant) { if (iConvertByWeekyear) { return julianToGregorianByWeekyear(instant); } else { return julianToGregorianByYear(instant); } } protected long gregorianToJulian(long instant) { if (iConvertByWeekyear) { return gregorianToJulianByWeekyear(instant); } else { return gregorianToJulianByYear(instant); } } } //----------------------------------------------------------------------- /** * Cutover field for variable length fields. These fields internally call * set whenever add is called. As a result, the same correction applied to * set must be applied to add and addWrapField. Knowing when to use this * field requires specific knowledge of how the GJ fields are implemented. */ private final class ImpreciseCutoverField extends CutoverField { @SuppressWarnings("unused") private static final long serialVersionUID = 3410248757173576441L; /** * Creates a duration field that links back to this. */ ImpreciseCutoverField(DateTimeField julianField, DateTimeField gregorianField, long cutoverMillis) { this(julianField, gregorianField, null, cutoverMillis, false); } /** * Uses a shared duration field rather than creating a new one. * * @param durationField shared duration field */ ImpreciseCutoverField(DateTimeField julianField, DateTimeField gregorianField, DurationField durationField, long cutoverMillis) { this(julianField, gregorianField, durationField, cutoverMillis, false); } /** * Uses shared duration fields rather than creating a new one. * * @param durationField shared duration field */ ImpreciseCutoverField(DateTimeField julianField, DateTimeField gregorianField, DurationField durationField, DurationField rangeDurationField, long cutoverMillis) { this(julianField, gregorianField, durationField, cutoverMillis, false); iRangeDurationField = rangeDurationField; } /** * Uses a shared duration field rather than creating a new one. * * @param durationField shared duration field */ ImpreciseCutoverField(DateTimeField julianField, DateTimeField gregorianField, DurationField durationField, long cutoverMillis, boolean convertByWeekyear) { super(julianField, gregorianField, cutoverMillis, convertByWeekyear); if (durationField == null) { durationField = new LinkedDurationField(iDurationField, this); } iDurationField = durationField; } public long add(long instant, int value) { if (instant >= iCutover) { instant = iGregorianField.add(instant, value); if (instant < iCutover) { // Only adjust if gap fully crossed. if (instant + iGapDuration < iCutover) { if (iConvertByWeekyear) { int wyear = iGregorianChronology.weekyear().get(instant); if (wyear <= 0) { instant = iGregorianChronology.weekyear().add(instant, -1); } } else { int year = iGregorianChronology.year().get(instant); if (year <= 0) { instant = iGregorianChronology.year().add(instant, -1); } } instant = gregorianToJulian(instant); } } } else { instant = iJulianField.add(instant, value); if (instant >= iCutover) { // Only adjust if gap fully crossed. if (instant - iGapDuration >= iCutover) { // no special handling for year zero as cutover always after year zero instant = julianToGregorian(instant); } } } return instant; } public long add(long instant, long value) { if (instant >= iCutover) { instant = iGregorianField.add(instant, value); if (instant < iCutover) { // Only adjust if gap fully crossed. if (instant + iGapDuration < iCutover) { if (iConvertByWeekyear) { int wyear = iGregorianChronology.weekyear().get(instant); if (wyear <= 0) { instant = iGregorianChronology.weekyear().add(instant, -1); } } else { int year = iGregorianChronology.year().get(instant); if (year <= 0) { instant = iGregorianChronology.year().add(instant, -1); } } instant = gregorianToJulian(instant); } } } else { instant = iJulianField.add(instant, value); if (instant >= iCutover) { // Only adjust if gap fully crossed. if (instant - iGapDuration >= iCutover) { // no special handling for year zero as cutover always after year zero instant = julianToGregorian(instant); } } } return instant; } public int getDifference(long minuendInstant, long subtrahendInstant) { if (minuendInstant >= iCutover) { if (subtrahendInstant >= iCutover) { return iGregorianField.getDifference(minuendInstant, subtrahendInstant); } // Remember, the add is being reversed. Since subtrahend is // Julian, convert minuend to Julian to match. minuendInstant = gregorianToJulian(minuendInstant); return iJulianField.getDifference(minuendInstant, subtrahendInstant); } else { if (subtrahendInstant < iCutover) { return iJulianField.getDifference(minuendInstant, subtrahendInstant); } // Remember, the add is being reversed. Since subtrahend is // Gregorian, convert minuend to Gregorian to match. minuendInstant = julianToGregorian(minuendInstant); return iGregorianField.getDifference(minuendInstant, subtrahendInstant); } } public long getDifferenceAsLong(long minuendInstant, long subtrahendInstant) { if (minuendInstant >= iCutover) { if (subtrahendInstant >= iCutover) { return iGregorianField.getDifferenceAsLong(minuendInstant, subtrahendInstant); } // Remember, the add is being reversed. Since subtrahend is // Julian, convert minuend to Julian to match. minuendInstant = gregorianToJulian(minuendInstant); return iJulianField.getDifferenceAsLong(minuendInstant, subtrahendInstant); } else { if (subtrahendInstant < iCutover) { return iJulianField.getDifferenceAsLong(minuendInstant, subtrahendInstant); } // Remember, the add is being reversed. Since subtrahend is // Gregorian, convert minuend to Gregorian to match. minuendInstant = julianToGregorian(minuendInstant); return iGregorianField.getDifferenceAsLong(minuendInstant, subtrahendInstant); } } // Since the imprecise fields have durations longer than the gap // duration, keep these methods simple. The inherited implementations // produce incorrect results. // // Degenerate case: If this field is a month, and the cutover is set // far into the future, then the gap duration may be so large as to // reduce the number of months in a year. If the missing month(s) are // at the beginning or end of the year, then the minimum and maximum // values are not 1 and 12. I don't expect this case to ever occur. public int getMinimumValue(long instant) { if (instant >= iCutover) { return iGregorianField.getMinimumValue(instant); } else { return iJulianField.getMinimumValue(instant); } } public int getMaximumValue(long instant) { if (instant >= iCutover) { return iGregorianField.getMaximumValue(instant); } else { return iJulianField.getMaximumValue(instant); } } } //----------------------------------------------------------------------- /** * Links the duration back to a ImpreciseCutoverField. */ private static class LinkedDurationField extends DecoratedDurationField { private static final long serialVersionUID = 4097975388007713084L; private final ImpreciseCutoverField iField; LinkedDurationField(DurationField durationField, ImpreciseCutoverField dateTimeField) { super(durationField, durationField.getType()); iField = dateTimeField; } public long add(long instant, int value) { return iField.add(instant, value); } public long add(long instant, long value) { return iField.add(instant, value); } public int getDifference(long minuendInstant, long subtrahendInstant) { return iField.getDifference(minuendInstant, subtrahendInstant); } public long getDifferenceAsLong(long minuendInstant, long subtrahendInstant) { return iField.getDifferenceAsLong(minuendInstant, subtrahendInstant); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy