Rrule: Переход на летнее время не обрабатывается правильно, несмотря на «Поддержка часового пояса».

Созданный на 20 нояб. 2018  ·  21Комментарии  ·  Источник: jakubroztocil/rrule

Я сравнивал поведение этой библиотеки (v2.5.6) во внешнем интерфейсе с эквивалентной библиотекой PHP (v2.3.3) во внутреннем. Существует определенное несоответствие тому, как обрабатывается переход на летнее время, и я считаю, что версия PHP справляется с этим лучше.

Пример кода:

В часовом поясе America/Denver переход на летнее время происходит в воскресенье, 4 ноября 2018 г. (переход с GMT-6 на GMT-7). Итак, давайте настроим повторяющуюся серию, начинающуюся в 13:00, повторяющуюся каждый понедельник, среду, четверг, начиная с 1 ноября, то есть до переключения времени:

RRule.fromString(
  "DTSTART;TZID=America/Denver:20181101T190000;\n"
  + "RRULE:FREQ=WEEKLY;BYDAY=MO,WE,TH;INTERVAL=1;COUNT=7"
).all()

В этом случае я ожидаю, что все повторения начнутся в 13:00, как показано ниже. (Согласно документам, я использую библиотеку Luxon. Я не видел ничего, говорящего о том, что мне нужно как-то ее инициализировать, поэтому я предполагаю, что она «установлена» правильно через пряжу.)

Ожидаемый результат (все с 13:00:00):

(7) [
    Thu Nov 01 2018 13:00:00 GMT-0600 (Mountain Daylight Time),
    Mon Nov 05 2018 13:00:00 GMT-0700 (Mountain Standard Time),
    Wed Nov 07 2018 13:00:00 GMT-0700 (Mountain Standard Time),
    Thu Nov 08 2018 13:00:00 GMT-0700 (Mountain Standard Time),
    Mon Nov 12 2018 13:00:00 GMT-0700 (Mountain Standard Time),
    Wed Nov 14 2018 13:00:00 GMT-0700 (Mountain Standard Time),
    Thu Nov 15 2018 13:00:00 GMT-0700 (Mountain Standard Time)
]

Фактический результат:

(7) [
    Thu Nov 01 2018 13:00:00 GMT-0600 (Mountain Daylight Time),
    Mon Nov 05 2018 12:00:00 GMT-0700 (Mountain Standard Time), <-- Should be 13:00:00
    Wed Nov 07 2018 12:00:00 GMT-0700 (Mountain Standard Time), <-- same
    Thu Nov 08 2018 12:00:00 GMT-0700 (Mountain Standard Time), <-- same
    Mon Nov 12 2018 12:00:00 GMT-0700 (Mountain Standard Time), <-- same
    Wed Nov 14 2018 12:00:00 GMT-0700 (Mountain Standard Time), <-- same
    Thu Nov 15 2018 12:00:00 GMT-0700 (Mountain Standard Time)  <-- same
]

Когда я настроил аналогичную ситуацию в библиотеке PHP, результат был таким, как и ожидалось выше, со всеми экземплярами, начинающимися в 13:00:00 в часовом поясе Америки/Денвера, а не в UTC .

Другие детали:

  • Версия 2.5.6
  • Mac OS X 10.13.6
  • Хром 70
  • Мое текущее местное время — MST (Америка/Денвер, GMT-7).

Изменить: исправить опечатку

Все 21 Комментарий

Вы получаете какие-либо предупреждения консоли? например:

'Using TZID without Luxon available is unsupported. Returned times are in UTC, not the requested time zone'

Luxon находится в optionalDependencies , поэтому он будет импортирован, если он находится в вашем node_modules , но в противном случае нет.

