0
异步视界/archive/fix-angular-datepipe-year-error
· ARTICLE · 2025.01.01 · 3 MIN ·

2024 年最后一天被显示为 2025?Angular DatePipe 显示年份错误

2024 年最后一天被显示为 2025?Angular DatePipe 显示年份错误 · by fancyoung
AI · HERO seed:3120250101 2024 年最后一天被显示为 2025?Angular DatePipe 显示年份错误
FIG.00 — cover · ai-generated · placeholder

在 2024 年的最后一天(12 月 31 日),我遇到了一个奇怪的问题: 前端页面所有时间显示都增加了 1 年。

问题描述

代码如下:

<td [nzAlign]="'center'">{{ data.createdAt | date : 'YYYY-MM-dd HH:mm' }}</td>

data.createdAt 是一个日期时间字段,期望格式化为 YYYY-MM-dd HH:mm,但在运行时,所有 2024 年的日期被错误显示为 2025 年。

原因分析

1. 格式化字符串的问题

Angular 的 DatePipe 使用的是 标准日期格式化规则,其中:

  • `YYYY:常被误认为是表示年份的占位符,但实际上这是错误的。
  • 正确的占位符是:yyyy

YYYY 在某些日期库(如 Moment.js)中表示 “ISO 年”,与普通公历年份(yyyy)不同:

  • YYYY 表示与某些周数相关联的年份。
  • yyyy 表示普通的公历年份。

示例:

  • 2024-12-31 使用 YYYY 格式化可能会显示为 2025,因为它属于 ISO 年 2025 的第一周。

2. ISO 年的影响

ISO周日历系统是ISO 8601日期和时间标准的一部分,是一种闰周历系统。这个系统主要用在政府和商务的会计年度,用以维持时序。这个系统依据格里历的年度中特定的一个周日,决定该年是否要增加一个星期。

ISO 年的使用:

  • ISO 年(YYYY) 一般不会与普通的 “月” 和 “日”(MM 和 DD)结合使用,因为 ISO 年是基于周的
  • ISO 年主要和 ISO 周(Www) 一起使用,用于表示基于周的日期
  • 主要用于财务、时间表管理、统计分析等需要基于周的场景

ISO 年和普通公历年份的不同点是:

  • ISO 年以每年的第一周为基准。
  • 某些日期(如 12 月 31 日)可能会被归入下一年的 ISO 年。

为什么只在 12 月 31 日出现问题,且之前几年都正常?

ISO 周年(YYYY)基于周的划分规则,它规定:

  1. 一年的第一周是包含 1 月 4 日的那一周。
  2. 如果 12 月 31 日所在的那一周属于下一年的 ISO 周,则 YYYY 会显示为下一年的年份。

因此:

  • 2024 年的 12 月 31 日 是 2025 年的第一周,所以 YYYY 会显示为 2025。
  • 2023 年的 12 月 31 日 是 2023 年的最后一周,所以 YYYY 正常显示为 2023。
  • 2022 年的 12 月 31 日 是 2022 年的最后一周,所以 YYYY 正常显示为 2022。

注:实际 2024 年的 12 月 30 日 时已经出问题了,会被显示成 2025-12-30,当天可能数据不多被忽略了。


解决方法

1. 修改格式化字符串

YYYY 替换为 yyyy,确保 DatePipe 使用普通的公历年份格式化。

修改后的代码如下:

<td [nzAlign]="'center'">{{ data.createdAt | date : 'yyyy-MM-dd HH:mm' }}</td>

2. 当于 Moment.js 或 day.js 混用

当批量替换代码时,发现有些地方用了 day.js,有些地方用的是 angular 的 DatePipe。不能统一使用 yyyy 格式化。

此时通过完整的日期格式来替换即可。区别在于:

  • angular: yyyy-MM-dd 表示公历年份日期。
  • day.js: YYYY-MM-DD 表示公历年份日期。

总结

在 AngularJS 中:

  • 使用 YYYY 时,年份会基于 ISO 周年的规则,导致某些日期(例如 12 月 31 日)可能显示为下一年的年份。
  • 使用 yyyy 时,年份总是基于普通的公历年份,不会出现这种问题。
  • 解决方法:修改格式化字符串, 批量搜索 YYYY-MM-dd 替换为 yyyy-MM-dd

如果您不希望受到 ISO 周年的影响,务必使用 yyyy 来显示年份。