Moment: 当时区具有午夜DST规则时,从日期中添加和减去日期不会保留小时

创建于 2018-08-22  ·  8评论  ·  资料来源: moment/moment

问题说明:

我正在尝试在(时刻日期数组)中创建一个日期范围,其中每个日期都比前一个日期晚1天。 这对于所有用户都可以正常工作,但是我们已经获得了针对美国/圣地亚哥时区的用户的错误报告,该用户在UI中看到重复的日期。 我注意到的是,将特定日期增加1天实际上只增加了23小时。

在美国/圣地亚哥时区中,日期为8/11/2018 00:00:00 ,增加1天将使时间增加到8/11/2018 23:00:00而不是8/12/2018 00:00:00

该文档解释说,在使用days_时跨DST转移时应保留小时数,但此处似乎不正确。

添加跨越夏时制时间的时间时,还需要记住一些特殊的注意事项。 如果要添加年,月,周或天,则原始小时将始终与添加的小时匹配。

// This code is from moment.js docs https://momentjs.com/docs/#/manipulating/add/
var m = moment(new Date(2011, 2, 12, 5, 0, 0)); // the day before DST in the US
m.hours(); // 5
m.add(1, 'days').hours(); // 5

重现步骤

在以下代码中,使用的时区在午夜是DST规则。 我的日期是该规则生效前一天的午夜。我为此日期添加了1天,并希望它应该是第二天的午夜/ 0小时(应保留小时),但实际上它只是在添加23小时。

fmt = d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('08/11/2018 00:00:00'), 'America/Santiago');
fmt(x);                       // "2018-08-11T00:00:00-04:00 America/Santiago"
fmt(x.clone().add(1, 'day')); // "2018-08-11T23:00:00-04:00 America/Santiago" - offset unchanged, added 23 hours not 1 day
fmt(x.clone().add(2, 'day')); // "2018-08-13T00:00:00-03:00 America/Santiago" - original hour preserved now

在这里,您可以看到添加24小时将其向上移动了25小时,并且更改了时区偏移量。

fmt = d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('08/11/2018 00:00:00'), 'America/Santiago');
fmt(x);                          // "2018-08-11T00:00:00-04:00 America/Santiago"
fmt(x.clone().add(24, 'hours')); // "2018-08-12T01:00:00-03:00 America/Santiago" - offset changed, added 25 hours
fmt(x.clone().add(48, 'hours')); // "2018-08-13T01:00:00-03:00 America/Santiago"

环境:

  • 版本68.0.3440.84(官方内部版本)(64位)
  • Mac OSX El Capitan 10.11.6(15G31)

其他可能有用的信息:

  • 机器时区:美国东部时区,夏令时
  • 时间和日期代码运行时间:2018年8月22日下午3:00
  • 我在Moment.js网页上的Chrome开发工具中重现了此问题

JS日期输出

(新的Date())。toString()

  • 2018年8月22日星期三15:13:40 GMT-0400(东部夏令时间)

(新的Date())。toLocaleString()

  • 2018/8/22下午3:13:40

(新的Date())。getTimezoneOffset()

  • 240

navigator.userAgent

  • Mozilla / 5.0(Macintosh; Intel Mac OS X 10_11_6)AppleWebKit / 537.36(KHTML,例如Gecko)Chrome / 68.0.3440.84 Safari / 537.36

瞬间版本

  • 2.22.2
Bug DST Pending Next Release Up-For-Grabs

最有用的评论

这里的行为看起来像是在时区v0.5.4和v0.5.26之间进行了更改。
在旧版本中,在“ 2018-08-11”中增加1天可让您当天的晚上11点
在新版本中,它在午夜为您提供``2018-08-12''。 但是这个时间实际上不存在,添加1分钟可以返回一个小时-但是,这种DST更改应该增加1小时。

旧版本,其行为如本期所述:
https://jsfiddle.net/eqyvuxht/1/

新版本,改变了行为,但我认为还是错误的?
https://jsfiddle.net/0h6atn4b/4/

这是一种解决方法:

function switchZone(m, zone) {
  let arr = [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second()];
  if(zone) {
    return moment.tz(arr, zone);
  }
  return moment(arr);
}

function safeAddDays(m, days) {
   let oldZone = m.tz();
   let utc = switchZone(m, 'UTC');
   utc.add(days, 'days');
   return switchZone(utc, oldZone);
}

所有8条评论

我试图在欧洲/罗马时区重现此内容,但无法执行以下操作:

欧洲/罗马-https: //www.timeanddate.com/time/change/italy/rome

2018年10月28日-夏令时结束
当当地夏令时快到了
2018年10月28日星期日凌晨3:00:00将时钟向后调1小时
改为当地标准时间2018年10月28日星期日凌晨2:00:00。

此代码中没有错误。

d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('10/27/2018 00:00:00'), 'Europe/Rome');
fmt(x)                       // "2018-10-27T06:00:00+02:00 Europe/Rome"
fmt(x.clone().add(1, 'day')) // "2018-10-28T06:00:00+01:00 Europe/Rome" - tz offset changed, hour preserved as expected
fmt(x.clone().add(2, 'day')) // "2018-10-29T06:00:00+01:00 Europe/Rome"

您可以看到TZ发生了变化,但是小时数保持不变。

