Как получить следующее появление rrule для разных часовых поясов

#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), будут ложными.