Moment.js 指南

指南区域旨在帮助开发人员学习更好地处理日期和时间问题领域,以及 Moment.js 库。我们在这里解决了最常见的支持请求,因此这里是您查找任何问题解决方案的好地方。

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

如果您刚开始使用,请查看此 scrimba moment 指南

可变性 1.0.0+

编辑

Moment.js 中的 moment 对象是可变的。这意味着像加、减或设置这样的操作会更改原始的 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"

日期计算 vs 时间计算

编辑

时间计算和日期计算之间存在逻辑差异。

在 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')

时区 vs 偏移量

编辑

人们经常混淆时区和 UTC 偏移量之间的区别。

UTC 偏移量是一个值,表示特定日期和时间与 UTC 的距离。它在大多数情况下以 HH:mm 格式表示。

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

由于夏令时,一个时区通常与 UTC 有多个偏移量。一年中的某个时间点,多个时区可能具有相同的偏移量。例如,时区 America/Chicago、America/Denver 和 America/Belize 在不同时间都具有 -06:00 的偏移量。因此,不可能仅从偏移量值推断出时区。

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

有关此问题的详细说明,请参阅 Stack Overflow 标签。

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 Date。开发人员经常对 _d 值的控制台输出感到困惑。Moment 使用一种称为纪元偏移的技术,导致此属性有时与 Moment 反映的实际日期值不同。特别是如果正在使用 Moment TimeZone,则此属性几乎永远不会与 Moment 从其公共 .format() 函数输出的实际值相同。因此,_d 和任何其他以前缀 _ 开头的属性的值不应被用于任何目的。

要打印 Moment 的值,请使用 .format().toString().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

有关解析字符串的完整列表,请参阅 API 文档。

严格模式

编辑

严格模式是解析日期的推荐模式。如果您的代码库允许,您应该始终使用严格模式。在 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”格式。

在严格模式下,以下代码会导致显示“无效日期”

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 用于确定使用哪种格式的启发式方法如下

  • 优先选择产生有效日期的格式,而不是无效日期的格式。
  • 优先选择解析字符串更多部分和使用格式更多部分的格式,即优先选择更严格的解析。
  • 优先选择数组中较早的格式,而不是较晚的格式。

在几个地方,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 将正确替换现有区域设置上的属性。

查看原始拉取请求

父区域设置未定义

编辑

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 不赞成将 add 和 subtract 的参数排序为(周期,数字)。反转您的参数。

错误

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 将返回所讨论的两个时刻中较大的一个,而 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"

查看原始 GitHub 问题。

时区

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

进行此弃用是为了清楚起见。

moment().zone() 的结果是一个整数,表示给定时刻与 UTC 的偏移分钟数,符号反转(美国时刻导致正值)。

使用 moment().zone(number) 设置偏移量将在日期上设置偏移量,也使用反转符号。

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

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

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

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

查看原始 GitHub 问题。

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