diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx index 187366e6b47..0d83fc4a53d 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx @@ -14,7 +14,7 @@ afterAll(() => { return setTimeZoneResolver(() => defaultTimeZone); }); -const renderDatetimePicker = (props?: Props) => { +const renderDatetimePicker = (props?: Partial) => { const combinedProps = Object.assign( { date: dateTimeForTimeZone(getTimeZone(), '2021-05-05 12:00:00'), @@ -234,4 +234,20 @@ describe('Date time picker', () => { ).not.toBeInTheDocument(); } ); + + it('should be able to use a custom timeZone', async () => { + renderDatetimePicker({ + timeZone: 'America/New_York', + date: dateTimeForTimeZone(getTimeZone({ timeZone: 'utc' }), '2024-07-01 02:00:00'), + }); + + const dateTimeInput = screen.getByTestId(Components.DateTimePicker.input); + expect(dateTimeInput).toHaveDisplayValue('2024-06-30 22:00:00'); + + await userEvent.click(screen.getByRole('button', { name: 'Time picker' })); + // Check that calendar date is set correctly + expect(screen.getByRole('button', { name: `June 30, 2024` })).toHaveClass('react-calendar__tile--active'); + // Check that time is set correctly + expect(screen.getAllByRole('textbox')[1]).toHaveValue('22:00:00'); + }); }); diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx index 9124d4636d0..1fdeb14114f 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx @@ -16,6 +16,7 @@ import { isDateTime, dateTimeForTimeZone, getTimeZone, + TimeZone, } from '@grafana/data'; import { Components } from '@grafana/e2e-selectors'; @@ -53,6 +54,8 @@ export interface Props { disabledSeconds?: () => number[]; /** Can input be cleared/have empty values */ clearable?: boolean; + /** Custom timezone for the date/time display */ + timeZone?: TimeZone; } export const DateTimePicker = ({ @@ -64,6 +67,7 @@ export const DateTimePicker = ({ disabledHours, disabledMinutes, disabledSeconds, + timeZone, showSeconds = true, clearable = false, }: Props) => { @@ -136,6 +140,7 @@ export const DateTimePicker = ({ ref={refs.setReference} showSeconds={showSeconds} clearable={clearable} + timeZone={timeZone} /> {isOpen ? ( isFullscreen ? ( @@ -155,6 +160,7 @@ export const DateTimePicker = ({ disabledHours={disabledHours} disabledMinutes={disabledMinutes} disabledSeconds={disabledSeconds} + timeZone={timeZone} /> @@ -176,6 +182,7 @@ export const DateTimePicker = ({ disabledHours={disabledHours} disabledMinutes={disabledMinutes} disabledSeconds={disabledSeconds} + timeZone={timeZone} /> @@ -187,21 +194,14 @@ export const DateTimePicker = ({ ); }; -interface DateTimeCalendarProps { - date?: DateTime; +interface DateTimeCalendarProps extends Omit { onChange: (date: DateTime) => void; onClose: () => void; isFullscreen: boolean; - maxDate?: Date; - minDate?: Date; style?: React.CSSProperties; - showSeconds?: boolean; - disabledHours?: () => number[]; - disabledMinutes?: () => number[]; - disabledSeconds?: () => number[]; } -type InputProps = Pick & { +type InputProps = Pick & { isFullscreen: boolean; onOpen: (event: FormEvent) => void; }; @@ -212,21 +212,25 @@ type InputState = { }; const DateTimeInput = React.forwardRef( - ({ date, label, onChange, onOpen, showSeconds = true, clearable = false }, ref) => { + ({ date, label, onChange, onOpen, timeZone, showSeconds = true, clearable = false }, ref) => { const styles = useStyles2(getStyles); const format = showSeconds ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm'; const [internalDate, setInternalDate] = useState(() => { - return { value: date ? dateTimeFormat(date) : !clearable ? dateTimeFormat(dateTime()) : '', invalid: false }; + return { + value: date ? dateTimeFormat(date, { timeZone }) : !clearable ? dateTimeFormat(dateTime(), { timeZone }) : '', + invalid: false, + }; }); useEffect(() => { if (date) { + const formattedDate = dateTimeFormat(date, { format, timeZone }); setInternalDate({ - invalid: !isValid(dateTimeFormat(date, { format })), - value: isDateTime(date) ? dateTimeFormat(date, { format }) : date, + invalid: !isValid(formattedDate), + value: isDateTime(date) ? formattedDate : date, }); } - }, [date, format]); + }, [date, format, timeZone]); const onChangeDate = useCallback((event: FormEvent) => { const isInvalid = !isValid(event.currentTarget.value); @@ -238,10 +242,10 @@ const DateTimeInput = React.forwardRef( const onBlur = useCallback(() => { if (!internalDate.invalid && internalDate.value) { - const date = dateTimeForTimeZone(getTimeZone(), internalDate.value); + const date = dateTimeForTimeZone(getTimeZone({ timeZone }), internalDate.value); onChange(date); } - }, [internalDate, onChange]); + }, [internalDate, onChange, timeZone]); const clearInternalDate = useCallback(() => { setInternalDate({ value: '', invalid: false }); @@ -285,6 +289,7 @@ const DateTimeCalendar = React.forwardRef disabledHours, disabledMinutes, disabledSeconds, + timeZone, }, ref ) => { @@ -294,17 +299,17 @@ const DateTimeCalendar = React.forwardRef // need to keep these 2 separate in state since react-calendar doesn't support different timezones const [timeOfDayDateTime, setTimeOfDayDateTime] = useState(() => { if (date && date.isValid()) { - return dateTimeForTimeZone(getTimeZone(), date); + return dateTimeForTimeZone(getTimeZone({ timeZone }), date); } - return dateTimeForTimeZone(getTimeZone(), new Date()); + return dateTimeForTimeZone(getTimeZone({ timeZone }), new Date()); }); const [reactCalendarDate, setReactCalendarDate] = useState(() => { if (date && date.isValid()) { - return adjustDateForReactCalendar(date.toDate(), getTimeZone()); + return adjustDateForReactCalendar(date.toDate(), getTimeZone({ timeZone })); } - return new Date(); + return adjustDateForReactCalendar(new Date(), getTimeZone({ timeZone })); }); const onChangeDate = useCallback['onChange']>>((date) => {