具有自定义切换日期的 GregorianCalendar 在切换前设置了意外日期
GregorianCalendar with a custom cutover date sets unexpected date before cutover
TL;DR:使用自定义 Julian->Gregorian 转换日期设置 GregorianCalendar
时,我得到了一个奇怪的结果。 1800 年 1 月 1 日变为 1800 年 1 月 12 日,其中 1800 是之前自定义转换日期(1918 年 1 月 31 日),但 之后 默认转换日期(10 月 15 日) 1582)。 (这不会发生在默认转换日期之前或默认日历上。)
这是一个更大的算法的一部分,我想在其中使用 GregorianCalendar
来计算一年中的某些日期,希望通过让 Julian/Gregorian 闰年计算对我透明来受益,因为日期可能在切换日期之前或之后。
我正在尝试使用 GregorianCalendar
的以下属性,引用自 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.
要复制基本问题,这里有一个基本的 Java main()
方法。我创建了 2 个日历,一个是默认的公历,另一个是公历,在俄罗斯采用它时有转换日期,即 1918 年 1 月 31 日。然后我将两个日历都设置为 1800 年 1 月 1 日。“俄罗斯”日历发生变化这个日期到 1800 年 1 月 12 日,如设置后立即打印时所示。
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()));
}
}
这输出:
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
11 天的差异看起来类似于 Julian/Gregorian 转换时损失的天数,但这仅适用于 1 月 31 日之后的 1918 年期间,在这种情况下(随后是 1 月 31 日)到 1918 年 2 月 14 日在俄罗斯,相差 13 天)。
我将不胜感激任何解释,或帮助将日期设置为我想要的。
不幸的是,我受困于标准 Java 8 个库,目前无法使用第 3 方库。此外,新的 java.time
类 似乎不会“自动”帮助 Julian/Gregorian 过渡(如果我错了欢迎指点),所以我的 B 计划只是简单地不使用任何日期进行计算 类.
您的代码虽然令人困惑,但表现正确,我同意。要格式化您的俄语日期,您需要指示您的格式化程序使用俄语公历交叉日期。
将俄罗斯日历设置为 1800 年 1 月 1 日有效。没有发生任何变化。
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));
输出为:
Russian month (0-based): 0
Russian day of month: 1
日期应该是 1 月 1 日(Calendar
混淆地使用 0 表示 1 月)。
当你把时间拿出来变成 Date
时,你会得到一个正常的 java.util.Date
,没有任何俄罗斯和 Julian 的东西。 Date
对象表示一天中的某个时间,即俄罗斯的 1 月 1 日和公历的 1 月 12 日,在您的默认时区中。当您使用具有默认设置的 DateFormat
进一步格式化此 Date
时,将使用 1582 年的标准公历交叉日期,因此打印 1 月 12 日。要像俄罗斯日历一样打印 1 月 1 日,您需要指示 格式化程序 使用俄罗斯公历交叉日期。您可以通过向其传递一个使用所需交叉日期的 GregorianCalendar
来执行此操作:
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));
我所在时区的输出:
Corresponding java.util.Date: Sun Jan 12 13:10:30 CET 1800
Formatted Russian date: 1/1/00
其他选项?
很遗憾,您是对的:java.time,现代 Java 日期和时间 API,不支持开箱即用的儒略历。我读到你不能使用任何第 3 方库。要考虑的选项包括:
对于可能使用第 3 方库的其他读者:Joda-Time 及其 GJChronology
。它
Implements the Gregorian/Julian calendar system which is the calendar system used in most of the world. …
对于您自己:您可以开发自己的儒略-格里高利年表以与 java.time 一起使用。这需要付出努力,但我希望它能给出一个漂亮的结果。
链接
- Oracle tutorial: Date Time 解释如何使用 java.time.
- Documentation of Joda-Time’s
GJChronology
TL;DR:使用自定义 Julian->Gregorian 转换日期设置 GregorianCalendar
时,我得到了一个奇怪的结果。 1800 年 1 月 1 日变为 1800 年 1 月 12 日,其中 1800 是之前自定义转换日期(1918 年 1 月 31 日),但 之后 默认转换日期(10 月 15 日) 1582)。 (这不会发生在默认转换日期之前或默认日历上。)
这是一个更大的算法的一部分,我想在其中使用 GregorianCalendar
来计算一年中的某些日期,希望通过让 Julian/Gregorian 闰年计算对我透明来受益,因为日期可能在切换日期之前或之后。
我正在尝试使用 GregorianCalendar
的以下属性,引用自 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.
要复制基本问题,这里有一个基本的 Java main()
方法。我创建了 2 个日历,一个是默认的公历,另一个是公历,在俄罗斯采用它时有转换日期,即 1918 年 1 月 31 日。然后我将两个日历都设置为 1800 年 1 月 1 日。“俄罗斯”日历发生变化这个日期到 1800 年 1 月 12 日,如设置后立即打印时所示。
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()));
}
}
这输出:
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
11 天的差异看起来类似于 Julian/Gregorian 转换时损失的天数,但这仅适用于 1 月 31 日之后的 1918 年期间,在这种情况下(随后是 1 月 31 日)到 1918 年 2 月 14 日在俄罗斯,相差 13 天)。
我将不胜感激任何解释,或帮助将日期设置为我想要的。
不幸的是,我受困于标准 Java 8 个库,目前无法使用第 3 方库。此外,新的 java.time
类 似乎不会“自动”帮助 Julian/Gregorian 过渡(如果我错了欢迎指点),所以我的 B 计划只是简单地不使用任何日期进行计算 类.
您的代码虽然令人困惑,但表现正确,我同意。要格式化您的俄语日期,您需要指示您的格式化程序使用俄语公历交叉日期。
将俄罗斯日历设置为 1800 年 1 月 1 日有效。没有发生任何变化。
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));
输出为:
Russian month (0-based): 0 Russian day of month: 1
日期应该是 1 月 1 日(Calendar
混淆地使用 0 表示 1 月)。
当你把时间拿出来变成 Date
时,你会得到一个正常的 java.util.Date
,没有任何俄罗斯和 Julian 的东西。 Date
对象表示一天中的某个时间,即俄罗斯的 1 月 1 日和公历的 1 月 12 日,在您的默认时区中。当您使用具有默认设置的 DateFormat
进一步格式化此 Date
时,将使用 1582 年的标准公历交叉日期,因此打印 1 月 12 日。要像俄罗斯日历一样打印 1 月 1 日,您需要指示 格式化程序 使用俄罗斯公历交叉日期。您可以通过向其传递一个使用所需交叉日期的 GregorianCalendar
来执行此操作:
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));
我所在时区的输出:
Corresponding java.util.Date: Sun Jan 12 13:10:30 CET 1800 Formatted Russian date: 1/1/00
其他选项?
很遗憾,您是对的:java.time,现代 Java 日期和时间 API,不支持开箱即用的儒略历。我读到你不能使用任何第 3 方库。要考虑的选项包括:
对于可能使用第 3 方库的其他读者:Joda-Time 及其
GJChronology
。它Implements the Gregorian/Julian calendar system which is the calendar system used in most of the world. …
对于您自己:您可以开发自己的儒略-格里高利年表以与 java.time 一起使用。这需要付出努力,但我希望它能给出一个漂亮的结果。
链接
- Oracle tutorial: Date Time 解释如何使用 java.time.
- Documentation of Joda-Time’s
GJChronology