From 3e3263b7b7eae6aebbe92c945bf9aa2553ee0255 Mon Sep 17 00:00:00 2001 From: Ilya Vinogradov <48182348+williamvinogradov@users.noreply.github.com> Date: Tue, 12 Sep 2023 14:25:10 +0400 Subject: [PATCH] T1185479: Scheduler - Incorrect date shown for recurrence in the New Zealand timezone (#25531) Co-authored-by: Ilya Vinogradov --- .../js/__internal/scheduler/m_recurrence.ts | 39 ++++++++++++++++--- packages/devextreme/js/core/utils/date.js | 12 +++++- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/packages/devextreme/js/__internal/scheduler/m_recurrence.ts b/packages/devextreme/js/__internal/scheduler/m_recurrence.ts index c226d5229ad7..06149b9b0a26 100644 --- a/packages/devextreme/js/__internal/scheduler/m_recurrence.ts +++ b/packages/devextreme/js/__internal/scheduler/m_recurrence.ts @@ -17,6 +17,16 @@ const loggedWarnings: any = []; const MS_IN_HOUR = 1000 * 60 * 60; const MS_IN_DAY = MS_IN_HOUR * 24; +const RRULE_BROKEN_TIMEZONES = [ + 'Etc/GMT-13', + 'MIT', + 'Pacific/Apia', + 'Pacific/Enderbury', + 'Pacific/Tongatapu', + 'Etc/GMT-14', + 'Pacific/Kiritimati', +]; + let recurrence: RecurrenceProcessor | null = null; export function getRecurrenceProcessor() { @@ -92,13 +102,8 @@ class RecurrenceProcessor { } _convertRruleResult(rruleIntervalParams, options, rruleDate) { - const localTimezoneOffset = timeZoneUtils.getClientTimezoneOffset(rruleDate); - // NOTE: Workaround for the RRule bug with timezones greater than GMT+12 (e.g. Apia Standard Time GMT+13) - // GitHub issue: https://github.com/jakubroztocil/rrule/issues/555 - const additionalWorkaroundOffsetForRrule = localTimezoneOffset / MS_IN_HOUR <= -13 ? -MS_IN_DAY : 0; const convertedBackDate = timeZoneUtils.setOffsetsToDate(rruleDate, [ - localTimezoneOffset, - additionalWorkaroundOffsetForRrule, + ...this._getLocalMachineOffset(rruleDate), -options.appointmentTimezoneOffset, rruleIntervalParams.startIntervalDateDSTShift, ]); @@ -114,6 +119,28 @@ class RecurrenceProcessor { return resultDate; } + _getLocalMachineOffset(rruleDate) { + const machineTimezoneOffset = timeZoneUtils.getClientTimezoneOffset(rruleDate); + const machineTimezoneName = dateUtils.getMachineTimezoneName(); + const result = [machineTimezoneOffset]; + + // NOTE: Workaround for the RRule bug with timezones greater than GMT+12 (e.g. Apia Standard Time GMT+13) + // GitHub issue: https://github.com/jakubroztocil/rrule/issues/555 + // UPD: 05.09.2023 - The issue still hasn't been fixed in the Rule package. + // RRule returns results that are one day greater than expected. + // Therefore, for broken from RRule point of view timezones, we subtract one day from the result. + const brokenTimezonesOffset = -13; + const isTimezoneOffsetInBrokenRange = machineTimezoneOffset / MS_IN_HOUR <= brokenTimezonesOffset; + const isTimezoneNameInBrokenNames = !machineTimezoneName + || RRULE_BROKEN_TIMEZONES.some((timezone) => machineTimezoneName.includes(timezone)); + + if (isTimezoneOffsetInBrokenRange && isTimezoneNameInBrokenNames) { + result.push(-MS_IN_DAY); + } + + return result; + } + hasRecurrence(options) { return !!this.generateDates(options).length; } diff --git a/packages/devextreme/js/core/utils/date.js b/packages/devextreme/js/core/utils/date.js index 9988f56c0bff..0360cfa222ed 100644 --- a/packages/devextreme/js/core/utils/date.js +++ b/packages/devextreme/js/core/utils/date.js @@ -1,3 +1,4 @@ +/* globals Intl */ import { isObject, isString, isDate, isDefined, isNumeric } from './type'; import { adjust } from './math'; import { each } from './iterator'; @@ -680,6 +681,13 @@ const createDateWithFullYear = function(year) { return result; }; +const getMachineTimezoneName = () => { + const hasIntl = typeof Intl !== 'undefined'; + return hasIntl + ? Intl.DateTimeFormat().resolvedOptions().timeZone + : null; +}; + const dateUtils = { dateUnitIntervals: dateUnitIntervals, @@ -740,7 +748,9 @@ const dateUtils = { getDatesOfInterval: getDatesOfInterval, - createDateWithFullYear: createDateWithFullYear + createDateWithFullYear: createDateWithFullYear, + + getMachineTimezoneName: getMachineTimezoneName, }; dateUtils.sameView = function(view, date1, date2) {