Java日期处理中最容易犯的错误

好奇的Naked Security读者向我们发出警告,他们认为Java日期处理中可能是类似Y2K的错误

引起警报的原因是一个Twitter帖子,该帖子以头条推文开头说:“ PSA:这是检查您的格式者的季节,人们。

PSA:这是检查

格式的季节,直到JavaDateTimeFormatter模式“ YYYY”为您提供基于周的年份(默认情况下为ISO-8601标准),该星期的星期四。

12/29/2019格式至2019
12/30/2019
格式至2020

-Giuliana Taylor@NmVAson20191220

正如@NmVAson指出的那样,当您要求JavaDateTimeFormatter库告诉您当前YYYY的(通常的程序员缩写)意思是以四位数字表示的年份时,问题就来了。

例如,当程序员缩写世界上常用的日期格式时,他们经常使用格式字符串来表示所需的布局,如下所示:

布局格式字符串示例

------------------------ ------------- ----------

美式(20191229日)MM / DD / YYYY 12/29/2019

欧式风格(20191229日)DD / MM / YYYY 2019/12/12

RFC 33392019-12-29YYYY-MM-DD 2019-12-29

实际上,许多编程语言都提供了代码库,可帮助您使用上述格式字符串来打印日期,以便您可以自动调整软件的输出以适合每个用户的个人喜好。

这里的问题是,有许多不同的日期处理功能,例如GetDateFormatEx()Windowsstrftime()UnixLinux系统上,一直到Java的全能,全舞DateTimeFormatter模块。

上面提到的Java库以及其他功能,使您可以方便地使用上面显示的三个字符串设置日期的格式,从而得到如下所示的合理结果:

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
 
public class CarefulWithThatDateEugene {
   private static void tryit(int Y, int M, int D, String pat) {
      DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pat);
      LocalDate         dat = LocalDate.of(Y,M,D);
      String            str = fmt.format(dat);
      System.out.printf("Y=%04d M=%02d D=%02d " +
                        "formatted with " +
                        "\"%s\" -> %s\n",Y,M,D,pat,str);
   }
   public static void main(String[] args){
      tryit(2020,01,20,"MM/DD/YYYY");
      tryit(2020,01,21,"DD/MM/YYYY");
      tryit(2020,01,22,"YYYY-MM-DD");
   }
}
 
//---------------
 
Y=2020 M=01 D=20 formatted with "MM/DD/YYYY" -> 01/20/2020
Y=2020 M=01 D=21 formatted with "DD/MM/YYYY" -> 21/01/2020
Y=2020 M=01 D=22 formatted with "YYYY-MM-DD" -> 2020-01-22

到现在为止还挺好!

但是,如果您在年中尝试此操作,则会得到:

Y=2020 M=05 D=17 formatted with "MM/DD/YYYY" -> 05/138/2020

Y=2020 M=05 D=18 formatted with "DD/MM/YYYY" -> 139/05/2020

Y=2020 M=05 D=19 formatted with "YYYY-MM-DD" -> 2020-05-140

一个容易发现的错误

什么?!?

请注意,尽管一年中最长的月份只有31天,但奇怪的日期数字远大于31

这样一来,您就可以回到文档,或者至少回到您最喜欢的搜索引擎,在其中粗略浏览一下就会发现该缩写DD实际上是一年中的某天而不是月份中的某天

因此DDdd仅在1月份产生相同的答案,此后一年的日期变为32,而2月的第一天的月份则重置为01。(要清楚,在除夕-一年的最后一天,1231-一年中一天365or366,而当月一天31。)

换句话说,即使对1月以外的日期进行粗略的测试也会显示此格式字符串错误,因此很少有人这样做。

您需要的是格式字符串dd,如下所示:

Y=2020 M=05 D=17 formatted with "MM/dd/YYYY" -> 05/17/2020

Y=2020 M=05 D=18 formatted with "dd/MM/YYYY" -> 18/05/2020

Y=2020 M=05 D=19 formatted with "YYYY-MM-dd" -> 2020-05-19

 

难以发现的错误

除非您仍然错,YYYY否则不代表基督教数字的四位数年份

这在Java库(以及其他全脂日期数据库)中也表示为小写文本string yyyy

相比之下,YYYY表示所谓的基于周的年份,会计所依赖的东西是避免在不同的两年之间分配周数,从而避免公司的薪水分配。

基于本周年数和基督教时代的年数几乎都是一样的,所以很容易看从Java的几个输出 DateTimeFormatter模块,并认为他们始终不变的...

但是你会犯错误的危险。

对于农民,牧师,天文学家和商人而言,不方便的是,太阳年不会精确地分为几天,因此也不能整齐地分为几周或几个月。(阴历月份与太阳年也不相称,这使事情变得更加复杂。)

每个簿记员都知道,一年中并不完全有52周,因为最后总是剩下一两天。

这是因为一年(或a年)中有365(或366)天的事实;一周有7天;并且365/7 = 52余数1(或366/7 = 52余数2)。

因此,为了会计上的方便,通常将某些年视为具有52个整周,而另一些则具有53个星期,从长远来看,这会使每周的收入计划和每周的工资单保持平衡。

换句话说,在某些年份中,工资周01”实际上是在元旦之前开始的。在其他年份,直到新年第一周的几天才开始。

有一个标准

ISO-8601日历系统中定义了一个标准,在Java文档中将其描述为当今世界大多数地方使用的现代民用日历系统

ISO-8601作了一些假设,包括:

  • 每周的第一天是星期一。
  • 如果在年末拆分一周,则将其分配给该周中有一半以上的日子发生的年份。

第二个假设似乎是合理的,因为这意味着在正确的年份中,您的工资日总是比错误的年份多。

