【问题标题】:What is the source of this off-by-one error in a JavaScript date?JavaScript 日期中这种非同一个错误的根源是什么?
【发布时间】:2020-03-03 16:29:34
【问题描述】:

我正在尝试编写一个函数,该函数将采用07/2020 之类的字符串,然后返回是否超过三个月。

我编写了一个函数isMoreThan3MonthsHence,我有理由确信它可以正常工作:

const isMoreThan3MonthsHence = ({ utcYear, utcMonth }, 
                                now = new Date, 
                                target = new Date(Date.UTC(utcYear, utcMonth)), 
                                threeMonthsAway = new Date(now.valueOf()).setUTCMonth(now.getUTCMonth() + 3)) => 
    (target > threeMonthsAway)


console.log(isMoreThan3MonthsHence({ utcYear: 2020, utcMonth: 7 })) // true (correct!)

当我尝试构造一个Date 对象以用于填充isMoreThan3MonthsHence 的参数时,问题就出现了。

const validate = (str, 
                  [localMonth, localYear] = str.split('/'), 
                  date = new Date(+localYear, (+localMonth)-1)) => 
    isMoreThan3MonthsHence({ utcYear: date.getUTCFullYear(), utcMonth: date.getUTCMonth() })

// Note: input is one-based months
console.log(validate('07/2020')) // false (but should be true!)

我认为原因是在 validate 中新建 Date 而不指定时区将使用在提供的日期有效的本地时区,即 BST (UTC+1)。

Wed Jul 01 2020 00:00:00 GMT+0100 (British Summer Time)

这个时间实际上是世界标准时间 6 月 30 日 2300 小时。所以月份实际上是从零开始的5。但我不想要这种行为。我希望它指定 7 月实际上意味着 UTC 中的 7 月。

我该如何解决这个问题?

【问题讨论】:

  • 可能是因为 Date.UTC() 将您的参数视为 UTC,而 new Date() 将它们视为本地 (UTC+1),一旦您调用 getUTCMonth(),您确实比 UTC 早了一个小时观点(然后仍然在上个月)。为什么不在两者中都使用Date.UTC()
  • new Date(2020, 6).toUTCString() - 即查看整个日期,而不仅仅是月份和年份 - 你会清楚地看到原因。是的,这确实与您在 7 月进入 BST 的事实有关。

标签: javascript date


【解决方案1】:

看起来您正在混合使用 Date.UTC 而不是在实例化日期时。例如,如果您将以下内容用于验证功能:

const validate = (str, 
                  [month, year] = str.split('/'), 
                  date = new Date(Date.UTC(+year, (+month)-1))) => 
    isMoreThan3MonthsHence({ utcYear: date.getUTCFullYear(), utcMonth: date.getUTCMonth() })

// Note: input is one-based months
console.log(validate('07/2020')) // Now true

它按预期工作:JSFiddle

完全删除Date.UTC 的使用将在用户的本地时区执行计算,包括任何适用的夏令时调整。这可以被视为一种有效的方法,但是会导致您所描述的行为。


请注意,我已根据来自 Bergi 的反馈重命名了 local 前缀变量。使用 Date.UTC 意味着您正在传递 UTC 参数。

【讨论】:

    【解决方案2】:

    除了混合 UTC 和本地日期之外,您添加 3 个月的方式会导致对 3 月 31 日等日期的错误响应,其中仅通过增加月份数字来添加 3 个月会导致日期为 7 月 1 日。见Adding months to a Date in JavaScript

    所以如果在 3 月 31 日运行,validate('07,2020') 将返回 false。

    要解决这个问题,在添加月份时,请检查更新的日期是否仍然在该月的同一天,否则它会翻转,因此请将其设置为上个月的最后一天。

    function validate(s) {
      let testDate = addMonths(new Date(), 3);
      let [m, y] = s.split(/\D/);
      return testDate < new Date(y, m-1);
    };
    
    function addMonths(date, months) {
      let d = date.getDate();
      date.setMonth(date.getMonth() + +months);
      // If rolled over to next month, set to last day of previous month
      if (date.getDate() != d) {
        date.setDate(0);
      }
      return date;
    }
    
    // Sample
    console.log('On ' + new Date().toDateString() + ':');
    ['07/2020', '04/2020'].forEach(
      s => console.log(s + ' - ' + validate(s))
    );

    【讨论】:

    • 链接问题中的最佳答案具有高度误导性。您愿意对其进行编辑以使其更准确吗?
    • @ben — 将链接答案更改为更好的答案。
    • 谢谢。实际上我已经进入并编辑了最初链接的答案。这是高度误导。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-14
    • 1970-01-01
    • 2013-07-24
    • 2019-11-19
    • 1970-01-01
    • 2021-12-08
    • 2011-03-05
    相关资源
    最近更新 更多