为什么向时刻日期添加以秒为单位的持续时间会为过去 1 个月的值提供奇怪的结果?

问题描述

我在 Rails 应用中遇到了向 moment 日期添加秒数的问题。我将 Stimulus 用于 JS,但这与问题的原因无关。

我使用 Rails 表单构建器使用这些 options_for_select 生成一个选择表单输入:

[["1 day",1.day],["3 days",3.days],["7 days",1.week],["14 days",2.weeks],["1 month",1.month],["3 months",3.months],["6 months",6.months],["1 year",1.year]]

这会导致一个看起来像这样的选择:

<select class="form-control" data-admin--reports-form-target="sendFrequencySelect"
<option value="86400">1 day</option>
<option value="259200">3 days</option>
<option value="604800">7 days</option>
<option value="1209600">14 days</option>
<option value="2629746">1 month</option>
<option value="7889238">3 months</option>
<option selected="selected" value="15778476">6 months</option>
<option value="31556952">1 year</option>
</select>

我有一些 JS 获取所选选项的值并将该秒数添加到时刻日期,然后将不同输入的值设置为该计算产生的新时刻日期:

let finalRunAt = moment(this.lastGeneratedTimestampInputTarget.value,"YYYY-MM-DD HH:mm");
let secondsToAdd = parseInt(this.sendFrequencySelectTarget.value);
finalRunAt.add(secondsToAdd,"seconds");
this.finalRunTimestampInputTarget.value = finalRunAt.format("YYYY-MM-DD HH:mm");

这可以正常工作...直到您达到 1 个月或更长时间。以下是开始日期 2021-02-25 12:00 的计算结果:

选择选项 将秒添加到开始日期的结果
1 天 2021-02-26 12:00
3 天 2021-02-28 12:00
7 天 2021-03-04 12:00
14 天 2021-03-11 12:00
1 个月 2021-03-27 23:29
3 个月 2021-05-27 20:27
6 个月 2021-08-27 03:54
1 年 2022-02-25 17:49

我一直在尝试对奇怪的(粗体)结果进行逆向工程。是什么导致了这些结果? Rails 认为 1 个月和哪个时刻认为 1 个月之间是否存在差异?

解决方法

尽管我不确定 Rails 为何使用 2629746 作为 1.month 的表示形式(以秒为单位)的细微差别,但我已经针对这种情况实施了一种解决方法。

我想保持由 Rails 生成的选择输入,因为它生成的值(在问题中列出)在 Rails 端工作得很好。也就是说,我可以无缝地使用该选择来选择要用作 ActiveSupport::Duration 的间隔。出于这个原因,我不想更改选择输入。

相反,我编写了一个简单(虽然有点笨拙)的函数来根据 select 的值执行正确的矩操作:

  addRailsSecondsStringToMoment(seconds_string,moment) {
    switch (seconds_string) {
      case "86400":
        moment.add(1,"days");
        break;
      case "259200":
        moment.add(3,"days");
        break;
      case "604800":
        moment.add(7,"days");
        break;
      case "1209600":
        moment.add(14,"days");
        break;
      case "2629746":
        moment.add(1,"months");
        break;
      case "7889238":
        moment.add(3,"months");
        break;
      case "15778476":
        moment.add(6,"months");
        break;
      case "31556952":
        moment.add(1,"years");
        break;
    }
  }

然后我可以这样称呼它:

this.addRailsSecondsStringToMoment(this.sendFrequencySelectTarget.value,finalRunAt);

... 其中 finalRunAt 是一个 moment 对象。可能会更漂亮一点,但它有效。如果有人发布了一个更性感的解决方案,我很乐意接受,尤其是如果它提供了一些关于 Rails 为何使用它所使用的秒值的信息。