例如,对于2015年,在第52周之后还剩下四天,因此2016年的前三天被吸回2015工资年度:

Sun 2015-12-27  -> Payroll week 52 of 2015

 

Mon 2015-12-28  -> Payroll week 53 of 2015

Tue 2015-12-29  -> Payroll week 53 of 2015

Wed 2015-12-30  -> Payroll week 53 of 2015

Thu 2015-12-31  -> Payroll week 53 of 2015

-------------NEW YEAR---------------------

Fri 2016-01-01  -> Payroll week 53 of 2015

Sat 2016-01-02  -> Payroll week 53 of 2015

Sun 2016-01-03  -> Payroll week 53 of 2015

 

Mon 2016-01-04  -> Payroll week 01 of 2016

但是到了2025年,情况恰恰相反,到2025年底只剩下三天的时间,就被推到2026年的薪资年:

Sun 2025-12-28  -> Payroll week 52 of 2025

 

Mon 2025-12-29  -> Payroll week 01 of 2026

Tue 2025-12-30  -> Payroll week 01 of 2026

Wed 2025-12-31  -> Payroll week 01 of 2026

-------------NEW YEAR---------------------

Thu 2026-01-01  -> Payroll week 01 of 2026

Fri 2026-01-02  -> Payroll week 01 of 2026

Sat 2026-01-03  -> Payroll week 01 of 2026

Sun 2026-01-04  -> Payroll week 01 of 2026

 

Mon 2026-01-05  -> Payroll week 02 of 2026

即将发生大日期错误!

你能看到这是怎么回事吗?

如果你已经有了一个日期格式字符串类似MM/dd/YYYYYYYY-MM-dd在任何软件的任何点在您使用的ISO-8601的日期格式库...

您不可避免地会遇到错误,这些错误会在一年的结尾或下一年的开始打印出错误的年份的日期,除非在元旦是星期一的年份。

(当1231日为星期日,而11日为星期一时,ISO-8601“周拆分过程将正常进行,到年底还剩0天。)

如果使用YYYY应该写的位置yyyy,则您的日期将有规律但很少出错,因此即使您可能不容易注意到它们,您的代码也会出错。

以下是您在2018年看到的过时日期:

Y=2018 M=12 D=30 formatted with "YYYY-MM-dd" -> 2018-12-30  +correct+

Y=2018 M=12 D=31 formatted with "YYYY-MM-dd" -> 2019-12-31  *WRONG* (one year ahead)

-------------------------------NEW YEAR------------------------------

Y=2019 M=01 D=01 formatted with "YYYY-MM-dd" -> 2019-01-01  +correct+

对于2019年:

Y=2019 M=12 D=28 formatted with "YYYY/MM/dd" -> 2019/12/28  +correct+

Y=2019 M=12 D=29 formatted with "YYYY-MM-dd" -> 2019-12-29  *WRONG* (one year ahead)

Y=2019 M=12 D=30 formatted with "YYYY-MM-dd" -> 2020-12-30  *WRONG* (one year ahead)

Y=2019 M=12 D=31 formatted with "YYYY-MM-dd" -> 2020-12-31  *WRONG* (one year ahead)

-------------------------------NEW YEAR------------------------------

Y=2020 M=01 D=01 formatted with "YYYY-MM-dd" -> 2020-01-01  +correct+

2020年:

Y=2020 M=12 D=31 formatted with "YYYY-MM-dd" -> 2020-12-31  +correct+

-------------------------------NEW YEAR------------------------------

Y=2021 M=01 D=01 formatted with "YYYY-MM-dd" -> 2020-01-01  *WRONG* (one year behind)

Y=2021 M=01 D=02 formatted with "YYYY-MM-dd" -> 2020-01-02  *WRONG* (one year behind)

Y=2021 M=01 D=03 formatted with "YYYY-MM-dd" -> 2020-01-03  *WRONG* (one year behind)

Y=2021 M=01 D=04 formatted with "YYYY-MM-dd" -> 2021-01-04  +correct+

如果可以通过@NmVAson启动Twitter线程,那么很多程序员似乎仍然会犯这种错误,这意味着他们没有很好地测试他们的代码。

正如我们上面提到的,DD用错误而不是错误的书写dd似乎是一个不寻常的错误,大概是因为该错误在一年中的出现率约为85%,并且由于三位数的天数而在一年中的出现率高达70%以上。

可以肯定的是,YYYY错误地写错误而不是yyyy在一年中不到1%的日期中产生错误,而且不是每7年中的任何一年都发生错误,但是即使错误率低于1%,也确实没有任何借口未能发现您犯了这个错误。

您可能会找借口,说没有碰到一个2分之32错误是倒霉。您甚至可能会因运气不好而逃脱,以为错误率高达百万分之一

但是只有1%(特别是当那些以百分比为中心的年份恰好在年末时)时,您真的不应该让这种错误逃脱您的注意。

该怎么办?

如果您是负责处理日期的代码的程序员或项目经理,并且几乎可以肯定需要执行任何类型的日志记录的任何工作,那么请确保您:

  • 不要做假设。仅仅因为大写YYYY表示某些地方的日历年并不意味着它总是如此。
  • 阅读完整的手册,或简称RTFM。遗憾的是,针对ISO-8601TFM非常复杂,但这应该是您的问题,而不是用户的问题-动力来自责任。
  • 正确检查您的代码。请记住,审稿人也需要进行RTFM
  • 彻底测试您的代码。YYYY遇到ISO-8601错误的人实际上并没有一个好的测试集,因为该错误大约在每7年的6年末出现一次。

我们认为日历是理所当然的,但日历的设计和使用是艺术与科学的融合,已经持续了数千年,仍然需要极大的关注和关注。

特别是在软件中!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值