Когда я запускаю ваш код в наборе тестов rrule, я получаю:

      [
        [Date: 2018-11-01T19:00:00.000Z]
        [Date: 2018-11-05T19:00:00.000Z]
        [Date: 2018-11-07T19:00:00.000Z]
        [Date: 2018-11-08T19:00:00.000Z]
        [Date: 2018-11-12T19:00:00.000Z]
        [Date: 2018-11-14T19:00:00.000Z]
        [Date: 2018-11-15T19:00:00.000Z]
      ]

именно этого я и ожидал, поскольку время, которое вы запросили, - 19:00 по часовому поясу Денвера. (Поскольку это JavaScript, а не PHP, мы не можем предоставить вам объекты Date в часовом поясе, отличном от часового пояса вашего локального компьютера или UTC, поэтому UTC — это то, что эта библиотека использует для всех времен.)

Тот факт, что вы видите локальные (MST/MDT) даты, а не даты UTC, меня немного сбивает с толку. Насколько мне известно, эта библиотека в настоящее время настроена на возврат только дат в формате UTC, хотя ранее вместо этого она возвращала локальные даты.

Вы получаете какие-либо предупреждения консоли? например:

«Использование TZID без Luxon не поддерживается. Возвращаемое время указано в формате UTC, а не в запрошенном часовом поясе.

Предупреждений консоли нет. Похоже, Luxon предоставляется правильно.

Что касается часовых поясов, я получаю MST/DST в результате запуска моего кода непосредственно в командной строке консоли Chrome devtools.

Шаги: Откройте Chrome --> Перейдите на страницу с предоставленными RRule и Luxon --> Cmd+Opt+I --> вкладка "Консоль" --> скопируйте и вставьте приведенный выше фрагмент в командную строку внизу --> Enter

эта библиотека в настоящее время настроена на возврат только дат в формате UTC, хотя ранее вместо этого она возвращала локальные даты.

Вы знаете, в какой версии это изменилось? Почему это изменение было бы сделано? Возврат только дат в формате UTC фактически приводит к несоответствию летнего времени! Событие, которое всегда происходит в 13:00 UTC, в значительной степени гарантирует, что событие начнется в другое время до/после перехода на летнее время. Я думаю, что это изменение должно быть отменено, или же должен быть предоставлен другой альтернативный метод, позволяющий учитывать переход на летнее время. UTC чрезвычайно полезен, но не всегда является правильным подходом во всех ситуациях. При работе с повторяющимися событиями мы ДОЛЖНЫ учитывать конкретный часовой пояс, в котором происходит событие, потому что летнее время имеет значение.

Действительно, я получаю другое поведение от Chrome, чем от Node.

Я бы сказал, что не понимаю, почему ваш данный «ожидаемый» результат - это то, что вы действительно ожидаете. Эти времена кажутся неправильными.

Что _было бы_ правильным, так это время, если бы вы добавляли смещение TZ для каждого времени к заданному местному времени. Тогда все время будет 19:00. Вот почему RRule имеет дело с UTC, и почему его использование UTC не имеет ничего общего с этой конкретной ошибкой.

Действительно, если вы сделаете:

const dates = RRule.fromString(
  "DTSTART;TZID=America/Denver:20181101T190000;\n"
  + "RRULE:FREQ=WEEKLY;BYDAY=MO,WE,TH;INTERVAL=1;COUNT=7"
).all()
dates.map((date) => date.toISOString())

вы увидите результирующие строки ISO с неизменно правильными датами и временем.

Более того, похоже, что Chrome вообще не поддерживает возврат дат в формате UTC:

> new Date(Date.UTC(2016, 10, 5))
Fri Nov 04 2016 17:00:00 GMT-0700 (Pacific Daylight Time)
// ^ I asked for a date in UTC, not in PDT!

