From 3441eedff4b4c51eec70d6b0995454396a0d4256 Mon Sep 17 00:00:00 2001 From: Ludwig Date: Mon, 21 Apr 2025 16:31:20 -0600 Subject: [PATCH] ERA-11347: Fix daylight saving time issue with new date pickers --- src/DatePicker/index.js | 15 +++-- src/DatePicker/index.test.js | 30 ++++++++- src/DatePicker/utils/index.js | 2 +- src/DateRangeSelector/index.js | 20 +++--- src/DateTimePicker/index.js | 8 +-- src/DateTimePicker/utils/index.js | 5 ++ src/PatrolDetailView/PlanSection/index.js | 64 +++++++++---------- .../SchemaForm/fields/DateTime/index.js | 19 +++--- .../SchemaForm/fields/DateTime/index.test.js | 6 +- src/ReportManager/DetailsSection/index.js | 24 +++---- src/SchemaFields/index.js | 27 ++++---- src/utils/datetime.js | 10 --- 12 files changed, 126 insertions(+), 104 deletions(-) diff --git a/src/DatePicker/index.js b/src/DatePicker/index.js index 6b4c4ca1a..9cb76f1f9 100644 --- a/src/DatePicker/index.js +++ b/src/DatePicker/index.js @@ -1,5 +1,5 @@ import React, { memo, useEffect, useImperativeHandle, useRef, useState } from 'react'; -import { parseISO } from 'date-fns'; +import { lastDayOfMonth, parseISO } from 'date-fns'; import { useTranslation } from 'react-i18next'; import { ReactComponent as CalendarIcon } from '../common/images/icons/calendar.svg'; @@ -13,6 +13,7 @@ import { isMonthInputComplete, isSecondDayDigitPossible, isSecondMonthDigitPossible, + isValidDate, isValidDayInput, isValidMonthInput, isValidYearInput, @@ -231,6 +232,12 @@ const DatePicker = ({ }; const onDayInputKeyDown = (event) => { + // If the year and month inputs are complete, we can calculate the last valid day of the current month to set when + // the user decreases or increases the day with the arrows. + const lastValidDayOfMonth = isYearInputComplete(year) && isMonthInputComplete(month) + ? lastDayOfMonth(new Date(year, month - 1)).getDate().toString() + : '31'; + switch (event.key) { case 'ArrowDown': if (!readOnly) { @@ -238,7 +245,7 @@ const DatePicker = ({ // Decrease the day when the user presses the down arrow. if (day === '' || parseInt(day) === 1) { - onDayChange('31'); + onDayChange(lastValidDayOfMonth); } else if (isValidDayInput(day)) { const dayMinusOne = (parseInt(day) - 1).toString().padStart(2, '0'); onDayChange(dayMinusOne); @@ -263,7 +270,7 @@ const DatePicker = ({ event.preventDefault(); // Increase the day when the user presses the up arrow. - if (day === '' || day === '31') { + if (day === '' || day === lastValidDayOfMonth) { onDayChange('01'); } else if (isValidDayInput(day)) { const dayPlusOne = (parseInt(day) + 1).toString().padStart(2, '0'); @@ -380,6 +387,6 @@ const DatePicker = ({ ; }; -export { EMPTY_DATE_VALUE }; +export { EMPTY_DATE_VALUE, isValidDate }; export default memo(DatePicker); diff --git a/src/DatePicker/index.test.js b/src/DatePicker/index.test.js index bce43d157..4ea09f925 100644 --- a/src/DatePicker/index.test.js +++ b/src/DatePicker/index.test.js @@ -798,7 +798,7 @@ describe('DatePicker', () => { expect(onChange).toHaveBeenCalledWith('--01'); }); - test('sets the day to 01 if the input has the value 31 and is focused and the user presses the up arrow', async () => { + test('sets the day to 01 if the year and month are not valid and the day input has the value 31 and is focused and the user presses the up arrow', async () => { renderDatePicker({ value: '--31' }); await userEvent.click(screen.getByLabelText('Day')); @@ -811,6 +811,19 @@ describe('DatePicker', () => { expect(onChange).toHaveBeenCalledWith('--01'); }); + test('sets the day to 01 if the year and month are valid and the day input has the last valid day of the month and is focused and the user presses the up arrow', async () => { + renderDatePicker({ value: '2020-02-29' }); + + await userEvent.click(screen.getByLabelText('Day')); + + expect(onChange).not.toHaveBeenCalled(); + + await userEvent.keyboard('[ArrowUp]'); + + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith('2020-02-01'); + }); + test('increments the day if the input has a valid value and is focused and the user presses the up arrow', async () => { renderDatePicker({ value: '--18' }); @@ -837,7 +850,7 @@ describe('DatePicker', () => { expect(onChange).toHaveBeenCalledWith('--31'); }); - test('sets the day to 31 if the input has the value 01 and is focused and the user presses the down arrow', async () => { + test('sets the day to 31 if the year and month are not valid and the day input has the value 01 and is focused and the user presses the down arrow', async () => { renderDatePicker({ value: '--01' }); await userEvent.click(screen.getByLabelText('Day')); @@ -850,6 +863,19 @@ describe('DatePicker', () => { expect(onChange).toHaveBeenCalledWith('--31'); }); + test('sets the day to the last valid day of the month if the year and month are valid and the day input has the value 01 and is focused and the user presses the down arrow', async () => { + renderDatePicker({ value: '2020-02-01' }); + + await userEvent.click(screen.getByLabelText('Day')); + + expect(onChange).not.toHaveBeenCalled(); + + await userEvent.keyboard('[ArrowDown]'); + + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith('2020-02-29'); + }); + test('decrements the day if the input has a valid value and is focused and the user presses the down arrow', async () => { renderDatePicker({ value: '--18' }); diff --git a/src/DatePicker/utils/index.js b/src/DatePicker/utils/index.js index c935cd15f..05c0bd998 100644 --- a/src/DatePicker/utils/index.js +++ b/src/DatePicker/utils/index.js @@ -59,4 +59,4 @@ export const shouldCompleteFirstDayDigitWithZero = (day) => /^[1-3]$/.test(day); export const isDayInputComplete = (day) => /^(0[1-9]|[12][0-9]|3[01])$/.test(day); -export const isValidDate = (time) => /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/.test(time); +export const isValidDate = (date) => /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/.test(date); diff --git a/src/DateRangeSelector/index.js b/src/DateRangeSelector/index.js index d30998137..cd574ada4 100644 --- a/src/DateRangeSelector/index.js +++ b/src/DateRangeSelector/index.js @@ -70,21 +70,21 @@ const DateRangeSelector = ({ onFilterSettingsToggle && onFilterSettingsToggle(!filterSettingsOpen); }, [filterSettingsOpen, onFilterSettingsToggle]); - const onStartDateTimePickerChange = (dateTime) => { - setStartDateTime(dateTime); + const onStartDateTimePickerChange = (newStartDateTime) => { + setStartDateTime(newStartDateTime); - const parsedDateTime = parseISO(dateTime); - if (isValid(parsedDateTime)) { - onStartDateChange(parsedDateTime); + const parsedNewStartDateTime = parseISO(newStartDateTime); + if (isValid(parsedNewStartDateTime)) { + onStartDateChange(parsedNewStartDateTime); } }; - const onEndDateTimePickerChange = (dateTime) => { - setEndDateTime(dateTime); + const onEndDateTimePickerChange = (newEndDateTime) => { + setEndDateTime(newEndDateTime); - const parsedDateTime = parseISO(dateTime); - if (isValid(parsedDateTime)) { - onEndDateChange(parsedDateTime); + const parsedNewEndDateTime = parseISO(newEndDateTime); + if (isValid(parsedNewEndDateTime)) { + onEndDateChange(parsedNewEndDateTime); } }; diff --git a/src/DateTimePicker/index.js b/src/DateTimePicker/index.js index 55bfc39e2..d839145f6 100644 --- a/src/DateTimePicker/index.js +++ b/src/DateTimePicker/index.js @@ -1,14 +1,12 @@ import React, { memo, useImperativeHandle, useRef } from 'react'; -import { getMaxDateAndTime, getMinDateAndTime } from './utils'; +import { EMPTY_DATE_TIME_VALUE, getMaxDateAndTime, getMinDateAndTime } from './utils'; -import DatePicker, { EMPTY_DATE_VALUE } from '../DatePicker'; +import DatePicker from '../DatePicker'; import TimePicker, { EMPTY_TIME_VALUE } from '../TimePicker'; import * as styles from './styles.module.scss'; -export const EMPTY_DATE_TIME_VALUE = `${EMPTY_DATE_VALUE}T${EMPTY_TIME_VALUE}`; - const DateTimePicker = ({ className = '', datePickerProps = {}, @@ -77,4 +75,6 @@ const DateTimePicker = ({ ; }; +export { EMPTY_DATE_TIME_VALUE }; + export default memo(DateTimePicker); diff --git a/src/DateTimePicker/utils/index.js b/src/DateTimePicker/utils/index.js index 0841e9613..21f416117 100644 --- a/src/DateTimePicker/utils/index.js +++ b/src/DateTimePicker/utils/index.js @@ -1,3 +1,8 @@ +import { EMPTY_DATE_VALUE } from '../../DatePicker'; +import { EMPTY_TIME_VALUE } from '../../TimePicker'; + +export const EMPTY_DATE_TIME_VALUE = `${EMPTY_DATE_VALUE}T${EMPTY_TIME_VALUE}`; + export const getMaxDateAndTime = (max, value) => { const [dateValue] = value.split('T'); const [maxDate, maxTime] = max.split('T'); diff --git a/src/PatrolDetailView/PlanSection/index.js b/src/PatrolDetailView/PlanSection/index.js index 43436891b..d9d38c8ed 100644 --- a/src/PatrolDetailView/PlanSection/index.js +++ b/src/PatrolDetailView/PlanSection/index.js @@ -13,7 +13,7 @@ import { displayStartTimeForPatrol } from '../../utils/patrols'; import { fetchTrackedBySchema } from '../../ducks/trackedby'; -import { getHoursAndMinutesString, getTimezoneOffsetString } from '../../utils/datetime'; +import { getHoursAndMinutesString } from '../../utils/datetime'; import { updateUserPreferences } from '../../ducks/user-preferences'; import { setMapLocationSelectionPatrol } from '../../ducks/map-ui'; import { useMatchMedia } from '../../hooks'; @@ -58,55 +58,49 @@ const PlanSection = ({ const [startDate, setStartDate] = useState(format(displayStartDate ?? new Date(), 'yyyy-MM-dd')); const [startTime, setStartTime] = useState(getHoursAndMinutesString(displayStartDate)); - const handleEndDateChange = useCallback((date) => { - setEndDate(date); + const handleEndDateChange = useCallback((newEndDate) => { + setEndDate(newEndDate); - let dateISO = `${date}T`; - dateISO += isValidTime(endTime) ? endTime : '00:00'; - dateISO += `:00${getTimezoneOffsetString()}`; - - const parsedDate = parseISO(dateISO); - if (isValid(parsedDate)) { - onPatrolEndDateChange(parsedDate, shouldScheduleDate(parsedDate, isAutoEnd)); + const parsedNewEndDate = parseISO(`${newEndDate}T${isValidTime(endTime) ? endTime : '00:00'}`); + if (isValid(parsedNewEndDate)) { + onPatrolEndDateChange(parsedNewEndDate, shouldScheduleDate(parsedNewEndDate, isAutoEnd)); } else { onPatrolEndDateChange(undefined); } }, [endTime, isAutoEnd, onPatrolEndDateChange]); - const handleStartDateChange = useCallback((date) => { - setStartDate(date); - - let dateISO = `${date}T`; - dateISO += isValidTime(startTime) ? startTime : '00:00'; - dateISO += `:00${getTimezoneOffsetString()}`; + const handleStartDateChange = useCallback((newStartDate) => { + setStartDate(newStartDate); - const parsedDate = parseISO(dateISO); - if (isValid(parsedDate)) { - onPatrolStartDateChange(parsedDate, shouldScheduleDate(parsedDate, isAutoStart)); + const parsedNewStartDate = parseISO(`${newStartDate}T${isValidTime(startTime) ? startTime : '00:00'}`); + if (isValid(parsedNewStartDate)) { + onPatrolStartDateChange(parsedNewStartDate, shouldScheduleDate(parsedNewStartDate, isAutoStart)); } else { onPatrolStartDateChange(undefined); } }, [isAutoStart, onPatrolStartDateChange, startTime]); - const handleEndTimeChange = useCallback((endTime) => { - setEndTime(endTime); - - const newEndTimeParts = endTime.split(':'); - const updatedEndDateTime = displayEndDate ? new Date(displayEndDate) : new Date(); - updatedEndDateTime.setHours(newEndTimeParts[0], newEndTimeParts[1], '00'); + const handleEndTimeChange = useCallback((newEndTime) => { + setEndTime(newEndTime); - onPatrolEndDateChange(updatedEndDateTime, shouldScheduleDate(updatedEndDateTime, isAutoEnd)); - }, [displayEndDate, isAutoEnd, onPatrolEndDateChange]); - - const handleStartTimeChange = useCallback((startTime) => { - setStartTime(startTime); + const parsedNewEndDate = parseISO(`${endDate}T${newEndTime}`); + if (isValid(parsedNewEndDate)) { + onPatrolEndDateChange(parsedNewEndDate, shouldScheduleDate(parsedNewEndDate, isAutoEnd)); + } else { + onPatrolEndDateChange(undefined); + } + }, [endDate, isAutoEnd, onPatrolEndDateChange]); - const newStartTimeParts = startTime.split(':'); - const updatedStartDateTime = displayStartDate ? new Date(displayStartDate) : new Date(); - updatedStartDateTime.setHours(newStartTimeParts[0], newStartTimeParts[1], '00'); + const handleStartTimeChange = useCallback((newStartTime) => { + setStartTime(newStartTime); - onPatrolStartDateChange(updatedStartDateTime, shouldScheduleDate(updatedStartDateTime, isAutoStart)); - }, [displayStartDate, isAutoStart, onPatrolStartDateChange]); + const parsedNewStartDate = parseISO(`${startDate}T${newStartTime}`); + if (isValid(parsedNewStartDate)) { + onPatrolStartDateChange(parsedNewStartDate, shouldScheduleDate(parsedNewStartDate, isAutoStart)); + } else { + onPatrolStartDateChange(undefined); + } + }, [isAutoStart, onPatrolStartDateChange, startDate]); const handleAutoEndChange = useCallback(() => { const newIsAutoEnd = !isAutoEnd; diff --git a/src/ReportManager/DetailsSection/SchemaForm/fields/DateTime/index.js b/src/ReportManager/DetailsSection/SchemaForm/fields/DateTime/index.js index b3c9a5d5d..af757a1b0 100644 --- a/src/ReportManager/DetailsSection/SchemaForm/fields/DateTime/index.js +++ b/src/ReportManager/DetailsSection/SchemaForm/fields/DateTime/index.js @@ -2,9 +2,8 @@ import React, { memo, useEffect, useState } from 'react'; import { format, isValid, parseISO } from 'date-fns'; import { DATE_TIME_ELEMENT_INPUT_TYPES } from '../../constants'; -import { getTimezoneOffsetString } from '../../../../../utils/datetime'; -import DatePicker, { EMPTY_DATE_VALUE } from '../../../../../DatePicker'; +import DatePicker, { isValidDate, EMPTY_DATE_VALUE } from '../../../../../DatePicker'; import DateTimePicker, { EMPTY_DATE_TIME_VALUE } from '../../../../../DateTimePicker'; import TimePicker, { EMPTY_TIME_VALUE } from '../../../../../TimePicker'; @@ -26,8 +25,11 @@ const DateTimeInput = ({ onChange, value, ...otherProps }) => { if (newDateTime === EMPTY_DATE_TIME_VALUE) { onChange(undefined); } else { - // JSON schema time format expects the time with the timezone offset. - const dateTimeWithSecondsAndOffset = `${newDateTime}:00${getTimezoneOffsetString()}`; + // JSON schema date time format expects the date time with seconds and timezone offset. If the new date is valid, + // consider the offset of that date (to match the daylight saving), otherwise consider the current date offset. + const [newDateValue] = newDateTime.split('T'); + const newOffset = format(isValidDate(newDateValue) ? newDateValue : new Date(), 'xxx'); + const dateTimeWithSecondsAndOffset = `${newDateTime}:00${newOffset}`; onChange(dateTimeWithSecondsAndOffset); } }; @@ -49,8 +51,8 @@ const TimeInput = ({ onChange, value, ...otherProps }) => { if (newTime === EMPTY_TIME_VALUE) { onChange(undefined); } else { - // JSON schema time format expects the time with the timezone offset. - const timeWithSecondsAndOffset = `${newTime}:00${getTimezoneOffsetString()}`; + // JSON schema time format expects the time with seconds and timezone offset. + const timeWithSecondsAndOffset = `${newTime}:00${format(new Date(), 'xxx')}`; onChange(timeWithSecondsAndOffset); } }; @@ -76,7 +78,7 @@ const DateTime = ({ autofillDefaultInput: _autofillDefaultInput, details, error, // Date-time and time input types have a timezone offset, so we correct the input value to the current user timezone // before rendering it. useEffect(() => { - if (!hasTimezoneBeenCorrected && value) { + if (value && !hasTimezoneBeenCorrected) { if (details.inputType === DATE_TIME_ELEMENT_INPUT_TYPES.DATE_TIME) { const parsedDateTimeValue = parseISO(value); if (isValid(parsedDateTimeValue)) { @@ -85,8 +87,7 @@ const DateTime = ({ autofillDefaultInput: _autofillDefaultInput, details, error, } if (details.inputType === DATE_TIME_ELEMENT_INPUT_TYPES.TIME) { - // We add a dummy date just to make it a valid ISO date - const parsedTimeValue = parseISO(`2000-01-01T${value}`); + const parsedTimeValue = parseISO(`${format(new Date(), 'yyyy-MM-dd')}T${value}`); if (isValid(parsedTimeValue)) { onFieldChange(id, format(parsedTimeValue, 'HH:mm:ssXXX')); } diff --git a/src/ReportManager/DetailsSection/SchemaForm/fields/DateTime/index.test.js b/src/ReportManager/DetailsSection/SchemaForm/fields/DateTime/index.test.js index 510994e28..da627a503 100644 --- a/src/ReportManager/DetailsSection/SchemaForm/fields/DateTime/index.test.js +++ b/src/ReportManager/DetailsSection/SchemaForm/fields/DateTime/index.test.js @@ -6,7 +6,6 @@ import { render, screen } from '../../../../../test-utils'; import { DATE_TIME_ELEMENT_INPUT_TYPES } from '../../constants'; import DateTime from './'; -import { getTimezoneOffsetString } from '../../../../../utils/datetime'; const transformISOToCurrentTimezone = (dateValue) => format(parseISO(dateValue), 'yyyy-MM-dd\'T\'HH:mm:ssXXX'); @@ -138,12 +137,13 @@ describe('ReportManager - DetailsSection - SchemaForm - fields - DateTime', () = await userEvent.click(screen.getByLabelText('Choose Monday, January 13th, 2020')); expect(onFieldChange).toHaveBeenCalledTimes(2); - expect(onFieldChange).toHaveBeenCalledWith('date-time-1', `2020-01-13T06:30:00${getTimezoneOffsetString()}`); + expect(onFieldChange).toHaveBeenCalledWith('date-time-1', transformISOToCurrentTimezone('2020-01-13T06:30')); await userEvent.click(screen.getByLabelText('Open time options')); await userEvent.click(screen.getByText('08:00 AM')); expect(onFieldChange).toHaveBeenCalledTimes(3); - expect(onFieldChange).toHaveBeenCalledWith('date-time-1', `2020-01-01T08:00:00${getTimezoneOffsetString()}`); + expect(onFieldChange.mock.calls[2][0]).toBe('date-time-1'); + expect(onFieldChange).toHaveBeenCalledWith('date-time-1', transformISOToCurrentTimezone('2020-01-01T08:00')); }); }); diff --git a/src/ReportManager/DetailsSection/index.js b/src/ReportManager/DetailsSection/index.js index 863609e43..00b663fac 100644 --- a/src/ReportManager/DetailsSection/index.js +++ b/src/ReportManager/DetailsSection/index.js @@ -1,7 +1,7 @@ import React, { memo, useCallback, useContext, useEffect, useState } from 'react'; import Dropdown from 'react-bootstrap/Dropdown'; import Form from '@rjsf/bootstrap-4'; -import { format, isToday, isValid as isValidDate, parseISO } from 'date-fns'; +import { format, isToday, isValid, parseISO } from 'date-fns'; import MoonLoader from 'react-spinners/MoonLoader'; import { useDispatch, useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; @@ -106,13 +106,9 @@ const DetailsSection = ({ const onDatePickerChange = (newDate) => { setDate(newDate); - const parsedDate = parseISO(newDate); - if (isValidDate(parsedDate)) { - if (isValidTime(time)) { - const [hour, minute] = time.split(':'); - parsedDate.setHours(hour, minute, '00'); - } - onReportDateChange(parsedDate); + const parsedNewDate = parseISO(`${newDate}T${isValidTime(time) ? time : '00:00'}`); + if (isValid(parsedNewDate)) { + onReportDateChange(parsedNewDate); } else { onReportDateChange(undefined); } @@ -123,13 +119,11 @@ const DetailsSection = ({ const onTimePickerChange = (newTime) => { setTime(newTime); - const parsedDate = parseISO(date); - if (isValidDate(parsedDate)) { - if (isValidTime(newTime)) { - const [newHour, newMinute] = newTime.split(':'); - parsedDate.setHours(newHour, newMinute, '00'); - } - onReportDateChange(parsedDate); + const parsedNewDate = parseISO(`${date}T${newTime}`); + if (isValid(parsedNewDate)) { + onReportDateChange(parsedNewDate); + } else { + onReportDateChange(undefined); } reportTracker.track('Change Report Time'); diff --git a/src/SchemaFields/index.js b/src/SchemaFields/index.js index 2b2a00d46..f6accbcf5 100644 --- a/src/SchemaFields/index.js +++ b/src/SchemaFields/index.js @@ -14,7 +14,6 @@ import { ReactComponent as TrashCanIcon } from '../common/images/icons/trash-can import { EVENT_REPORT_CATEGORY, trackEventFactory } from '../utils/analytics'; import { getElementPositionDataWithinScrollContainer } from '../utils/layout'; -import { getTimezoneOffsetString } from '../utils/datetime'; import { uuid } from '../utils/string'; import DateTimePicker, { EMPTY_DATE_TIME_VALUE } from '../DateTimePicker'; @@ -347,13 +346,20 @@ export const DateTimeWidget = ({ required, schema, }) => { - let timeZoneCorrectedData = formData; - if (formData) { - const parsedDateTimeValue = parseISO(formData); - if (isValid(parsedDateTimeValue)) { - timeZoneCorrectedData = format(parsedDateTimeValue, 'yyyy-MM-dd\'T\'HH:mm:ssXXX'); - } - } + // If the form data contains a value, we use date-fns format to transform it to the user's current time zone and set + // the initial value string in the time picker format. + const [dateTime, setDateTime] = useState(formData + ? format(formData, 'yyyy-MM-dd\'T\'HH:mm') + : EMPTY_DATE_TIME_VALUE); + + const onDateTimePickerChange = (newDateTime) => { + setDateTime(newDateTime); + + // When there is a change, if the new date time is valid, we store it with the right offset corresponding to the + // date (format method considers the daylight saving) and the user's current time zone. + const parsedNewDateTime = parseISO(newDateTime); + onChange(isValid(parsedNewDateTime) ? format(parsedNewDateTime, 'yyyy-MM-dd\'T\'HH:mm:ssxxx') : undefined); + }; return <> @@ -362,12 +368,11 @@ export const DateTimeWidget = ({ autofocus={autofocus} disabled={disabled} onBlur={onBlur} - onChange={(newDateTime) => onChange(`${newDateTime}:00${getTimezoneOffsetString()}`)} + onChange={onDateTimePickerChange} onFocus={onFocus} readOnly={readonly} required={required} - // Slice out the seconds and the time offset. - value={timeZoneCorrectedData ? timeZoneCorrectedData.slice(0, -9) : EMPTY_DATE_TIME_VALUE} + value={dateTime} /> ; }; diff --git a/src/utils/datetime.js b/src/utils/datetime.js index 27912154f..30184e689 100644 --- a/src/utils/datetime.js +++ b/src/utils/datetime.js @@ -141,16 +141,6 @@ export const getUserLocaleTime = (date = new Date()) => date.toLocaleTimeString( export const isGreaterThan = (date1, date2) => date1.getTime() > date2.getTime(); -export const getTimezoneOffsetString = () => { - const offsetMinutes = new Date().getTimezoneOffset(); - const absoluteMinutes = Math.abs(offsetMinutes); - - const sign = offsetMinutes > 0 ? '-' : '+'; - const hours = String(Math.floor(absoluteMinutes / 60)).padStart(2, '0'); - const minutes = String(absoluteMinutes % 60).padStart(2, '0'); - return `${sign}${hours}:${minutes}`; -}; - export const formatDateToLocalISO = (date) => format(date, 'yyyy-MM-dd\'T\'HH:mm'); export const shouldUse12HourFormat = (locale) => {