跨年Bug在前端如何治理

1. 问题背景

每年临近年元旦的几天里,各位总会为一个跨年级别的Bug而发愁---明明是2019年12月31日,但显示的时候却出现了2020/12/31这样错误的日期显示*,甚至像微信提供的订阅号助手工具也出现了这样的错误。

微信订阅号助手工具Bug

这种情况在编程语言中并不罕见,特别是在处理时间和数字数据时,往往在平时运行良好,但在特定时间或特定环境下就会出现问题。经排查,是因为日期格式化时使用了"YYYY-MM-dd"作为格式化模板。每年都有人因为这个问题而在元旦当天被紧急召回去修改Bug,甚至有的同学因为这个Bug导致即将到手的年终奖飞了。

气死爹了

从各位大神分享的博客、文章中可以看出,使用Java的程序员最容易踩到这个坑 [1] [2],而前端似乎极少碰到这个问题。真的是这样吗?本文便带你一探究竟。

2. 问题根本原因

根本原因在某篇博客中 [1]已经提到,使用大写的YYYY格式表示基于周的年份,即当前日期所在周所属的年份。根据这种格式,一周从周日开始,到周六结束,因此如果这一周跨年了,那么整个周将被归入下一年。

因此,回望上一部分提到的Bug,不难发现,2019年12月30日和31日均产生跨年,那么使用YYYY格式化时便会产生跨年Bug。

2019-12

虽然问题分享博客 [1] [2]均提到了将YYYY改为小写的yyyy,但都没有指出其所以然。事实上,Unicode官网 3上已经明确指出大写YYYY与小写yyyy的区别:小写yyyy指的是日历年,即该日属于哪一年就是哪一年,换句话说就是实际日期;大写YYYY即前文提到的基于周的年份。此外, Unicode官网也贴心地指出了YYYYyyyy在跨年上的不同。

Unicode官网截图

结论:

  1. 小写的yyyy表示日历年,即和日历同步的日期,最为准确
  2. 大写的YYYY表示基于周的年份,可能会导致跨年问题

3. 问题处理方式及结论

上一小节详述了问题发生的根本原因,本小节则着重回答两个问题:

  1. 虽然这个问题在Java编程中经常碰见,但是前端就没有这个问题了吗?
  2. 前端如何治理与解决这个问题?

3.1 前端不会发生这个问题吗?

TLDR (太长不看):

  1. 常用库都不会出现这个问题
  2. 各类时间库对yyyyYYYY的处理上有所不同

正文部分:

本文选取四个较常用的日期处理库,调研其format方法实现方式,以获取对于大写YYYY和小写yyyy的处理方式。经调研,结论如下:

moment dayjs date-fns luxon
大写 YYYY 处理为日历年 处理为日历年 如不开启options.useAdditionalWeekYearTokens则会报错,开启后处理为基于周的年份 不处理
小写 yyyy 处理为Era(纪年),对于日本时间适用(如“令和1年”)。如无纪年,则处理为日历年 不处理 默认方式,处理为日历年 处理为日历年

四个库对于获取年份的底层实现:

moment dayjs date-fns luxon
判断是否为UTC,是则getUTCFullYear,否则getFullYear 判断是否为UTC,是则getUTCFullYear,否则getFullYear 默认为getUTCFullYear 一律使用getUTCFullYear

getFullYear根据本地时间返回对应的年份,而getUTCFullYear根据GMT时间返回对应的年份。例如,北京时间2024年1月1日凌晨1点,getFullYear返回2024,而getUTCFullYear返回2023,因为此时GMT时间仍是2023年12月31日下午5点。

getFullYear与getUTCFullYear

那么,对于跨年时间,如2019-12-31getFullYeargetUTCFullYear是否会出现差错呢?用一段代码测试:

// 使用原生Date测试
const str = '2019-12-31';
const d = new Date(str);

console.log(d.getFullYear());
console.log(d.getUTCFullYear());

将测试代码运行结果如下图所示:

测试代码运行结果

综上,我们可以看出:

  1. moment / dayjs使用大写YYYY表示正常日历年;
  2. date-fns / luxon使用小写yyyy表示正常日历年。这也是date-fns的默认行为。
  3. 只有date-fns在开启了options.useAdditionalWeekYearTokens选项后才会将YYYY处理为基于周的年份,否则在处理YYYY时会抛出错误。
  4. dayjs不处理小写yyyy, luxon不处理大写YYYY
  5. 各个库的底层实现均为getFullYeargetUTCFullYear,一般不会发生上一小节的跨年的错误。
  6. (重要) 使用getUTCFullYear时要注意时差。北京时间比GMT快8小时。

3.2 前端治理方案

综合前述,本人提出治理方案如下:

  1. 如项目中没有使用任何日期库,获取年份一般应直接使用getFullYear,仅在必须获取GMT年份时才可以使用getUTCFullYear
  2. 原则上禁止使用getYear [9],因为getYear有2000年bug。
  3. 重要)如项目中使用了moment / dayjs,仅使用YYYY获取年份。
  4. 如项目中使用了date-fns,仅使用yyyy获取年份。
  5. 如使用date-fnsluxon,要十分小心时差问题。

Reference

  1. 即将跨年,YYYY-MM-dd 的BUG 自查了吗?
  2. 日期格式化跨年bug,是否与你不期而遇?
  3. UNICODE LOCALE DATA MARKUP LANGUAGE (LDML) PART 4: DATES
  4. 常用日期处理库Trend对比
  5. moment GitHub
  6. dayjs GitHub
  7. date-fns GitHub
  8. luxon GitHub
  9. getYear
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容