RRule использует Luxon для генерации правильного местного времени для _всех часовых поясов мира_, а не только для вашего местного часового пояса. Вот почему его использование UTC имеет решающее значение: если код часового пояса возвращает «правильные» даты для вашего местного часового пояса, но вам нужно сгенерировать даты в другом часовом поясе (указанном с параметром TZID ), вы получите дико неверные результаты. Мы используем «даты UTC» не потому, что вычисляем летнее время в UTC (это не так, вы можете просмотреть набор тестов и увидеть множество примеров правильных расчетов летнего времени), а потому, что часовые пояса в спецификации JavaScript ужасно не работает, а UTC наиболее близок к «нейтральной» концепции даты из доступных. Мне жаль, что с ней сложно работать, и что ее использование в этой библиотеке не может быть более понятным (хотя я открыт для предложений).

Что _is_ касается, так это проблема совместимости браузера, о которой вы говорите. Я бы попросил вас теперь попробовать свой собственный пример кода в работающей консоли Node или, если уж на то пошло, в Firefox. Вы увидите очень разные (и правильные) результаты.

Итак, настоящая проблема здесь в том, что результаты в Chrome сбивают с толку. Я думаю, что это стоит обсудить, хотя мне не сразу ясно, как именно это сделать. Я _могу_ сказать вам, что обе среды генерируют одни и те же строки ISO и временные метки.

@davidgoli Вы правы, Firefox показывает другой результат в консоли при запуске моего исходного кода. Тем не менее, результат в конечном итоге является одним и тем же временем, просто результат выражается во времени UTC в Firefox, тогда как в Chrome он выражается во времени America/Denver (или, возможно, в любом другом часовом поясе).

Я бы сказал, что не понимаю, почему ваш данный «ожидаемый» результат - это то, что вы действительно ожидаете. Эти времена кажутся неправильными.

Что было бы правильно, так это время, если бы вы добавляли смещение TZ для каждого времени к заданному местному времени. Тогда все время будет 19:00. Вот почему RRule имеет дело с UTC, и почему его использование UTC не имеет ничего общего с этой конкретной ошибкой.

Позвольте мне попытаться объяснить, почему мы НЕ хотим 19:00 UTC для каждого повторяющегося события.

Допустим, пользователь хочет запланировать повторяющееся событие в определенном месте где-то в часовом поясе America/Denver каждую среду в 13:00. Проще говоря, это означает, что независимо от того, какое сейчас время года, пользователь ожидает, что одно и то же событие будет происходить каждую среду в 13:00, когда в этом месте среда в 13:00 . Не в 12, не в 14.00. Всегда в 13:00 по американскому/денверскому времени.

Однако rrule разрешает повторения в соответствии со временем UTC, даже если передается часовой пояс. Делая это, он фактически говорит: «О, 13:00 Америка/Денвер оценивается как 19:00. Следовательно, 19:00 должен ВСЕГДА ссылаться на 13:00 в Америке/Денвере круглый год». Но это просто неправда. До 4 ноября 2018 г. это верно, но после 4 ноября Америка/Денвер переходит на летнее время, после чего 19:00 UTC теперь соответствует 12:00 по американскому/денверскому времени . Таким образом, нашему пользователю неправильно сообщают, что событие происходит в 12 часов дня, а не в час дня.

Другими словами, 1pm America/Denver может быть либо 19:00, либо 20:00 UTC, в зависимости от того, используем ли мы летнее время или нет, и невозможно использовать rrule для получения правильных повторений, потому что он хочет сказать, что 19: 00 - правильное время круглый год.

Я чувствую, что rrule верит в миф, в который верит большинство программистов, который заключается в том, что UTC всегда является правильным решением при работе с датами и временем. Но есть определенные случаи, когда это не лучшее решение, и этот случай (повторение будущих событий в определенном месте) является одним из них.


Редактировать: Теоретически я согласен с тем, что rrule возвращает время в UTC, но это время должно быть правильным для данного часового пояса, когда часовой пояс передается. Это означает, что мы должны увидеть, как 19:00 UTC переключится на 20:00 UTC в какую-то точку в наборе результатов, например, обсуждаемый здесь.

