#javascript #node.js #date #moment-timezone #rrule
Вопрос:
У меня есть приложение, которое позволяет пользователям планировать задачи. Пользователи могут устанавливать расписание ( schedule
, строку rrule), а также часовой пояс ( scheduleTimeZone
, например, строку Asia/Dubai
).
Я пытаюсь написать функцию ( getNextRunAt
), которая получает следующее вхождение задачи в дату UTC и сохраняет это в моей базе данных Postgres в виде timestamptz
поля.
Я изо всех сил пытаюсь учесть летнее время, многие результаты-это час или даже выходной день.
Вот функция (в машинописном виде):
import RRule from 'rrule';
import moment from 'moment-timezone';
const getNextRunAt = ({
schedule,
scheduleTimeZone,
}: {
schedule?: string | null;
scheduleTimeZone?: string | null;
}): Date | undefined => {
if (!schedule) {
return undefined;
}
const options = RRule.parseString(schedule);
if (scheduleTimeZone) {
options.tzid = scheduleTimeZone;
}
const dtstart = moment.utc().toDate();
const rule = new RRule({ ...options, dtstart, count: 1 });
const dates = rule.all();
let date = dates[0];
if (scheduleTimeZone amp;amp; moment(date).tz(scheduleTimeZone).isDST()) {
date = moment(date).subtract(1, 'hour').toDate();
}
return date;
};
export default getNextRunAt;
Это работает для некоторых дат/времен/часовых поясов:
Date.now = jest.fn(() => new Date('2021-03-02 10:24:27.000000Z').getTime());
const nextDate = getNextRunAt({
schedule: 'RRULE:INTERVAL=1;BYDAY=MO,TU,WE,TH,SA,FR,SU;BYMINUTE=0;BYHOUR=9;BYSECOND=0;FREQ=DAILY',
scheduleTimeZone: 'America/Los_Angeles',
});
expect(nextDate).toEqual(new Date('2021-03-02 17:00:00.000000Z'));
// WORKS
Но не для других:
Date.now = jest.fn(() => new Date('2021-03-02 10:24:27.000000Z').getTime());
const nextDate = getNextRunAt({
schedule: 'RRULE:INTERVAL=1;BYDAY=MO,TU,WE,TH,SA,FR,SU;BYMINUTE=0;BYHOUR=9;BYSECOND=0;FREQ=DAILY',
scheduleTimeZone: 'America/Los_Angeles',
});
expect(nextDate).toEqual(new Date('2021-03-02 17:00:00.000000Z'));
// DOES NOT WORK
Expected: 2021-03-02T17:00:00.000Z
Received: 2021-03-03T17:00:00.000Z
Комментарии:
1.
Date.now = jest.fn(...);
не похоже на хорошую идею. :-/
Ответ №1:
Я думаю, что последний случай не подходит для одного дня. Но для других решение может выглядеть так:
const getNextRunAt = ({ schedule, scheduleTimeZone }: { schedule?: string | null; scheduleTimeZone?: string | null }): Date | undefined => {
if (!schedule) {
return undefined;
}
const options = RRule.parseString(schedule);
const dtstart = moment();
const rule = new RRule({
...options,
dtstart: dtstart.toDate(),
count: 1,
});
const dates = rule.all();
const date = dates[0];
let mDate = moment.tz(date, scheduleTimeZone);
const offset = Math.abs(mDate.utcOffset()) > 16 ? mDate.utcOffset() / 60 : mDate.utcOffset();
if (!mDate.isDST()) {
mDate = mDate.add(1, 'hours');
}
mDate.add(-offset, 'hour');
return mDate.toDate();};
Комментарии:
1. В moment.js Метод utcOffset возвращает смещение в минутах, поэтому
Math.abs(mDate.utcOffset()) > 16
в основном оно будет истинным, так как смещения обычно кратны 30 минутам, поэтому только места, наблюдающие 0 (GMT/UTC), будут ложными.