

年底将至,元旦假期就在眼前。准备好出行的我在 2019-12-30 下单机票时,App 居然给我显示成了 2020-12-30!一脸懵逼,差点以为自己买错了整整一年的票。
究其原因,是开发者在日期格式化时把年份格式写成了 YYYY-MM-dd,结果导致跨周跨年的那一周被当成了下一年!这个小细节不注意,足以让用户 怒刷差评,也可能让程序员被“祭天”……
本文将带你 深度剖析 Java 中 yyyy vs YYYY 的区别,揭秘 ISO-8601 周年制背后的玄机,并给出多种规避方案,助你在跨年时节不再翻车。
yyyy-MM-dd(calendar year)YYYY-MM-dd(week-based year)package com.rumenz;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class DateTest {
public static void main(String[] args) {
// 设置日期:2019-12-31
Calendar cal = Calendar.getInstance();
cal.set(2019, Calendar.DECEMBER, 31);
Date date = cal.getTime();
// 正确的年:yyyy
DateFormat fmt1 = new SimpleDateFormat("yyyy-MM-dd");
System.out.println("yyyy 格式: " + fmt1.format(date));
// 错误的年:YYYY
DateFormat fmt2 = new SimpleDateFormat("YYYY-MM-dd");
System.out.println("YYYY 格式: " + fmt2.format(date));
}
}yyyy 格式: 2019-12-31
YYYY 格式: 2020-12-31yyyy:表示「年-时代」(year-of-era),2019YYYY:表示「基于周的年」(week-based-year),2020为何 2019-12-31 会被当成 2020 年?背后的原因就是 ISO-8601 周年制!
yyyy vs YYYY:背后原理符号 | 含义 | 启始点 |
|---|---|---|
y | year-of-era(公历年) | 1 月 1 日 |
Y | week-based-year(ISO 周年) | 周一为一周开始,跨周跨年归属下一年 |
根据 ISO-8601:
YYYY 会输出 2020。日期 | 星期 | 周编号 | 周年(YYYY) | 公历年(yyyy) |
|---|---|---|---|---|
2019-12-29 | 日 | 52 | 2019 | 2019 |
2019-12-30 | 一 | 01 | 2020 | 2019 |
2019-12-31 | 二 | 01 | 2020 | 2019 |
2020-01-01 | 三 | 01 | 2020 | 2020 |
String[] patterns = {"yyyy-'W'ww-u", "YYYY-'W'WW-u"};
for (String p : patterns) {
DateFormat df = new SimpleDateFormat(p);
System.out.println(p + ": " + df.format(date));
}yyyy-'W'ww-u: 2019-W52-2
YYYY-'W'WW-u: 2020-W01-3ww:周编号,两位u:周中第几天(1=Monday, …,7=Sunday)由此可见,用错大写字母,不仅年会错,**“周数”**也可能错!
使用 java.time API,显式区分公历年和周年制:
import java.time.*;
import java.time.format.DateTimeFormatter;
public class JavaTimeDemo {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2019, 12, 31);
// 公历年
String s1 = date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
// ISO 周年
String s2 = date.format(DateTimeFormatter.ofPattern("YYYY-'W'WW-u"));
System.out.println("公历年: " + s1);
System.out.println("ISO 周年: " + s2);
}
}yyyy,避免“跨年周”陷阱。date-fns、moment.js):
YYYY:ISO 年yyyy:公历年strftime):
%Y:公历年%G:ISO 年.NET):
yyyy:公历年YYYY 并不支持 ISO 年,需要 ddd 等另行计算小结:跨语言同样存在此坑,查看文档时务必核对大小写含义。
yyyy:绝大多数日期显示、订单、日志场景应使用公历年。YYYY、ww、u 等周年制模式。YYYY。这次“YYYY”小坑足以让人“跨年”买错机票,希望你也能在元旦前弥补这个细节,避免被用户或老板“祭天”!祝大家新年代码无 Bug,生活多惊喜!