Позвольте мне вернуться сюда - я думаю, что это проблема с документацией.

RRule _always_ возвращает даты JS "UTC". Однако это не означает, что эти даты предназначены для представления времени UTC. Скорее, из-за ограничений единственной альтернативы — локальных дат — было принято решение, что _только_ даты JS с нулевым смещением часового пояса можно использовать для математики дат, которую выполняет эта библиотека. Это имеет вводящий в заблуждение побочный эффект, заключающийся в том, что кажется, что это время на самом деле представляет время в формате UTC, хотя на самом деле оно предназначено для интерпретации в часовом поясе, представленном параметром TZID . Так, например, в Firefox предполагаемое поведение заключается в том, что библиотека возвращает вам дату, представляющую 19:00, и, поскольку вы запросили, чтобы это время было денверским, оно будет правильным для денверского времени. Вы должны игнорировать тот факт, что эта дата сообщает, что это дата UTC, потому что это единственный тип даты, который полезен в JavaScript. Вместо этого вы должны использовать знание того, что это правило имеет часовой пояс Денвера, чтобы интерпретировать 19:00 как местное время в Денвере.

Это более ясно, когда вы используете часовые пояса за пределами зоны вашего локального компьютера. Например, если вместо этого вы настроили RRule с именем зоны America/New_York , вы увидите, что даты возвращаются как 17:00, потому что это местное время в часовом поясе вашего компьютера (Денвер), когда сейчас 19 часов. :00 в запрошенном часовом поясе (Нью-Йорк).

В JavaScript нет способа «привязать» дату к определенному часовому поясу, поэтому мы всегда представляем правильную дату и время, несмотря ни на что, и вы можете отбросить тот факт, что дата сообщает себя как UTC. Когда часовые пояса используются в RRule, это фактически местное время. Вот почему Firefox показывает правильное местное время как 19:00 (хотя эта дата указывается как UTC, на самом деле это время Денвера).

Это вообще что-то проясняет? Я понимаю, что это сбивает с толку и не интуитивно понятно, но, надеюсь, это достаточно просто, чтобы понять, что, как только это понято, становится легко рассуждать. Мы определенно должны документировать это лучше.

Одна вещь, которую я хотел бы изучить, — это возможность использования Luxon для изменения зон полученных дат в местный часовой пояс, чтобы это, по крайней мере, соответствовало вашим ожиданиям. Я думаю, что это не должно быть слишком сложно и должно помочь прояснить недоразумения, подобные этому. Проблема Chrome действительно нова для меня, и это плохие новости.

Однако это было бы серьезным изменением, поэтому я хотел бы предложить возможность получить отзывы сообщества перед отправкой.

@shorlbeck Этот diff, я полагаю, реализует поведение, которое вы ожидаете увидеть:

diff --git a/src/datewithzone.ts b/src/datewithzone.ts
index 8ae3ed0..d9b917c 100644
--- a/src/datewithzone.ts
+++ b/src/datewithzone.ts
@@ -38,7 +38,10 @@ export class DateWithZone {

       const rezoned = datetime.setZone(this.tzid!, { keepLocalTime: true })

-      return rezoned.toJSDate()
+      return rezoned
+        .toUTC()
+        .setZone('local', { keepLocalTime: true })
+        .toJSDate()
     } catch (e) {
       if (e instanceof TypeError) {
         console.error('Using TZID without Luxon available is unsupported. Returned times are in UTC, not the requested time zone')
diff --git a/test/rrule.test.ts b/test/rrule.test.ts
index 7774b8a..a794e02 100644
--- a/test/rrule.test.ts
+++ b/test/rrule.test.ts
@@ -3804,4 +3804,17 @@ describe('RRule', function () {
     expect(() => rule.between(invalidDate, validDate)).to.throw('Invalid date passed in to RRule.between')
     expect(() => rule.between(validDate, invalidDate)).to.throw('Invalid date passed in to RRule.between')
   })
+
+  it('#300', () => {
+    const rule = RRule.fromString(
+      "DTSTART;TZID=America/Denver:20181101T190000;\n"
+      + "RRULE:FREQ=WEEKLY;BYDAY=MO,WE,TH;INTERVAL=1;COUNT=3"
+    )
+
+    expect(rule.all()).to.deep.equal([
+      DateTime.utc(2018, 11, 2, 1, 0, 0).toJSDate(),
+      DateTime.utc(2018, 11, 6, 2, 0, 0).toJSDate(),
+      DateTime.utc(2018, 11, 8, 2, 0, 0).toJSDate(),
+    ])
+  })
 })

