I'm running into an issue with adding an amount of seconds to a moment date in my Rails app. I use Stimulus for JS but that shouldn't relevant to the cause of the problem.
I've got a select form input that I've generated using the Rails form builder using these 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]]
That results in a select that looks like this:
<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>
I have some JS that takes the value of the selected option and adds that amount of seconds to a moment date, and then sets the value of a different input to the new moment date that results from that calculation:
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");
This works correctly... until you get to 1 month or larger. Here's how the calculations come out for a start date of 2021-02-25 12:00
:
Select option | Result of adding seconds to start date |
---|---|
1 day | 2021-02-26 12:00 |
3 days | 2021-02-28 12:00 |
7 days | 2021-03-04 12:00 |
14 days | 2021-03-11 12:00 |
1 month | 2021-03-27 23:29 |
3 months | 2021-05-27 20:27 |
6 months | 2021-08-27 03:54 |
1 year | 2022-02-25 17:49 |
I've been trying to reverse-engineer the weird (bolded) results. What's causing these results? Is it some discrepancy between what Rails considers 1 month and what moment considers 1 month?
Though I'm unsure about the nuances of why Rails uses, say 2629746
as a representation of 1.month
in seconds, I've implemented a workaround for this scenario.
I wanted to keep the select input generated as-is by Rails, since the values it generates with (listed in the question) work great on the Rails end. That is, I can use that select seamlessly to select an interval to be used as an ActiveSupport::Duration
. For this reason, I didn't want to change the select input.
Instead, I wrote a simple (albeit a bit hacky) function to do the correct moment operation depending on the value of the 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;
}
}
Then I can just call it like so:
this.addRailsSecondsStringToMoment(this.sendFrequencySelectTarget.value, finalRunAt);
... where finalRunAt
is a moment
object. Could probably be a bit prettier, but it works. Happy to accept a sexier solution if someone posts one, especially if it provides some information on why Rails uses the seconds values it does.