跳至主要內容

Java 笔记之 YYYY 格式化日期

JI,XIAOYONG...大约 5 分钟

最近看到一个帖子open in new window,表示有人以"YYYY-MM-dd"格式化日期时,在2019-12-30时出现2020-12-30的 BUG。

本文来简单分析一下为什么会出现这个情况。

根据JDK 文档关于日期的定义open in new windowy表示的是我们日常使用的年份,而Y表示的是Week year

先了解几个知识点:

Week year

Week year表示的是这个周所属的年份

A week year is in sync with a WEEK_OF_YEAR cycle. All weeks between the first and last weeks (inclusive) have the same week year value. Therefore, the first and last days of a week year may have different calendar year values.

来源:https://docs.oracle.com/javase/7/docs/api/java/util/GregorianCalendar.html#week_yearopen in new window

WEAK_OF_YEAR

指的是这一年所有的周,从第 01 周开始到该年最后一周。

要注意这个周不一定是自然周,所包含的日期也不一定全部都是当年的日期。

Values calculated for the WEEK_OF_YEARopen in new window field range from 1 to 53. The first week of a calendar year is the earliest seven day period starting on getFirstDayOfWeek()open in new window that contains at least getMinimalDaysInFirstWeek()open in new window days from that year.

第 01 周

根据这份JDK 文档open in new window,当 getFirstDayOfWeek() is MONDAY(2) and getMinimalDaysInFirstWeek() is 4 时,JAVA 判断周日期的标准与ISO_8601open in new window兼容:

第 01 周有几个相互等效且兼容的描述:

一年中第一个星期四的星期(正式的 ISO 定义),

1 月 4 日这一周,

起始年份中大部分(四天或以上)的第一周,以及

从 12 月 29 日至 1 月 4 日的星期一开始的一周。

来源:https://en.wikipedia.org/wiki/ISO_8601open in new window

按照 JAVA 文档中的定义,每年最开始的几天和最后的几天的Week year不一定是当年的年份值,而是受到每年的第 01 周/最后一周的影响。

JAVA 中判断周主要受到Calendar对象的getFirstDayOfWeek()getMinimalDaysInFirstWeek()这两个本地值的影响。

其中:

  • getFirstDayOfWeek() 指定一周的第一天,比如,美国一周从SUNDAY 开始,法国则是MONDAY
  • getMinimalDaysInFirstWeek() 一年第一周所需最小的天数。比如 1 表示只要包含第一天就算该年的第一周,而 7 表示只有完整的一周都在该年才算该年的第一周。

注意:真正影响我们格式化日期结果的是SimpleDateFormat中的calendar对象对应的值。

而通过打印这个simpleDateFormat.calendar,我们看到:

//JDK1.7
minimalDaysInFirstWeek:1
firstDayOfWeek:1 //SUNDAY

所以可以得出结论,JAVA默认只要次年的 1 月 1 日在在这个跨年周,那么本周所有日期的Week year都是次年的JDK1.7)。

问题分析

有了以上知识,我们再看看2019-12-30YYYY格式化为什么会出现问题:

先看一下这些日期对应的星期:

周日周一周二周三周四周五周六
2930311234

首先根据 JDK 默认的第01周的定义,2020-01-01所在的周为2020的第一周,所以2019-12-29到2020-01-04都属于是2020年的第01周

再根据YYYY表示的是Week year的结论,可以知道,当使用YYYY格式化时,2019-12-29到2020-01-04都会得到2020

val calendar = Calendar.getInstance()
val simpleDateFormat = SimpleDateFormat("YYYY yyyy MM dd")
calendar.set(2019, 12-1, 29)
println(simpleDateFormat.format(calendar.time))

//output
DATE:2019 12 29
minimalDaysInFirstWeek:1
firstDayOfWeek:1
YYYY yyyy MM dd
2020 2019 12 29