Однако я не совсем уверен, что это правильный путь для этой библиотеки. учитывая общий подход библиотеки к возврату «плавающего» времени, которое всегда предназначено для интерпретации в локальном часовом поясе клиента (хотя и со смещением 0). Подходящий геттер для дат, возвращаемых rrule , — это геттер getUTC* , поэтому, например, если вы используете getUTCHours() для каждой даты в исходном результате, вы получите правильные часы, даже в Chrome.

Я склоняюсь к тому, что правильным исправлением здесь является не изменение поведения этой библиотеки, а более четкое документирование ее несколько идиосинкразического использования псевдо-"UTC" (фактически "плавающего") времени. Я открыт для убеждения по этому поводу, однако.

Я обновил README некоторыми разъяснениями и инструкциями для вашего варианта использования. Однако я заинтересован в продолжении этого обсуждения и открыт для возможного изменения этого поведения в будущей версии библиотеки.

Спасибо за ответ и подробное объяснение.

К сожалению, я никогда не был более сбит с толку.

Работа с датами и временем достаточно сложна, когда эти даты либо на самом деле UTC, либо на самом деле местное время, но необходимость также помнить, что иногда время UTC на самом деле не является временем UTC, - это больше, чем может обработать мой мозг. Сегодня я провел весь день, пытаясь понять, что вы написали, но, в конце концов, это настолько противоречит здравому смыслу, что я просто не могу понять это.

Всякий раз, когда я смотрю на код и вижу дату «UTC», я не могу вспомнить, действительно ли это UTC или просто псевдо-UTC , особенно когда вы добавляете туда проблему Chrome. Это делает для меня практически невозможным кодирование. Это разрушает мое доверие к читабельности моего кода.

Тогда вот мой вопрос... Возвращаясь к моему исходному примеру... В истинном UTC 2018-11-01 19:00:00 относится к 13:00 по американскому/денверскому времени (13:00). И в моем примере 13:00 Америка/Денвер — правильное время моего мероприятия. Но если UTC не является истинным UTC, означает ли это, что я должен передавать new Date(Date.UTC(2018, 10, 1, 13, 0, 0)) или new Date(Date.UTC(2018, 10, 1, 19, 0, 0)) при использовании параметра tzid ? (Разница в 13:00 против 19:00)

Это псевдо-UTC входит и выходит? Или истинное UTC входит, но псевдо-UTC выходит? И как я могу когда-либо надежно запомнить это??

Ваше правило указано как:

DTSTART;TZID=America/Denver:20181101T190000
RRULE:FREQ=WEEKLY;BYDAY=MO,WE,TH;INTERVAL=1;COUNT=7

Но, согласно RRULE RFC5545, это указывает на rrule в часовом поясе Денвера с местным временем 19:00: https://tools.ietf.org/html/rfc5545#section -3.3.5

For example, the following represents 2:00 A.M. in New York on January 19, 1998:

       TZID=America/New_York:19980119T020000

Если вы хотите DTSTART в 13:00 по местному денверскому времени, вы должны указать его как 13:00 в своем rrule. Библиотека должна полностью соответствовать спецификации в этом отношении. (Ну, за исключением незначительного факта, что он вернет 13:00 UTC...)

