/*
 *  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 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 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 JulianChronology, and to create a pure proleptic Gregorian chronology, use GregorianChronology.

GJChronology is thread-safe and immutable.

Author:Brian S O'Neill, Stephen Colebourne
Since:1.0
/** * 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. * <p> * 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. * <p> * 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. * <p> * 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. * <p> * 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. * <p> * To create a pure proleptic Julian chronology, use {@link JulianChronology}, * and to create a pure proleptic Gregorian chronology, use * {@link GregorianChronology}. * <p> * 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
/** Serialization lock */
private static final long serialVersionUID = -2545574827706931671L;
Convert a datetime from one chronology to another.
/** * 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.
/** * 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.
/** * The default GregorianJulian cutover point. */
static final Instant DEFAULT_CUTOVER = new Instant(-12219292800000L);
Cache of zone to chronology list
/** Cache of zone to chronology list */
private static final ConcurrentHashMap<GJCacheKey, GJChronology> cCache = new ConcurrentHashMap<GJCacheKey, GJChronology>();
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 Monday, and the minimum days in the first week of the year is 4.

The time zone of the returned instance is UTC.

/** * 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). * * <p>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. * * <p>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 Monday, and the minimum days in the first week of the year is 4.

The returned chronology is in the default time zone.

/** * 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). * * <p>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. * * <p>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 Monday, and the minimum days in the first week of the year is 4.

Params:
  • zone – the time zone to use, null is default
/** * 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). * * <p>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 Monday, and the minimum days in the first week of the year is 4.

Params:
  • zone – the time zone to use, null is default
  • gregorianCutover – the cutover to use, null means default
/** * Factory method returns instances of the GJ cutover chronology. Any * cutover date may be specified. * * <p>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.
Params:
  • zone – the time zone to use, null is default
  • gregorianCutover – the cutover to use, null means default
  • minDaysInFirstWeek – minimum number of days in first week of the year; default is 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.
Params:
  • zone – the time zone to use, null is default
  • gregorianCutover – the cutover to use
  • minDaysInFirstWeek – minimum number of days in first week of the year; default is 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 * @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;
Params:
  • julian – chronology used before the cutover instant
  • gregorian – chronology used at and after the cutover instant
  • cutoverInstant – instant when the gregorian chronology began
/** * @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.
/** * 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
/** * 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.
Returns:the chronology in UTC
/** * 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.
Params:
  • zone – the zone to get the chronology in, null is default
Returns:the chronology
/** * 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.
Returns:the cutover 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.
Returns:the minimum days
/** * 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.
Params:
  • obj – the object to compare to
Returns:true if equal
Since:1.6
/** * 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.
Returns:the hash code
Since:1.6
/** * 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.
Returns:a debugging string
/** * 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.
/** * 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;
Params:
  • julianField – field from the chronology used before the cutover instant
  • gregorianField – field from the chronology used at and after the cutover
  • cutoverMillis – the millis of the cutover
/** * @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); }
Params:
  • julianField – field from the chronology used before the cutover instant
  • gregorianField – field from the chronology used at and after the cutover
  • cutoverMillis – the millis of the cutover
  • 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 cutoverMillis the millis of the cutover * @param convertByWeekyear */
CutoverField(DateTimeField julianField, DateTimeField gregorianField, long cutoverMillis, boolean convertByWeekyear) { this(julianField, gregorianField, null, cutoverMillis, convertByWeekyear); }
Params:
  • julianField – field from the chronology used before the cutover instant
  • gregorianField – field from the chronology used at and after the cutover
  • rangeField – the range field
  • cutoverMillis – the millis of the cutover
  • 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.
/** * 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.
/** * 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.
Params:
  • durationField – shared duration field
/** * 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.
Params:
  • durationField – shared duration field
/** * 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.
Params:
  • durationField – shared duration field
/** * 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.
/** * 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); } } }