Moment.js 指南

指南区域旨在帮助开发人员学习如何更好地与日期和时间问题域以及 Moment.js 库进行交互。 我们在这里处理我们最常见的支持请求,因此这是一个检查您可能遇到的任何问题的解决方案的好地方。

指南部分是新建的,仍在建设中。 如果您有想要在此处看到的指南请求,或者想要添加指南 请在 momentjs.com repository 中创建问题或提出拉取请求。

如果您刚刚开始,请查看此 斯克里巴moment指南

可变性 1.0.0+

Moment.js 中的 moment 对象是可变的。 这意味着诸如加、减或设置之类的操作会更改原始矩对象。 当第一次使用 Moment.js 时,许多开发人员对这样的场景感到困惑:

var a = moment('2016-01-01'); 
var b = a.add(1, 'week'); 
a.format();
"2016-01-08T00:00:00-06:00"

如您所见,添加一周变异的 a。 为避免出现这种情况,请在执行日期数学运算之前克隆moment:

var a = moment('2016-01-01'); 
var b = a.clone().add(1, 'week'); 
a.format();
"2016-01-01T00:00:00-06:00"

日期数学与时间数学

时间数学和日期数学之间存在逻辑差异。

在 Moment.js 中,时间数学假定线性时间尺度,只是根据提供的时间单位的数量递增或递减基于 UTC 的时间戳。

日期数学不使用线性时间尺度,而是增加或减少日历上的日期。 这是因为一天、一个月或一年中的时间量是可变的。 例如,由于夏令时转换,一天的长度可能在 23 到 25 小时之间。 当然,月份的天数会有所不同,并且由于闰年,年份的长度也会有所不同。 日期数学会导致一些有趣的场景。

由于夏令时,一天可能不等于 24 小时:

//date math
moment('2016-03-12 13:00:00').add(1, 'day').format('LLL')
"March 13, 2016 1:00 PM"
//time math
moment('2016-03-12 13:00:00').add(24, 'hours').format('LLL')
"March 13, 2016 2:00 PM"

由于闰年,一年可能不等于 365 天:

moment('2016-01-01').add(1, 'year').format('LL')
"January 1, 2017"
moment('2016-01-01').add(365, 'day').format('LL')
"December 31, 2016"

由于天数学中持续时间的可变性,Moment 的 API 不正式支持天数和更大天数的加减小数值。 Moment.js 将接受小数值并通过四舍五入到最接近的整数来尽力处理它们。

2.12.0 开始,十进制日期和月份值使用绝对值/舍入转换为整数。 这意味着 1.5 舍入为 2,-1.5 舍入为 -2。

moment().add(1.5, 'days') == moment().add(2, 'days')
moment().add(-1.5, 'days') == moment().add(-2, 'days') == moment().subtract(1.5, 'days') == moment().subtract(2, 'days')
moment().add(2.3, 'months') == moment().add(2, 'months')
moment().add(-2.3, 'months') == moment().add(-2, 'months') == moment().subtract(2.3, 'months') == moment().subtract(2, 'months')

季度和年份转换为月份,然后绝对值/四舍五入。

moment().add(1.5, 'years') == moment().add(18, 'months')
moment().add(.8, 'years') == moment().add(9.6, 'months') == moment().add(10, 'months')
moment().add(1.5, 'quarters') == moment().add(4.5, 'months') == moment().add(5, 'months')

时区与偏移量

通常,人们对时区和 UTC 偏移量之间的差异感到困惑。

UTC 偏移量是表示特定日期和时间距 UTC 多远的值。 它以 HH 格式表示:mm大部分时间。

时区是所有人遵守法定标准时间的地理区域。

由于夏令时,一个时区通常与 UTC 有多个偏移量。 多个时区在一年中的某个时间点可能具有相同的偏移量。 例如,美国/芝加哥、美国/丹佛和美国/伯利兹时区的偏移量均为 -06:00 在不同的时间。 因此,不可能仅从偏移值推断出时区。

Moment.js 核心库提供与基于偏移值调整时间相关的功能。 它不支持根据时区数据调整日期 - 这是由 Moment TimeZone 库提供的。

For an in depth description of this issue, see the Stack Overflow tag.

JavaScript 日期

