moment.js 模拟 local() 所以单元测试运行一致

Nod*_*.JS 4 javascript momentjs jestjs

我想测试下面的一段代码。我想知道是否有办法模拟moment.js或强制它认为我当前的位置是America/New_York这样我的单元测试不会在 gitlab.ci runner 中失败,它可能位于不同的地理位置?

  const centralTimeStartOfDay = moment.tz('America/Chicago').startOf('day');
  const startHour = centralTimeStartOfDay
    .hour(7)
    .local()
    .hour();
    
Run Code Online (Sandbox Code Playgroud)

基本上我想对我的时区进行硬编码,America/New_York并希望这个函数的行为一致。

编辑

我试过:

  1. Date.now = () => new Date("2020-06-21T12:21:27-04:00")
  2. moment.tz.setDefault('America/New_York')

而且,我得到了相同的结果。我想模拟当前时间,以便startHour返回一致的值。

dap*_*azz 9

问题

所以这个问题没有一行答案。这个问题是 javascript 的一个基本问题,您可以通过以下两种方式之一查看日期:

  1. UTC( getUTCHours()getUTCMinutes()等等)
  2. 本地(即系统,getHours()getMinutes()等等)

并且没有指定的方法来设置有效的系统时区,甚至是 UTC 偏移量。

(浏览mdnDate参考查看规范以了解这一切是多么无用。)

“可是等等!” 我们哭了,“这不就是为什么moment-timezone存在吗??”

不完全是。momentmoment-timezone在 javascript 中更好/更轻松地控制管理时间,但即使他们也无法知道本地时区Date正在使用什么,并使用其他机制来了解这一点。这是一个如下问题。

一旦你了解了代码,你就会看到 moment 的 moment.local()方法(setOffsetToLocal 的原型声明和实现)有效地执行以下操作:

  • 将时刻的 UTC 偏移量设置为 0
  • 通过设置_isUTC为 false禁用“UTC 模式” 。

禁用“UTC 模式”的效果是意味着大多数访问器方法被转发到底层Date对象。例如,.hours()最终调用moment/get-set.jsget(),如下所示:

export function get(mom, unit) {
    return mom.isValid()
        ? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]()
        : NaN;
}
Run Code Online (Sandbox Code Playgroud)

_dDate时刻 ( mom) 包裹的对象。对于非 UTC 模式时刻如此有效,moment.hours()是对Date.prototype.getHours(). 无论您设置了什么moment.tz.setDefault(),或者您是否覆盖了Date.now(). 这些东西都没有使用。

另一件事...

你说:

基本上我想硬编码我的时间是 America/New_York 并希望这个函数的行为一致

但实际上,这通常是不可能的。您正在使用芝加哥,我认为它与纽约同步偏移,但例如,英国在与美国不同的时间转移,因此如果您从美国时区到英国时区。

解决方案。

但这仍然令人沮丧,因为我不希望我在波兰和美国西海岸的开发人员因为我的 CI 服务器在 UTC 中运行而进行破坏性的本地测试。那么我们能做些什么呢?

第一个解决方案是一个非解决方案:找到一种不同的方式来做你正在做的事情!通常使用的用例.local()非常有限,并且是向用户显示其当前偏移量中的时间。它甚至不是他们的时区,因为本地Date方法只会查看当前的偏移量。因此,大多数情况下,您只想在当前时间使用它,或者如果您不介意使用它的一半Date对象是否错误(对于使用夏令时的时区)。最好通过其他方式了解用户想要的时区,而根本不使用.local()

第二种解决方案也是一个非解决方案:不要太担心你的测试!显示本地时间的主要事情是它有效,您并不真正关心它到底是什么。手动验证它显示的时间是否正确,并且在您的测试中只验证它返回一个看起来合理的东西,而不检查特定时间。

如果您仍然想继续,最后一个解决方案至少可以使您的案例和其他一些解决方案起作用,并且如果您发现需要扩展它,您需要做什么很明显。然而,这是一个复杂的领域,我不保证这不会有一些意想不到的副作用!

在您的测试设置文件中:

[
  'Date',
  'Day',
  'FullYear',
  'Hours',
  'Minutes',
  'Month',
  'Seconds',
].forEach(
  (prop) => {
    Date.prototype[`get${prop}`] = function () {
      return new Date(
        this.getTime()
        + moment(this.getTime()).utcOffset() * 60000
      )[`getUTC${prop}`]();
    };
  }
);
Run Code Online (Sandbox Code Playgroud)

您现在应该能够使用moment.tz.setDefault()并且使用.local()应该允许您访问日期时间的属性,就好像它认为本地时区与moment-timezone.

我想过尝试修补moment,但它比 复杂得多Date,而且修补Date应该很健壮,因为它是原始的。