Java 日历异常行为
Java Calendar Strange behaviour
我在 java 中使用日历对象将输入日期(year/month/date 等从网络获取)转换为纪元时间。我正在重用相同的日历对象。有时我得到的那一年
network 为 0,并且没有对此进行验证。一旦发生这种情况,每当我将日期转换为纪元时,我得到的纪元时间总是负数。这是一个有效的行为
请找到我针对此问题进行单元测试时得到的示例代码和结果。
Calendar cal= Calendar.getInstance();
cal.set(Calendar.YEAR, 0);
System.out.println("time 1 : "+cal.getTimeInMillis());
System.out.println("Date 1 : "+new Date(cal.getTimeInMillis()));
cal.set(Calendar.YEAR, 2020);
System.out.println("time 2 : "+cal.getTimeInMillis());
System.out.println("Date 2 : "+new Date(cal.getTimeInMillis()));
cal.set(Calendar.YEAR, 2010);
cal.set(Calendar.MONTH, 10);
System.out.println("time 3 : "+cal.getTimeInMillis());
System.out.println("Date 3 : "+new Date(cal.getTimeInMillis()));
此代码的输出是
时间 1:-62151385126938
日期 1:2004 年 7 月星期日 11:51:13 IST 1
时间 2:-125866201126938
日期 2:7 月 4 日星期三 11:51:13 IST 2020
时间 3:-125540041126938
日期 3:2010 年 11 月星期五 11:51:13 IST[=12=]
这是 java 日历的预期行为吗?
我的 JDK 版本是
打开JDK 运行时环境 Corretto-8.252.09.1 (build 1.8.0_252-b09)
编辑:
还有其他 option/class 可以解决此问题,但我的目的是找到此行为的根本原因
I am using calendar object
停止这样做。永远不要使用可怕的 Calendar
和 Date
classes。仅使用 JSR 310 中定义的现代 java.time classes。
Sometimes the year i am getting from network is 0 and there was no validation for this once. Once this happens the whenever i convert the date to epoch, the epoch time im getting is always negative. Is this a valid behaviour
是的,自 UTC 中 1970 年第一时刻的纪元参考以来的毫秒数负数对于零年的日期是正确的。 0000 年发生在 2000 多年前,因此从 1970-01-01T00:00Z 开始倒计时是一个非常大的毫秒数。
我了解到您看到 Calendar
在当代年份重置后返回奇怪的负数。如果您想了解更多信息,请参阅优秀的 我认为:既然我们有 java.time,就没有必要调查 Calendar
。埋葬死者,继续前行
如果您必须与尚未为 java.time 更新的旧代码互操作,您可以在遗留 classes 和现代的。查看添加到旧 classes 的新方法。尽量减少对旧遗留 classes.
的使用
if( myCalendar instanceof GregorianCalendar )
{
ZonedDateTime zdt = ( ( GregorianCalendar ) myCalendar ).toZonedDateTime() ;
}
……和……
Calendar myCalendar = GregorianCalendar.from( zdt ) ;
你说:
I am reusing same calendar object .
停止这样做。重用 class 的对象会导致各种问题。
java.time 中的一个重要设计考虑因素是仅使用不可变对象,以避免这些重用问题。
am using calendar object in java to convert an input date(year/month/date
您没有向我们展示此代码,因此我无法进一步帮助您。
发布前搜索 Stack Overflow。所有这一切已经被多次提及。
Is this an expected behaviour for java Calendar?
我的答案可能是有争议的,Calendar
的行为常常与预期相反。它的设计和记录就是为了这样做。我理解你的困惑。
没有 0 年。所以第一个期望可能是 Calendar
应该在您尝试将纪元年设置为 0 时抛出异常。使用标准设置它不会。相反,它推断:第 0 年被认为是第 1 年之前的一年,所以那是公元前 1 年。在您的输出中,您可以看到年份是 1(不是 0);但是你看不到的是时代,是公元前,不是公元(CE)。因此,让我们在代码中添加几行打印纪元的代码:
Calendar cal= Calendar.getInstance();
System.out.format("Initial: Era %s year %d%n",
cal.getDisplayName(Calendar.ERA, Calendar.SHORT_STANDALONE, Locale.ENGLISH),
cal.get(Calendar.YEAR));
cal.set(Calendar.YEAR, 0);
System.out.format("Year 0: Era %s year %d%n",
cal.getDisplayName(Calendar.ERA, Calendar.SHORT_STANDALONE, Locale.ENGLISH),
cal.get(Calendar.YEAR));
System.out.println("time 1 : "+cal.getTimeInMillis());
System.out.println("Date 1 : "+new Date(cal.getTimeInMillis()));
当我运行刚才的代码时,输出是:
Initial: Era AD year 2020
Year 0: Era BC year 1
time 1 : -62151371618330
Date 1 : Sun Jul 04 11:06:21 CET 1
接下来,Calendar.YEAR
表示纪元年份,至少对于 GregorianCalendar
(这是 Calendar
的具体子 class,您从中获得了一个实例Calendar.getInstance()
)。所以当你接下来设置 YEAR
,也就是纪元年,到 2020 年时,Calendar
停留在基督之前(在普通纪元之前),所以你得到 2020 BC,距今 4000 多年前。并且您的毫秒数“增长”到已经为负值的大约两倍。
cal.set(Calendar.YEAR, 2020);
System.out.format("Year 2020: Era %s year %d%n",
cal.getDisplayName(Calendar.ERA, Calendar.SHORT_STANDALONE, Locale.ENGLISH),
cal.get(Calendar.YEAR));
System.out.println("time 2 : "+cal.getTimeInMillis());
System.out.println("Date 2 : "+new Date(cal.getTimeInMillis()));
Year 2020: Era BC year 2020
time 2 : -125866187618330
Date 2 : Wed Jul 04 11:06:21 CET 2020
恕我直言,这是避免 Calendar
class.
的好案例
java.time
是的,如果仅针对其他读者,还有其他选择。
首先,你不应该试图去处理毫无意义的一年。我不知道你的情况和要求,所以不能告诉你当你得到0或负数的年份时该怎么办,但你需要决定,你需要检查并采取适当的行动。正如您所知,您到目前为止所做的是错误的。
其次,很明显你应该使用 java.time,现代 Java 日期和时间 API。
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.systemDefault());
zdt = zdt.withYear(0);
System.out.println(zdt);
System.out.println(zdt.toEpochSecond());
zdt = zdt.withYear(2020);
System.out.println(zdt);
System.out.println(zdt.toEpochSecond());
zdt = zdt.withYear(2010);
System.out.println(zdt);
0000-07-04T11:23:08.573357+00:50:20[Europe/Copenhagen]
-62151197232
2020-07-04T11:23:08.573357+02:00[Europe/Copenhagen]
1593854588
2010-07-04T11:23:08.573357+02:00[Europe/Copenhagen]
您注意到在此输出中,自纪元以来的秒数在负数之后再次变为正数。在java.time中,一年表示临产年,即有符号的年份。 Proleptic 第 0 年是公元前 1 年,-1 表示公元前 2 年,依此类推。所以第 0 年只是任何其他年份,不会产生任何奇怪的行为。
另外 java.time 反对将时代设置为 0,这可能会帮助您更早发现问题:
zdt = zdt.with(ChronoField.YEAR_OF_ERA, 0);
Exception in thread "main" java.time.DateTimeException: Invalid value for YearOfEra (valid values 1 - 999999999/1000000000): 0
at java.base/java.time.temporal.ValueRange.checkValidValue(ValueRange.java:311)
at java.base/java.time.temporal.ChronoField.checkValidValue(ChronoField.java:717)
at java.base/java.time.LocalDate.with(LocalDate.java:1048)
at java.base/java.time.LocalDateTime.with(LocalDateTime.java:970)
at java.base/java.time.ZonedDateTime.with(ZonedDateTime.java:1312)
at ovv.so.date.special.SetCalendarYear0.main(SetCalendarYear0.java:25)
我在 java 中使用日历对象将输入日期(year/month/date 等从网络获取)转换为纪元时间。我正在重用相同的日历对象。有时我得到的那一年 network 为 0,并且没有对此进行验证。一旦发生这种情况,每当我将日期转换为纪元时,我得到的纪元时间总是负数。这是一个有效的行为
请找到我针对此问题进行单元测试时得到的示例代码和结果。
Calendar cal= Calendar.getInstance();
cal.set(Calendar.YEAR, 0);
System.out.println("time 1 : "+cal.getTimeInMillis());
System.out.println("Date 1 : "+new Date(cal.getTimeInMillis()));
cal.set(Calendar.YEAR, 2020);
System.out.println("time 2 : "+cal.getTimeInMillis());
System.out.println("Date 2 : "+new Date(cal.getTimeInMillis()));
cal.set(Calendar.YEAR, 2010);
cal.set(Calendar.MONTH, 10);
System.out.println("time 3 : "+cal.getTimeInMillis());
System.out.println("Date 3 : "+new Date(cal.getTimeInMillis()));
此代码的输出是
时间 1:-62151385126938
日期 1:2004 年 7 月星期日 11:51:13 IST 1
时间 2:-125866201126938
日期 2:7 月 4 日星期三 11:51:13 IST 2020
时间 3:-125540041126938
日期 3:2010 年 11 月星期五 11:51:13 IST[=12=]
这是 java 日历的预期行为吗?
我的 JDK 版本是 打开JDK 运行时环境 Corretto-8.252.09.1 (build 1.8.0_252-b09)
编辑: 还有其他 option/class 可以解决此问题,但我的目的是找到此行为的根本原因
I am using calendar object
停止这样做。永远不要使用可怕的 Calendar
和 Date
classes。仅使用 JSR 310 中定义的现代 java.time classes。
Sometimes the year i am getting from network is 0 and there was no validation for this once. Once this happens the whenever i convert the date to epoch, the epoch time im getting is always negative. Is this a valid behaviour
是的,自 UTC 中 1970 年第一时刻的纪元参考以来的毫秒数负数对于零年的日期是正确的。 0000 年发生在 2000 多年前,因此从 1970-01-01T00:00Z 开始倒计时是一个非常大的毫秒数。
我了解到您看到 Calendar
在当代年份重置后返回奇怪的负数。如果您想了解更多信息,请参阅优秀的 Calendar
。埋葬死者,继续前行
如果您必须与尚未为 java.time 更新的旧代码互操作,您可以在遗留 classes 和现代的。查看添加到旧 classes 的新方法。尽量减少对旧遗留 classes.
的使用if( myCalendar instanceof GregorianCalendar )
{
ZonedDateTime zdt = ( ( GregorianCalendar ) myCalendar ).toZonedDateTime() ;
}
……和……
Calendar myCalendar = GregorianCalendar.from( zdt ) ;
你说:
I am reusing same calendar object .
停止这样做。重用 class 的对象会导致各种问题。
java.time 中的一个重要设计考虑因素是仅使用不可变对象,以避免这些重用问题。
am using calendar object in java to convert an input date(year/month/date
您没有向我们展示此代码,因此我无法进一步帮助您。
发布前搜索 Stack Overflow。所有这一切已经被多次提及。
Is this an expected behaviour for java Calendar?
我的答案可能是有争议的,Calendar
的行为常常与预期相反。它的设计和记录就是为了这样做。我理解你的困惑。
没有 0 年。所以第一个期望可能是 Calendar
应该在您尝试将纪元年设置为 0 时抛出异常。使用标准设置它不会。相反,它推断:第 0 年被认为是第 1 年之前的一年,所以那是公元前 1 年。在您的输出中,您可以看到年份是 1(不是 0);但是你看不到的是时代,是公元前,不是公元(CE)。因此,让我们在代码中添加几行打印纪元的代码:
Calendar cal= Calendar.getInstance();
System.out.format("Initial: Era %s year %d%n",
cal.getDisplayName(Calendar.ERA, Calendar.SHORT_STANDALONE, Locale.ENGLISH),
cal.get(Calendar.YEAR));
cal.set(Calendar.YEAR, 0);
System.out.format("Year 0: Era %s year %d%n",
cal.getDisplayName(Calendar.ERA, Calendar.SHORT_STANDALONE, Locale.ENGLISH),
cal.get(Calendar.YEAR));
System.out.println("time 1 : "+cal.getTimeInMillis());
System.out.println("Date 1 : "+new Date(cal.getTimeInMillis()));
当我运行刚才的代码时,输出是:
Initial: Era AD year 2020 Year 0: Era BC year 1 time 1 : -62151371618330 Date 1 : Sun Jul 04 11:06:21 CET 1
接下来,Calendar.YEAR
表示纪元年份,至少对于 GregorianCalendar
(这是 Calendar
的具体子 class,您从中获得了一个实例Calendar.getInstance()
)。所以当你接下来设置 YEAR
,也就是纪元年,到 2020 年时,Calendar
停留在基督之前(在普通纪元之前),所以你得到 2020 BC,距今 4000 多年前。并且您的毫秒数“增长”到已经为负值的大约两倍。
cal.set(Calendar.YEAR, 2020);
System.out.format("Year 2020: Era %s year %d%n",
cal.getDisplayName(Calendar.ERA, Calendar.SHORT_STANDALONE, Locale.ENGLISH),
cal.get(Calendar.YEAR));
System.out.println("time 2 : "+cal.getTimeInMillis());
System.out.println("Date 2 : "+new Date(cal.getTimeInMillis()));
Year 2020: Era BC year 2020 time 2 : -125866187618330 Date 2 : Wed Jul 04 11:06:21 CET 2020
恕我直言,这是避免 Calendar
class.
java.time
是的,如果仅针对其他读者,还有其他选择。
首先,你不应该试图去处理毫无意义的一年。我不知道你的情况和要求,所以不能告诉你当你得到0或负数的年份时该怎么办,但你需要决定,你需要检查并采取适当的行动。正如您所知,您到目前为止所做的是错误的。
其次,很明显你应该使用 java.time,现代 Java 日期和时间 API。
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.systemDefault());
zdt = zdt.withYear(0);
System.out.println(zdt);
System.out.println(zdt.toEpochSecond());
zdt = zdt.withYear(2020);
System.out.println(zdt);
System.out.println(zdt.toEpochSecond());
zdt = zdt.withYear(2010);
System.out.println(zdt);
0000-07-04T11:23:08.573357+00:50:20[Europe/Copenhagen] -62151197232 2020-07-04T11:23:08.573357+02:00[Europe/Copenhagen] 1593854588 2010-07-04T11:23:08.573357+02:00[Europe/Copenhagen]
您注意到在此输出中,自纪元以来的秒数在负数之后再次变为正数。在java.time中,一年表示临产年,即有符号的年份。 Proleptic 第 0 年是公元前 1 年,-1 表示公元前 2 年,依此类推。所以第 0 年只是任何其他年份,不会产生任何奇怪的行为。
另外 java.time 反对将时代设置为 0,这可能会帮助您更早发现问题:
zdt = zdt.with(ChronoField.YEAR_OF_ERA, 0);
Exception in thread "main" java.time.DateTimeException: Invalid value for YearOfEra (valid values 1 - 999999999/1000000000): 0 at java.base/java.time.temporal.ValueRange.checkValidValue(ValueRange.java:311) at java.base/java.time.temporal.ChronoField.checkValidValue(ChronoField.java:717) at java.base/java.time.LocalDate.with(LocalDate.java:1048) at java.base/java.time.LocalDateTime.with(LocalDateTime.java:970) at java.base/java.time.ZonedDateTime.with(ZonedDateTime.java:1312) at ovv.so.date.special.SetCalendarYear0.main(SetCalendarYear0.java:25)