而如果我们把第一周最小天数minimalDaysInFirstWeek改为5天,那么很明显这一周属于2020年的天数(从周日到周一,只有 1 号到 4 号 4 天)不够 5 天,所以这一周被划归为2019年的第53周2019-12-29到2020-01-04week year都是属于2019

val calendar = Calendar.getInstance()
val simpleDateFormat = SimpleDateFormat("YYYY yyyy MM dd")
calendar.set(2019, 12-1, 29)
simpleDateFormat.calendar.minimalDaysInFirstWeek = 5
println(simpleDateFormat.format(calendar.time))

//output
DATE:2019 12 29
minimalDaysInFirstWeek:5
firstDayOfWeek:1
YYYY yyyy MM dd
2019 2019 12 29

再比如下面这个示例open in new window中的2010-12-26

按照JDK1.7默认算法,一周从周日(2010-12-26)开始,并且当年的 1 月 1 日(2011-01-01)所在周为该年第一周,所以2010-12-26到2011-01-01都被划到了2011年的第一周。

但如果按照ISO_8601open in new window的标准,一周从周一开始,并且起始年份包含的天数至少要有4天:

则很明显2010-12-26属于2010年的51周,而2010-12-27到2011-01-02都属于2010年的第52周(属于 2020 年的只有 2 天,不满足第一周的条件)。

周一周二周三周四周五周六周日
20212223242526
272829303112

总结

结合以上结论,我们可以看到,在 JAVA 中(JDK1.7):

  • “YYYY” 表示Week year

  • 每年最开始的几天和最后的几天的Week year不一定是当年的值,而是受到当年的第一周/最后一周的影响。

  • JAVA 周的判断与simpleDateFormat.calendar.minimalDaysInFirstWeeksimpleDateFormat.calendar.firstDayOfWeek有关。

    而这两个值都属于本地化值,在国内可以简单理解为一年 1 月 1 日所在的周就是当年的第一周。

  • 我们可以通过修改minimalDaysInFirstWeekfirstDayOfWeek来更改YYYY格式化的值。

附录

JDK 中日期格式化的参数及含义(来自 https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html#month):open in new window

LetterDate or Time ComponentPresentationExamples
GEra designatorTextopen in new windowAD
yYearYearopen in new window1996; 96
YWeek yearYearopen in new window2009; 09
MMonth in yearMonthopen in new windowJuly; Jul; 07
wWeek in yearNumberopen in new window27
WWeek in monthNumberopen in new window2
DDay in yearNumberopen in new window189
dDay in monthNumberopen in new window10
FDay of week in monthNumberopen in new window2
EDay name in weekTextopen in new windowTuesday; Tue
uDay number of week (1 = Monday, ..., 7 = Sunday)Numberopen in new window1
aAm/pm markerTextopen in new windowPM
HHour in day (0-23)Numberopen in new window0
kHour in day (1-24)Numberopen in new window24
KHour in am/pm (0-11)Numberopen in new window0
hHour in am/pm (1-12)Numberopen in new window12
mMinute in hourNumberopen in new window30
sSecond in minuteNumberopen in new window55
SMillisecondNumberopen in new window978
zTime zoneGeneral time zoneopen in new windowPacific Standard Time; PST; GMT-08:00
ZTime zoneRFC 822 time zoneopen in new window-0800
XTime zoneISO 8601 time zoneopen in new window-08; -0800; -08:00

参考资料

感谢这篇文章,让我推翻了上一次的结论,发现了真正的原因:JAVA 中的 SimpleDateFormat yyyy 和 YYYY 的区别open in new window

GregorianCalenda jdk1.7open in new window

在线显示本周是一年第几周的网站:What's the Current Week Number?open in new window

文章标题:《Java 笔记之 YYYY 格式化日期》
本文作者: JI,XIAOYONG
发布时间: 2020/01/07 11:38:54 UTC+8
更新时间: 2023/12/30 16:17:02 UTC+8
written by human, not by AI
本文地址: https://jixiaoyong.github.io/blog/posts/87a89a43.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。转载请注明出处!
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.8