GregorianCalendar with a custom cutover date sets unexpected date before cutover

699 views Asked by At

TL;DR: I'm getting a strange result when setting a GregorianCalendar with a custom Julian->Gregorian cutover date. 1 Jan 1800 becomes 12 Jan 1800, where 1800 is before the custom cutover date (31 Jan 1918) but after the default cutover date (15 Oct 1582). (This does not happen before the default cutover date or on the default calendar.)

This is part of a bigger algorithm where I want to use GregorianCalendar to calculate certain dates in a year, hoping to benefit by having the Julian/Gregorian leap year calculations be transparent to me, since the dates may be either before or after the cutover date.

I'm trying to use the following properties of GregorianCalendar, quoted from GregorianCalendar API docs:

  • GregorianCalendar is a hybrid calendar that supports both the Julian and Gregorian calendar systems with the support of a single discontinuity, which corresponds by default to the Gregorian date when the Gregorian calendar was instituted (October 15, 1582 in some countries, later in others). The cutover date may be changed by the caller by calling setGregorianChange().

  • Before the Gregorian cutover, GregorianCalendar implements the Julian calendar. The only difference between the Gregorian and the Julian calendar is the leap year rule.

  • Prior to the institution of the Gregorian calendar, New Year's Day was March 25. To avoid confusion, this calendar always uses January 1.

To replicate the base problem, here is a basic Java main() method. I create 2 calendars, one the default Gregorian, and another one a Gregorian with the cutover date when Russia adopted it, i.e. 31 Jan 1918. Then I set both calendars to 1 Jan 1800. The "Russian" calendar changes this date to 12 Jan 1800, as shown when printed out immediately after the set.

public static void main(String[] args) {
  DateFormat DF = DateFormat.getDateInstance(DateFormat.SHORT);

  System.out.printf("Generic Gregorian Calendar (after cutover)%n");
  GregorianCalendar genericCal = new GregorianCalendar();
  System.out.printf("  Changeover=%s%n", DF.format(genericCal.getGregorianChange()));
  genericCal.set(1800, Calendar.JANUARY, 1);
  System.out.printf("%3s  %s%n", "", DF.format(genericCal.getTime()));

  System.out.printf("Russian Gregorian Calendar (before cutover)%n");
  GregorianCalendar russianCal = new GregorianCalendar();
  russianCal.setGregorianChange(new GregorianCalendar(1918, Calendar.JANUARY, 31).getTime());
  System.out.printf("  Changeover=%s%n", DF.format(russianCal.getGregorianChange()));
  russianCal.set(1800, Calendar.JANUARY, 1);
  System.out.printf("%3s  %s%n", "", DF.format(russianCal.getTime()));
  for (int i = 1; i < 15; i++) {
    russianCal.add(Calendar.DAY_OF_YEAR, -1);
    System.out.printf("%3d: %s %n", -i, DF.format(russianCal.getTime()));
  }
}

This outputs:

Generic Gregorian Calendar (after cutover)
  Changeover=1582/10/15
     1800/01/01
Russian Gregorian Calendar (before cutover)
  Changeover=1918/01/31
     1800/01/12
 -1: 1800/01/11 
 -2: 1800/01/10 
 -3: 1800/01/09 
 -4: 1800/01/08 
 -5: 1800/01/07 
 -6: 1800/01/06 
 -7: 1800/01/05 
 -8: 1800/01/04 
 -9: 1800/01/03 
-10: 1800/01/02 
-11: 1800/01/01 
-12: 1799/12/31 
-13: 1799/12/30 
-14: 1799/12/29 

The 11-day difference looks similar to the days lost when the Julian/Gregorian switchover would be made, but that would only apply during the period in 1918 after 31 Jan, in this instance (31 Jan was followed by 14 Feb in 1918 in Russia, which is a 13-day difference).

I would appreciate any explanation, or help to get the date set to what I intend.

Unfortunately I'm stuck with standard Java 8 libraries, no 3rd party libraries possible at this time. Also, it seems the new java.time classes won't "automatically" help with the Julian/Gregorian transition (if I'm wrong I welcome pointers), so my Plan B would be to simply do the calculations without using any date classes.

1

There are 1 answers

2
Anonymous On BEST ANSWER

Your code behaves correctly though confusingly, I agree. For formatting your Russian date you need to instruct your formatter to use the Russian Gregorian crossover date.

Setting your Russian calendar to 1 Jan 1800 works. There is no change happening.

    GregorianCalendar russianCal = new GregorianCalendar();
    russianCal.setGregorianChange(
            new GregorianCalendar(1918, Calendar.JANUARY, 31).getTime());
    russianCal.set(1800, Calendar.JANUARY, 1);
    
    System.out.println("Russian month (0-based):      "
            + russianCal.get(Calendar.MONTH));
    System.out.println("Russian day of month:         "
            + russianCal.get(Calendar.DATE));

Output is:

Russian month (0-based):      0
Russian day of month:         1

The date is 1 Jan as it should (Calendar confusingly uses 0 for January).

When you take out the time into a Date, you get a normal java.util.Date, there’s nothing Russian nor Julian about it. The Date object represents some time of the day that is 1 Jan in Russia and 12 Jan in the Gregorian calendar, in your default time zone. When you further format this Date using a DateFormat with default settings, is uses the standard Gregorian crossover date in 1582 and therefore prints 12 Jan. To print 1 Jan as in the Russian calendar you need to instruct the formatter to use the Russian Gregorian crossover date. You do this by passing it a GregorianCalendar that uses the desired crossover date:

    Date dateCorrespondingToRussianCal = russianCal.getTime();
    System.out.println("Corresponding java.util.Date: "
            + dateCorrespondingToRussianCal);
    
    DateFormat russianDateFormatter
            = DateFormat.getDateInstance(DateFormat.SHORT);
    russianDateFormatter.setCalendar(russianCal);
    System.out.println("Formatted Russian date:       "
            + russianDateFormatter.format(dateCorrespondingToRussianCal));

Output in my time zone:

Corresponding java.util.Date: Sun Jan 12 13:10:30 CET 1800
Formatted Russian date:       1/1/00

Other options?

Unfortunately you are correct: java.time, the modern Java date and time API, has no support for the Julian calendar out of the box. I read that you could not use any 3rd party libraries. Options to consider include:

  • For other readers that may use a 3rd party library: Joda-Time and it GJChronology. It

    Implements the Gregorian/Julian calendar system which is the calendar system used in most of the world. …

  • For yourself: You may develop your own Julian-Gregorian chronology to use with java.time. It would require an effort, but I would expect it to give a beautiful result.

Links