Кроме того, обязательно прочитайте раздел о «плавающем времени»:

FORM #1: DATE WITH LOCAL TIME

      The date with local time form is simply a DATE-TIME value that
      does not contain the UTC designator nor does it reference a time
      zone.  For example, the following represents January 18, 1998, at
      11 PM:

       19980118T230000

      DATE-TIME values of this type are said to be "floating" and are
      not bound to any time zone in particular.  They are used to
      represent the same hour, minute, and second value regardless of
      which time zone is currently being observed.  For example, an
      event can be defined that indicates that an individual will be
      busy from 11:00 AM to 1:00 PM every day, no matter which time zone
      the person is in.  In these cases, a local time can be specified.

JavaScript, к сожалению, не предлагает реализацию "плавающего времени". Самый близкий вариант, который у нас есть для «чистого времени без часового пояса», - это UTC, следовательно, «псевдо-UTC». (Для справки, rrule также поддерживает плавающее время, не указанное ни с TZID, ни с обозначением UTC Z .)

Переход на UTC был полностью начат с этой фиксации: https://github.com/jakubroztocil/rrule/commit/850ed075175eb1acfcbd7b2cddf0606f2b2206f7

Если вы проверите предыдущую фиксацию и выберете добавленный тест из # 850ed075, вы увидите, что он не работает. На самом деле сотни тестов провалились, если только весь набор не запускался в UTC. Это связано с тем, что для расчета будущих дат, лежащих за границами перехода на летнее время, нам приходилось создавать экземпляры дат, когда текущие часы работали в режиме летнего времени, а затем вычислять математические расчеты, которые помещали нас в стандартное время, что приводило к неправильному смещению на 1 час. Текущая реализация всегда дает правильное местное время. Теперь весь набор тестов будет фактически проходить в любой локальной среде, с той оговоркой, что для получения правильной даты/часа вы должны использовать методы getUTCDate() / getUTCHour() .

Я изучаю возможность изменить это в будущей версии, но это изменение не является тривиальным без использования необходимой сторонней библиотеки, такой как Luxon.

В следующей версии я хотел бы изменить поведение, чтобы возвращать даты, которые всегда верны в вашем местном часовом поясе, например:

  • DTSTART;TZID=America/Denver:20181101T130000 вернет вам дату 1300-0700 (стандартное время), если вы находитесь в Денвере, или 2000Z , если вы находитесь в UTC.
  • DTSTART=20181101T130000 даст вам 1300-0700 , если вы находитесь в Денвере, и 1300Z , если вы находитесь в UTC.
  • DTSTART=20181101T130000Z даст вам 0600-0700 , если вы находитесь в Денвере, и 1300Z , если вы находитесь в UTC.

Это то поведение, которое вы ожидаете?

Одна большая проблема заключается в том, что данная локальная среда JS может представлять даты только в одном часовом поясе. Таким образом, ни new Date(...) , ни new Date(Date.UTC(...)) не подходят, если вы хотите представить дату и время в определенном часовом поясе (который может отличаться от вашего текущего часового пояса).

Итак, какое поведение должно быть с:

new RRule({
  dtstart: new Date(2018, 10, 1, 10, 0, 0),
  tzid: 'America/Denver'
})

если локальная машина находится в America/Los_Angeles ? Вы должны были бы хотя бы немного знать о 1-часовой разнице между временем, которое вы создаете, и часовым поясом, в котором вы хотите это время. Таким образом, предполагается, что он представляет 11:00 (время Денвера, запрошенная зона) или 10:00 ( время Лос-Анджелеса, исходная зона)? Думаю, вы могли бы обосновать и то, и другое.