Moment.js 为原生 JavaScript 日期对象提供了一个封装器。 在这样做时,Moment.js 扩展了功能并解决了对象中的几个缺陷。

使用原生日期进行解析尤其不可预测。 例如,假设我在美国使用一台计算机,但我有一个 DD/MM/YYYY 格式的日期。

var a = new Date('01/12/2016'); //December 1 2016 in DD/MM/YYYY format
//"Tue Jan 12 2016 00:00:00 GMT-0600 (Central Standard Time)"

对于原生 Date 对象的这种行为没有好的解决方法。 Moment 的解析器处理得很好:

moment('01/12/2016', 'DD/MM/YYYY', true).format()
"2016-12-01T00:00:00-06:00"

此外,ECMA Script 5 规范对 ISO 8601 日期的偏移量做出了不寻常的断言:

缺少时区偏移量的值为 "Z"

实际上,这意味着没有偏移量的 ISO 8601 日期将被视为 UTC 值,从而产生以下奇怪现象:

//US local format
var a = new Date('1/1/2016'); 
//"Fri Jan 01 2016 00:00:00 GMT-0600 (Central Standard Time)"

//ISO 8601
var a = new Date('2016-01-01');
//"Thu Dec 31 2015 18:00:00 GMT-0600 (Central Standard Time)"

ES2015 规范修复了这个错误,使其与 ISO8601 规范保持一致,后者指定本地时间不存在偏移量。 这本身就很糟糕,因为它具有许多负面的向后兼容性影响。

对于 Moment,日期始终被解释为本地时间,除非您另有说明。 这不会随着 ES2015 的采用而改变。

moment('2016-01-01')
//"2016-01-01T00:00:00-06:00"

算术是另一个缺少原生 Date 对象的领域。 Date 对象实际上没有为此提供 API。 相反,它依赖于溢出的日期值。 假设您想将 2016 年 4 月 30 日增加 1 天。 使用日期对象,您可以执行以下操作:

var a = new Date('4/30/2016'); 
a.setDate(a.getDate() + 1);

这可以解决问题,但有点不直观。 Moment 提供了一个 API 来添加/减去:

moment('4/30/2016', 'MM/DD/YYYY').add(1, 'day')
//"2016-05-01T00:00:00-05:00"

内部属性

