Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: date-picker bug for negative UTC timezones attempt 3 #6261

Merged
merged 15 commits into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"simplur": "^3.0.1",
"spark-md5": "^3.0.2",
"stripe": "^11.1.0",
"timezone-mock": "^1.3.6",
"type-fest": "^2.8.0",
"typescript": "^4.5.3",
"use-debounce": "^7.0.1",
Expand Down
31 changes: 9 additions & 22 deletions frontend/src/components/DatePicker/DatePickerContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
useMultiStyleConfig,
} from '@chakra-ui/react'
import { format, isValid, parse } from 'date-fns'
import { zonedTimeToUtc } from 'date-fns-tz'

import { ThemeColorScheme } from '~theme/foundations/colours'
import { useIsMobile } from '~hooks/useIsMobile'
Expand Down Expand Up @@ -84,7 +83,6 @@ const useProvideDatePicker = ({
isReadOnly: isReadOnlyProp,
isRequired: isRequiredProp,
isInvalid: isInvalidProp,
timeZone = 'UTC',
locale,
isDateUnavailable,
allowManualInput = true,
Expand Down Expand Up @@ -120,9 +118,9 @@ const useProvideDatePicker = ({
const formatInputValue = useCallback(
(date: Date | null) => {
if (!date || !isValid(date)) return ''
return format(zonedTimeToUtc(date, timeZone), displayFormat, { locale })
return format(date, displayFormat, { locale })
},
[displayFormat, locale, timeZone],
[displayFormat, locale],
)

// What is rendered as a string in the input according to given display format.
Expand All @@ -142,11 +140,7 @@ const useProvideDatePicker = ({

const handleInputBlur: FocusEventHandler<HTMLInputElement> = useCallback(
(e) => {
const date = parse(
internalInputValue,
dateFormat,
zonedTimeToUtc(new Date(), timeZone),
)
const date = parse(internalInputValue, dateFormat, new Date())
// Clear if input is invalid on blur if invalid dates are not allowed.
if (!allowInvalidDates && !isValid(date)) {
setInternalValue(null)
Expand All @@ -161,7 +155,6 @@ const useProvideDatePicker = ({
onBlur,
setInternalInputValue,
setInternalValue,
timeZone,
],
)

Expand All @@ -179,12 +172,11 @@ const useProvideDatePicker = ({

const handleDateChange = useCallback(
(date: Date | null) => {
const zonedDate = date ? zonedTimeToUtc(date, timeZone) : null
if (allowInvalidDates || isValid(zonedDate) || !zonedDate) {
setInternalValue(zonedDate)
if (allowInvalidDates || isValid(date) || !date) {
setInternalValue(date)
}
if (zonedDate) {
setInternalInputValue(format(zonedDate, displayFormat, { locale }))
if (date) {
setInternalInputValue(format(date, displayFormat, { locale }))
} else {
setInternalInputValue('')
}
Expand All @@ -198,23 +190,18 @@ const useProvideDatePicker = ({
locale,
setInternalInputValue,
setInternalValue,
timeZone,
],
)

const handleInputChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const date = parse(
event.target.value,
dateFormat,
zonedTimeToUtc(new Date(), timeZone),
)
const date = parse(event.target.value, dateFormat, new Date())
setInternalInputValue(event.target.value)
if (isValid(date)) {
setInternalValue(date)
}
},
[dateFormat, setInternalInputValue, setInternalValue, timeZone],
[dateFormat, setInternalInputValue, setInternalValue],
)

const handleInputClick: MouseEventHandler<HTMLInputElement> = useCallback(
Expand Down
6 changes: 0 additions & 6 deletions frontend/src/components/DatePicker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,4 @@ export interface DatePickerBaseProps
refocusOnClose?: boolean
/** date-fns's Locale of the date to be applied if provided. */
locale?: Locale
/**
* Time zone of date created.
* Defaults to `'UTC'`.
* Accepts all possible `Intl.Locale.prototype.timeZones` values
*/
timeZone?: string
}
34 changes: 11 additions & 23 deletions frontend/src/components/DateRangePicker/DateRangePickerContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
useMultiStyleConfig,
} from '@chakra-ui/react'
import { format, isValid, parse } from 'date-fns'
import { zonedTimeToUtc } from 'date-fns-tz'

import { ThemeColorScheme } from '~theme/foundations/colours'
import { useIsMobile } from '~hooks/useIsMobile'
Expand Down Expand Up @@ -92,7 +91,6 @@ const useProvideDateRangePicker = ({
isReadOnly: isReadOnlyProp,
isRequired: isRequiredProp,
isInvalid: isInvalidProp,
timeZone = 'UTC',
locale,
isDateUnavailable,
allowManualInput = true,
Expand Down Expand Up @@ -129,13 +127,13 @@ const useProvideDateRangePicker = ({
// What is rendered as a string in the start date range input according to given display format.
const [startInputDisplay, setStartInputDisplay] = useState(
startDate && isValid(startDate)
? format(zonedTimeToUtc(startDate, timeZone), displayFormat, { locale })
? format(startDate, displayFormat, { locale })
: '',
)
// What is rendered as a string in the end date range input according to given display format.
const [endInputDisplay, setEndInputDisplay] = useState(
endDate && isValid(endDate)
? format(zonedTimeToUtc(endDate, timeZone), displayFormat, { locale })
? format(endDate, displayFormat, { locale })
: '',
)

Expand All @@ -151,24 +149,18 @@ const useProvideDateRangePicker = ({
) as DateRangeValue

const [nextStart, nextEnd] = sortedRange
const zonedStartDate = nextStart
? zonedTimeToUtc(nextStart, timeZone)
: null
const zonedEndDate = nextEnd ? zonedTimeToUtc(nextEnd, timeZone) : null
if (zonedStartDate) {
if (isValid(zonedStartDate)) {
setStartInputDisplay(
format(zonedStartDate, displayFormat, { locale }),
)
if (nextStart) {
if (isValid(nextStart)) {
setStartInputDisplay(format(nextStart, displayFormat, { locale }))
} else if (!allowInvalidDates) {
setStartInputDisplay('')
}
} else {
setStartInputDisplay('')
}
if (zonedEndDate) {
if (isValid(zonedEndDate)) {
setEndInputDisplay(format(zonedEndDate, displayFormat, { locale }))
if (nextEnd) {
if (isValid(nextEnd)) {
setEndInputDisplay(format(nextEnd, displayFormat, { locale }))
} else if (!allowInvalidDates) {
setEndInputDisplay('')
}
Expand All @@ -177,7 +169,7 @@ const useProvideDateRangePicker = ({
}
setInternalValue(validRange)
},
[allowInvalidDates, displayFormat, locale, setInternalValue, timeZone],
[allowInvalidDates, displayFormat, locale, setInternalValue],
)

const fcProps = useFormControlProps({
Expand Down Expand Up @@ -274,11 +266,8 @@ const useProvideDateRangePicker = ({

const handleCalendarDateChange = useCallback(
(date: DateRangeValue) => {
const zonedDateRange = date.map((d) =>
d ? zonedTimeToUtc(d, timeZone) : null,
) as DateRangeValue
const [nextStartDate, nextEndDate] = zonedDateRange
setInternalValue(zonedDateRange)
const [nextStartDate, nextEndDate] = date
setInternalValue(date)
setStartInputDisplay(
nextStartDate ? format(nextStartDate, displayFormat, { locale }) : '',
)
Expand All @@ -296,7 +285,6 @@ const useProvideDateRangePicker = ({
displayFormat,
locale,
setInternalValue,
timeZone,
],
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo } from 'react'
import { useCallback, useMemo } from 'react'
import { Controller, RegisterOptions } from 'react-hook-form'
import { Box, FormControl, SimpleGrid } from '@chakra-ui/react'
import { isBefore, isEqual, isValid } from 'date-fns'
Expand All @@ -10,7 +10,11 @@ import {
DateValidationOptions,
} from '~shared/types/field'

import { fromUtcToLocalDate, isDateOutOfRange } from '~utils/date'
import {
isDateOutOfRange,
loadDateFromNormalizedDate,
normalizeDateToUtc,
} from '~utils/date'
import { createBaseValidationRules } from '~utils/fieldValidation'
import { DatePicker } from '~components/DatePicker'
import { SingleSelect } from '~components/Dropdown'
Expand Down Expand Up @@ -49,10 +53,10 @@ const transformDateFieldToEditForm = (field: DateFieldBase): EditDateInputs => {
selectedDateValidation:
field.dateValidation.selectedDateValidation ?? ('' as const),
customMaxDate: field.dateValidation.selectedDateValidation
? field.dateValidation.customMaxDate ?? null
? loadDateFromNormalizedDate(field.dateValidation.customMaxDate)
: null,
customMinDate: field.dateValidation.selectedDateValidation
? field.dateValidation.customMinDate ?? null
? loadDateFromNormalizedDate(field.dateValidation.customMinDate)
: null,
}
return {
Expand Down Expand Up @@ -97,6 +101,28 @@ const transformDateEditFormToField = (
}

export const EditDate = ({ field }: EditDateProps): JSX.Element => {
const preSubmitTransform = useCallback(
(inputs: EditDateInputs, output: DateFieldBase): DateFieldBase => {
// normalize time to UTC before saving
return {
...output,
dateValidation: {
selectedDateValidation:
inputs.dateValidation.selectedDateValidation === ''
? null
: inputs.dateValidation.selectedDateValidation,
customMinDate: normalizeDateToUtc(
inputs.dateValidation.customMinDate,
),
customMaxDate: normalizeDateToUtc(
inputs.dateValidation.customMaxDate,
),
},
} as DateFieldBase
},
[],
)

const {
register,
formState: { errors },
Expand All @@ -111,6 +137,7 @@ export const EditDate = ({ field }: EditDateProps): JSX.Element => {
transform: {
input: transformDateFieldToEditForm,
output: transformDateEditFormToField,
preSubmit: preSubmitTransform,
},
})

Expand Down Expand Up @@ -222,9 +249,7 @@ export const EditDate = ({ field }: EditDateProps): JSX.Element => {
isDateUnavailable={(d) =>
isDateOutOfRange(
d,
fromUtcToLocalDate(
getValues('dateValidation.customMinDate'),
),
getValues('dateValidation.customMinDate'),
)
}
{...field}
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/templates/Field/Date/DateField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { FormColorTheme } from '~shared/types'
import { DateSelectedValidation } from '~shared/types/field'

import {
fromUtcToLocalDate,
isDateAfterToday,
isDateBeforeToday,
isDateOutOfRange,
loadDateFromNormalizedDate,
} from '~utils/date'
import { createDateValidationRules } from '~utils/fieldValidation'
import { DatePicker } from '~components/DatePicker'
Expand Down Expand Up @@ -53,8 +53,8 @@ export const DateField = ({
// need to convert to local time but with the same date as UTC.
return isDateOutOfRange(
date,
fromUtcToLocalDate(customMinDate),
fromUtcToLocalDate(customMaxDate),
loadDateFromNormalizedDate(customMinDate),
loadDateFromNormalizedDate(customMaxDate),
)
}
default:
Expand Down
Loading