Педантично, объект Date отличается как от строки даты ISO, так и от строки DTSTART тем, что он представляет отметку времени в миллисекундах UTC, которая не имеет понятия часового пояса или смещения. Поэтому я думаю, что имеет смысл представить отметку времени желаемого времени _в правильной зоне_, что означает, что если вы находитесь в Лос-Анджелесе и хотите представить время в Денвере, вам нужно передать объект Date, инициализированный относительным применено смещение (поэтому приведенный выше пример будет представлять 11 утра в Денвере). Однако это сбивает с толку, поскольку, если вы затем отправитесь в Нью-Йорк и захотите представить то же время, вам придется написать new Date(2018, 10, 1, 13, 0, 0) !

Преимущество отказа от всей концепции местных дат в том, что вам не нужно иметь с этим дело; представленные дату и время можно рассматривать как плавающее время, которое можно спроецировать в любой часовой пояс без какого-либо преобразования.

Это усложняется, конечно, поведением таких движков, как Chrome.

Для ваших целей вы, вероятно, можете получить нужные значения, выполнив следующие действия:

rule.all().map(d => new Date(
    d.getUTCFullYear(),
    d.getUTCMonth(),
    d.getUTCDate(),
    d.getUTCHours(),
    d.getUTCMinutes(),
    d.getUTCMilliseconds()
  ))

В настоящее время я пытаюсь решить, принадлежит ли это библиотеке...

Спасибо! Ваш ответ действительно многое прояснил для меня. Кажется, я устал и выгорел вчера, когда пытался все понять. Теперь я вижу, что происходит, и вы правы, я указывал неверное время при использовании параметра tzid .

Для ваших целей вы, вероятно, можете получить нужные значения, выполнив следующие действия:

rule.all().map(d => new Date(
    d.getUTCFullYear(),
    d.getUTCMonth(),
    d.getUTCDate(),
    d.getUTCHours(),
    d.getUTCMinutes(),
    d.getUTCMilliseconds()
  ))

В настоящее время я пытаюсь решить, принадлежит ли это библиотеке...

Это сработало для меня! Большое спасибо. Было бы неплохо, если бы был способ настроить то, что возвращают rule.all() и rule.between() , чтобы мы не вносили еще одно критическое изменение, но по-прежнему могли получить вышеописанное поведение изначально. Не знаете, как это сделать, если вы также считаете, что мне нравится использовать rrulestr() или RRule.fromString() , но, возможно, дополнительный аргумент или что-то еще, чтобы указать, какой тип даты возвращается?


ОБНОВЛЕНИЕ: d.getUTCMilliseconds() выше следует заменить на d.getUTCSeconds()

Итак, ваше решение работает, когда часовой пояс моей системы совпадает с запрошенным часовым поясом, но у меня ужасное время, пытаясь понять, как заставить RRule возвращать даты в запрошенном часовом поясе , когда он отличается от системного часового пояса. Он всегда возвращает даты, представленные в часовом поясе системы, а не в запрошенном часовом поясе. Пример:

  1. Установите системный часовой пояс вашей ОС на America/Denver
  2. В консоли Chrome devtools введите:
RRule.fromString(
    "DTSTART;TZID=America/Denver:20181101T130000;\n"
    + "RRULE:FREQ=WEEKLY;BYDAY=MO,WE,TH;INTERVAL=1;COUNT=7"
).all().map(d => new Date(
    d.getUTCFullYear(),
    d.getUTCMonth(),
    d.getUTCDate(),
    d.getUTCHours(),
    d.getUTCMinutes(),
    d.getUTCSeconds()
));

Результат правильный (для того, что я ожидаю/нужно):

(7) [Thu Nov 01 2018 13:00:00 GMT-0600 (Mountain Daylight Time), Mon Nov 05 2018 13:00:00 GMT-0700 (Mountain Standard Time), Wed Nov 07 2018 13:00:00 GMT-0700 (Mountain Standard Time), Thu Nov 08 2018 13:00:00 GMT-0700 (Mountain Standard Time), Mon Nov 12 2018 13:00:00 GMT-0700 (Mountain Standard Time), Wed Nov 14 2018 13:00:00 GMT-0700 (Mountain Standard Time), Thu Nov 15 2018 13:00:00 GMT-0700 (Mountain Standard Time)]
  1. Измените системный часовой пояс ОС на America/New_York
  2. Запустите ту же команду в консоли Chrome devtools.