Moment 对象有几个以 ``_` 为前缀的内部属性。

最常查看的内部属性是 _d 属性,它保存 Moment 封装的 JavaScript 日期。 通常,开发人员对 _d 值的控制台输出感到困惑。 Moment 使用一种称为纪元偏移的技术,导致此属性有时与 Moment 反映的实际日期值不同。 特别是如果正在使用 Moment TimeZone,此属性几乎永远不会与 Moment 从其公共 .format() 函数输出的实际值相同。 因此,不应将 _d and any other properties prefixed with _ 的值用于任何目的。

要打印出 Moment 的值,请使用 .format(), .toString() or .toISOString()

要从 Moment 检索原生 Date 对象,请使用 .toDate()。 此函数返回一个正确移动的日期以与第三方 API 交互。

Moment.js 有一个非常灵活和高级的解析器,可以提供范围广泛的功能。
解析器的灵活性也使其成为 Moment.js 最常被误用的工具之一。

本节列出了有关如何在您的情况下正确使用解析器的一些指南。

本地 vs UTC vs 偏移量

Moment 提供了三个用于解析日期的函数,基本的 moment 函数、moment.utc 和 moment.parseZone。

如果您希望在用户本地时间的上下文中与日期交互,请使用 moment 函数。

moment('2016-01-01T23:35:01');

这会产生一个与本地计算机具有相同 UTC 偏移量的日期:

"2016-01-01T23:35:01-06:00"

如果您希望将日期作为 UTC 日期进行交互,请使用 moment.utc:

moment.utc('2016-01-01T23:35:01');

这会导致日期的 utc 偏移量为 +0:00:

"2016-01-01T23:35:01+00:00"

如果您的日期格式具有固定的时区偏移量,请使用 moment.parseZone:

moment.parseZone("2013-01-01T00:00:00-13:00");

这会产生一个具有固定偏移量的日期:

"2013-01-01T00:00:00-13:00"

请注意,如果您使用 moment() 或 moment.utc() 来解析具有指定偏移量的日期,则日期将从该偏移量转换为本地或 UTC:

此日期偏移 8 小时,从 +2 移动到 -6(本地机器的偏移量)

moment('2016-01-01T00:00:00+02:00').format()
"2015-12-31T16:00:00-06:00"

此日期偏移 2 小时,从 +2 变为 UTC

moment.utc('2016-01-01T00:00:00+02:00').format()
"2015-12-31T22:00:00+00:00"

已知的日期格式

如果您知道要解析的日期字符串的格式,那么明确指定该格式始终是最佳选择。

例子:

moment('01/01/2016', 'MM/DD/YYYY')
moment('2016-01-01 11:31:23 PM', 'YYYY-MM-DD hh:mm:ss a')

如果您的日期采用 ISO 8601 格式,则可以使用 moment 中内置的常量来指示:

moment('2016-01-01 12:25:32', moment.ISO_8601)

ISO 8601 格式包括但不限于:

2013-02-08               # A calendar date part
2013-W06-5               # A week date part
2013-02-08T09            # An hour time part separated by a T
2013-02-08 09            # An hour time part separated by a space
2013-02-08 09:30:26      # An hour, minute, and second time part
2013-02-08 09+07:00      # +-HH:mm

See the API documentation on parsing strings for a full listing.

严格模式

严格模式是解析日期的推荐模式。 如果您的代码库允许,您应该始终使用严格模式。 在 GitHub 和 Stack Overflow 上看到的一半以上的解析器问题可以通过严格模式修复。

在以后的版本中,解析器将默认使用严格模式。

严格模式要求moment的输入与指定的格式完全匹配,包括分隔符。 通过将 true 作为第三个参数传递给 moment 函数来设置严格模式。

moment('01/01/2016', 'MM/DD/YYYY', true).format()
"2016-01-01T00:00:00-06:00"
moment('01/01/2016 some text', 'MM/DD/YYYY', true).format()
"Invalid date"

分离器匹配:

//forgiving mode
moment('01-01-2016', 'MM/DD/YYYY', false).format()
"2016-01-01T00:00:00-06:00"
//strict mode
moment('01-01-2016', 'MM/DD/YYYY', true).format()
"Invalid date"

严格模式修复的场景:

//UUID matches YYYYDDD because it starts with 7 digits
moment('5917238b-33ff-f849-cd63-80f4c9b37d0c', moment.ISO_8601).format()
"5917-08-26T00:00:00-05:00"
//strict mode fails because trailing data exists
moment('5917238b-33ff-f849-cd63-80f4c9b37d0c', moment.ISO_8601, true).format()
"Invalid date"
//date has out of range value but is parsed anyways
moment('100110/09/2015', 'MM/DD/YYYY').format()
"2015-10-09T00:00:00-05:00"
//strict mode catches out of range issue
moment('100110/09/2015', 'MM/DD/YYYY', true).format()
"Invalid date"
//wrong date is parsed because non-strict mode ignores data after format
moment('2016-12-31 11:32 PM').format('LT')
"11:32 AM"
//trailing data is noticed
moment('2016-12-31 11:32 PM', moment.ISO_8601, true).format('LT')
"Invalid date"

宽容模式

虽然严格模式在大多数情况下效果更好,但当传递给 moment 的字符串格式可能不同时,宽容模式可能非常有用。

宽容模式有用的常见场景是第三方 API 提供日期,并且该 API 的日期格式可能会更改。 假设一个 API 以 'YYYY-MM-DD' 格式发送日期,然后更改为 'MM/DD/YYYY' 格式。

在严格模式下,以下代码会导致显示 'Invalid Date':

moment('01/12/2016', 'YYYY-MM-DD', true).format()
"Invalid date"

在使用格式字符串的宽容模式下,您会得到一个错误的日期:

moment('01/12/2016', 'YYYY-MM-DD').format()
"2001-12-20T00:00:00-06:00"

宽容模式下的错误日期场景对用户来说当然不太明显,但由于这个原因可能会在很长一段时间内被忽视。

在严格模式和宽容模式之间进行选择时,重要的是要考虑是日期准确更重要,还是日期从不显示为 "无效日期"。

多种格式

Moment 的解析器支持为日期字符串指定多种可能的格式。 这对于日期可能来自多个数据源的情况非常有用。 只需将格式作为数组传递:

moment('12 March, 2016', ['DDMMMMY', 'MMMMDDY']).format()
"2016-03-12T00:00:00-06:00"
moment('March 12, 2016', ['DDMMMMY', 'MMMMDDY']).format()
"2016-03-12T00:00:00-06:00"

为了使此功能正常工作,moment 必须解析提供的每种格式。 因此,使用的格式越多,解析所需的时间就越长。 Moment 用于确定使用哪种格式的启发式方法如下:

  • 比无效日期更喜欢产生 valid 日期的格式。
  • 更喜欢解析更多字符串的格式而不是更少,使用更多格式而不是更少的格式,即更喜欢更严格的解析。
  • 优先使用数组中较早的格式而不是较晚的格式。

Moment.js 有几个地方显示有关将来将被删除的功能的弃用警告。 此处概述了解决方法。

定义语言环境覆盖

Use moment.updateLocale(localeName, config) to change an existing locale. 
moment.defineLocale(localeName, config) should only be used for creating a new locale

当您尝试使用 defineLocale 函数更改现有语言环境时,会抛出此弃用警告。 这样做会导致与属性继承相关的意外行为。 moment.updateLocale 将正确替换现有语言环境的属性。

View original pull request

父语言环境未定义

2.16.0 起已删除警告。

可以在定义或加载父对象本身之前用父对象定义语言环境。 如果在创建moment时父级不存在或无法延迟加载,则父级将默认为全局语言环境。

找不到语言环境

Locale <key> not found. Did you forget to load it?

当设置了全局语言环境但 Moment 找不到它时,会显示此警告。 也许这个语言环境没有捆绑在您的副本中。

加法/减法

moment().add(period, number) is deprecated. Please use moment().add(number, period)
moment().subtract(period, number) is deprecated. Please use moment().subtract(number, period)

Moment 不推荐将加减参数排序为(句点,数字)。 反转你的参数。

坏的:

moment().add('hours', 3);

好的:

moment().add(3, 'hours');

最小/最大

moment().min is deprecated, use moment.max
moment().max is deprecated, use moment.min

这个警告不是拼写错误,但它令人困惑。

在 2.7.0 版本之前,moment 支持 moment().min 和 moment().max 函数。 这些功能不直观。

Min 将返回所讨论的两个moment中较大的一个,而 max 将返回较小的一个。

由于这种反向行为,弃用警告中提供的建议是正确的。

moment('2016-01-01').min('2016-02-01').format()
"2016-02-01T00:00:00-06:00"
//is equivalent to
moment.max(moment('2016-01-01'), moment('2016-02-01')).format()
"2016-02-01T00:00:00-06:00"
moment('2016-01-01').max('2016-02-01').format()
"2016-01-01T00:00:00-06:00"
//is equivalent to
moment.min(moment('2016-01-01'), moment('2016-02-01')).format()
"2016-01-01T00:00:00-06:00"

See original GitHub issue.

moment().zone is deprecated, 
use moment().utcOffset instead.

此弃用是为了清楚起见。

moment().zone() 的结果是一个整数,表示给定moment相对于 UTC 的偏移分钟数,符号倒置(美国moment为正值)。

使用 moment().zone(number) 设置偏移量将设置日期的偏移量,也使用倒号。

因为时区与偏移量不同,所以名称更改为 utcOffset。 那时符号被更正以反映 UTC 偏移的实际方向。

moment().zone()
360
//is replaced by
moment().utcOffset()
-360

moment().zone(420)
//is replaced by 
moment().utcOffset(-420)

有关时区与偏移量的更多信息,请参阅时区与偏移量指南。

View original GitHub issue.

这些资源由日期/时间/时区社区的成员制作。

博客

Matt Johnson's Blog

  • Moment.js 核心贡献者。 编程中的日期和时间概念。

Maggie Pint's Blog

  • Moment.js 核心贡献者。 Moment.js 开发进度和蓝图。 Moment.js 库帮助。

Lau Taarnskov's blog

  • 作者 - 长生不老药日历。 日期和时间编程概念。 长生不老药日期和时间。

Jon Skeet's blog

  • 作者 - NodaTime。 Stack Overflow #1 用户。 日期和时间编程概念。