根据复制步骤,当添加的日期恰好落在DST截止日期上时,代码中似乎存在差异。 在圣地亚哥时区,DST规则是在午夜将时钟倒退一个小时。

似乎这种边缘情况可能专门针对DST截止时间为午夜的时区,因为我无法使用截止时间为凌晨3点的时区进行重现,而x日期为凌晨3点。

欧洲/罗马-https: //www.timeanddate.com/time/change/italy/rome

2018年10月28日-夏令时结束
当当地夏令时快到了
2018年10月28日星期日凌晨3:00:00将时钟向后调1小时
改为当地标准时间2018年10月28日星期日凌晨2:00:00。

此代码中没有错误。

fmt = d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('October 27, 2018 03:00:00'), 'Europe/Rome');
fmt(x);                       // "2018-10-27T09:00:00+02:00 Europe/Rome"
fmt(x.clone().add(1, 'days')) // "2018-10-28T09:00:00+01:00 Europe/Rome" - tz offset changed, hour preserved as expected
fmt(x.clone().add(2, 'days')) // "2018-10-29T09:00:00+01:00 Europe/Rome"

我能够确认该错误仅在DST截止时间是午夜时才存在于时区,并且您要向日期添加时间,从而导致该日期恰好在截止时间。

美国/ Punta_Arenas - https://www.timeanddate.com/time/zone/chile/punta-arenas
在2016年,该时区在午夜也有一个截止时间。

此代码似乎有一个错误。 每行后查看评论

fmt = d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('08/13/2016 00:00:00'), 'America/Punta_Arenas')
fmt(x);                        // "2016-08-13T00:00:00-04:00 America/Punta_Arenas"
fmt(x.clone().add(1, 'days')); // "2016-08-13T23:00:00-04:00 America/Punta_Arenas" - 23 hours added, not 1 day, no tz offset change
fmt(x.clone().add(2, 'days')); // "2016-08-15T00:00:00-03:00 America/Punta_Arenas"

我发现在减去相同类型的时区(午夜DST)时似乎出现了类似的问题。

将日期精确地减去DST时,不只在当天保留小时,而是更改日期。

fmt = d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('08/13/2018 23:00:00'), 'America/Santiago');
fmt(x);                             // "2018-08-14T00:00:00-03:00 America/Santiago"
fmt(x.clone().subtract(1, 'days')); // "2018-08-13T00:00:00-03:00 America/Santiago"
fmt(x.clone().subtract(2, 'days')); // "2018-08-12T01:00:00-03:00 America/Santiago" - hour not preserved, but date changed
fmt(x.clone().subtract(3, 'days')); // "2018-08-11T00:00:00-04:00 America/Santiago" - original hour preserved now

我们收到了以America/Asuncion作为其时区的用户的另一报告。

在我们的应用程序中产生错误的函数如下所示:

function generateDayRange(start, end) {
    const days = [];
    let current = start.clone();

    while (current <= end) {
        days.push(current.clone());
        current = current.add(1, 'days');
    }

    return days;
}

使用此功能时,我们传入的开始日期是一天的开始,而结束日期的时间不太重要:

generateDayRange(startDate.clone().startOf('week'), startDate.clone().endOf('week'));
generateDayRange(getAppDate(startDate), getAppDate(endDate));
generateDayRange(startRange, startRange.clone().add(27, 'days'));

问题在于,生成的时刻日期数组包含重复的日期,这反映在我们的应用程序的UI中(日历具有重复的日期)。

我更大的担心是,由于库的行为不符合预期,因此可能会注意到一些更细微的问题,例如感知到的数据丢失,错误的请求等。

我将努力在Codepen或其他工具中进行一系列测试,以使好奇的读者可以刺痛地解决此问题。

相关问题#4785包含以下代码的链接:
时刻示例https://runkit.com/embed/1r62d83amq7x
luxon可以正确处理
https://runkit.com/embed/t49achvensqf

我知道最近有DST更改,因此我们应该对照develop (但未发布)上当前的代码进行检查。 或者,我们可以等到下一个版本,看看情况是否仍然相同。

这里的行为看起来像是在时区v0.5.4和v0.5.26之间进行了更改。
在旧版本中,在“ 2018-08-11”中增加1天可让您当天的晚上11点
在新版本中,它在午夜为您提供``2018-08-12''。 但是这个时间实际上不存在,添加1分钟可以返回一个小时-但是,这种DST更改应该增加1小时。

旧版本,其行为如本期所述:
https://jsfiddle.net/eqyvuxht/1/

新版本,改变了行为,但我认为还是错误的?
https://jsfiddle.net/0h6atn4b/4/

这是一种解决方法:

function switchZone(m, zone) {
  let arr = [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second()];
  if(zone) {
    return moment.tz(arr, zone);
  }
  return moment(arr);
}

function safeAddDays(m, days) {
   let oldZone = m.tz();
   let utc = switchZone(m, 'UTC');
   utc.add(days, 'days');
   return switchZone(utc, oldZone);
}

我们在本月(2019年9月)遇到了同样的问题,使用我们的日期选择器的圣地亚哥用户看到的日历的日期分别为1、2、3、4、5、6、7、7、7、7 9月……在后台,我们还使用了.add(1, 'days') 。 如果到那时我们还没有解决方案,它将在明年2020年9月再次发生。

@mblandfo解决方法似乎也对我们

此页面是否有帮助?
0 / 5 - 0 等级