Результат отличается, потому что он представлен в системном часовом поясе:

(7) [Thu Nov 01 2018 15:00:00 GMT-0400 (Eastern Daylight Time), Mon Nov 05 2018 15:00:00 GMT-0500 (Eastern Standard Time), Wed Nov 07 2018 15:00:00 GMT-0500 (Eastern Standard Time), Thu Nov 08 2018 15:00:00 GMT-0500 (Eastern Standard Time), Mon Nov 12 2018 15:00:00 GMT-0500 (Eastern Standard Time), Wed Nov 14 2018 15:00:00 GMT-0500 (Eastern Standard Time), Thu Nov 15 2018 15:00:00 GMT-0500 (Eastern Standard Time)]

Но я хочу, чтобы результат представлял время America/Denver , а не часовой пояс моей системы или настоящий UTC. Как я могу изменить его на America/Denver ? Я не знаком с Luxon, поэтому вместо этого я попробовал moment() :

RRule.fromString(
    "DTSTART;TZID=America/Denver:20181101T130000;\n"
    + "RRULE:FREQ=WEEKLY;BYDAY=MO,WE,TH;INTERVAL=1;COUNT=7"
).all().map(d => moment(new Date(
    d.getUTCFullYear(),
    d.getUTCMonth(),
    d.getUTCDate(),
    d.getUTCHours(),
    d.getUTCMinutes(),
    d.getUTCSeconds()
)).tz('America/Denver').toDate());

Но это возвращает то же самое в нью-йоркском времени, потому что toDate() мгновенно возвращает его в системный часовой пояс (по крайней мере, в Chrome).

@shorlbeck Если вы заметили, что результат в MDT и EDT одинаков в UTC, поэтому мой вопрос: вы уверены, что хотите использовать параметр TZID , а не просто плавающее время? Я рекомендую плавающее время (в основном то же самое, но с DSTART:<datetime> вместо DSTART;TZID=<timezone>:<datetime> ), если вы хотите, чтобы оно всегда генерировало 13:00 независимо от системного часового пояса.

13:00 псевдо-UTC, то есть. Если вы хотите обойти проблему Chrome, вам все равно придется преобразовать ее в локальную дату, используя метод, описанный в моем предыдущем комментарии.

@davidgoli Ого , ты прав. Я не понимал, что на самом деле мне не нужен параметр tzid после того, как я понял, как на самом деле работает плавающее время/псевдо-UTC. А это значит, что мне не нужно решение .map() выше. Ваш комментарий указал мне правильное направление. Большое спасибо.

Я бы солгал, если бы сказал, что это не самая запутанная вещь, над которой я работал за долгое время, но я просто рад, что сейчас двигаюсь в правильном направлении.

Рассматривали ли вы возможность использования явного представления того, что вам нужно (плавающие даты без часового пояса)? Использование нативной даты JS с часовым поясом UTC и утверждение, что это UTC в документации, чрезвычайно сбивает с толку, потому что это работает до тех пор, пока вы не столкнетесь с ошибками DST.

Я бы предложил объект, назовем его "RRuleDate", который принимает конструктор (year, month, day, hours?, minutes?, seconds?, milliseconds?) и имеет метод .toDate() .

Этот RRuleDate — это то, что должны возвращать before() , after() , between() и all() , и единственное, что before() , after() , between() и опция dtstart должны приниматься в качестве параметров.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

agordeev picture agordeev  ·  16Комментарии

espen picture espen  ·  11Комментарии

jimmywarting picture jimmywarting  ·  9Комментарии

elazar picture elazar  ·  18Комментарии

anthwinter picture anthwinter  ·  11Комментарии