This is an automated email from the ASF dual-hosted git repository. kaxilnaik pushed a commit to branch v3-1-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 58735c44ba538ebe3b3e73e08a192b1b76df9905 Author: Pierre Jeambrun <[email protected]> AuthorDate: Fri Sep 12 08:35:33 2025 -0600 Unify datetime format in the UI (#55572) (cherry picked from commit 318a1f162d94941cf038b20c7db19c8a3ee91558) --- airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx | 3 ++- airflow-core/src/airflow/ui/src/components/DurationChart.tsx | 3 ++- airflow-core/src/airflow/ui/src/components/Time.test.tsx | 9 +++++---- airflow-core/src/airflow/ui/src/components/Time.tsx | 9 +++------ .../airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx | 3 ++- .../src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx | 10 +++++----- .../src/airflow/ui/src/layouts/Nav/TimezoneSelector.tsx | 3 ++- .../ui/src/pages/HITLTaskInstances/HITLResponseForm.tsx | 3 ++- airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts | 5 ++++- 9 files changed, 27 insertions(+), 21 deletions(-) diff --git a/airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx b/airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx index d31257a3623..3ee9dbf62c8 100644 --- a/airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx +++ b/airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx @@ -22,6 +22,7 @@ import tz from "dayjs/plugin/timezone"; import { forwardRef } from "react"; import { useTimezone } from "src/context/timezone"; +import { DEFAULT_DATETIME_FORMAT } from "src/utils/datetimeUtils"; dayjs.extend(tz); @@ -35,7 +36,7 @@ export const DateTimeInput = forwardRef<HTMLInputElement, Props>(({ onChange, va // Convert UTC value to local time for display const displayValue = Boolean(value) && dayjs(value).isValid() - ? dayjs(value).tz(selectedTimezone).format("YYYY-MM-DDTHH:mm:ss.SSS") + ? dayjs(value).tz(selectedTimezone).format(DEFAULT_DATETIME_FORMAT) : ""; return ( diff --git a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx index e0a44ebe9aa..c0fd7cf796b 100644 --- a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx +++ b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx @@ -36,6 +36,7 @@ import { useNavigate } from "react-router-dom"; import type { TaskInstanceResponse, GridRunsResponse } from "openapi/requests/types.gen"; import { getComputedCSSVariableValue } from "src/theme"; +import { DEFAULT_DATETIME_FORMAT } from "src/utils/datetimeUtils"; ChartJS.register( CategoryScale, @@ -160,7 +161,7 @@ export const DurationChart = ({ label: translate("durationChart.runDuration"), }, ], - labels: entries.map((entry: RunResponse) => dayjs(entry.run_after).format("YYYY-MM-DD, hh:mm:ss")), + labels: entries.map((entry: RunResponse) => dayjs(entry.run_after).format(DEFAULT_DATETIME_FORMAT)), }} datasetIdKey="id" options={{ diff --git a/airflow-core/src/airflow/ui/src/components/Time.test.tsx b/airflow-core/src/airflow/ui/src/components/Time.test.tsx index c2f0f53c02c..f342ad0b484 100644 --- a/airflow-core/src/airflow/ui/src/components/Time.test.tsx +++ b/airflow-core/src/airflow/ui/src/components/Time.test.tsx @@ -22,8 +22,9 @@ import { describe, it, expect, vi } from "vitest"; import { TimezoneContext } from "src/context/timezone"; import { Wrapper } from "src/utils/Wrapper"; +import { DEFAULT_DATETIME_FORMAT, DEFAULT_DATETIME_FORMAT_WITH_TZ } from "src/utils/datetimeUtils"; -import Time, { defaultFormat, defaultFormatWithTZ } from "./Time"; +import Time from "./Time"; describe("Test Time and TimezoneProvider", () => { it("Displays a UTC time correctly", () => { @@ -38,7 +39,7 @@ describe("Test Time and TimezoneProvider", () => { }, ); - const utcTime = screen.getByText(dayjs.utc(now).format(defaultFormat)); + const utcTime = screen.getByText(dayjs.utc(now).format(DEFAULT_DATETIME_FORMAT)); expect(utcTime).toBeDefined(); expect(utcTime.title).toBeFalsy(); @@ -58,9 +59,9 @@ describe("Test Time and TimezoneProvider", () => { ); const nowTime = dayjs(now); - const samoaTime = screen.getByText(nowTime.tz(tz).format(defaultFormat)); + const samoaTime = screen.getByText(nowTime.tz(tz).format(DEFAULT_DATETIME_FORMAT)); expect(samoaTime).toBeDefined(); - expect(samoaTime.title).toEqual(nowTime.tz("UTC").format(defaultFormatWithTZ)); + expect(samoaTime.title).toEqual(nowTime.tz("UTC").format(DEFAULT_DATETIME_FORMAT_WITH_TZ)); }); }); diff --git a/airflow-core/src/airflow/ui/src/components/Time.tsx b/airflow-core/src/airflow/ui/src/components/Time.tsx index 8edd92d983f..99b7f029f72 100644 --- a/airflow-core/src/airflow/ui/src/components/Time.tsx +++ b/airflow-core/src/airflow/ui/src/components/Time.tsx @@ -23,10 +23,7 @@ import tz from "dayjs/plugin/timezone"; import utc from "dayjs/plugin/utc"; import { useTimezone } from "src/context/timezone"; - -export const defaultFormat = "YYYY-MM-DD, HH:mm:ss"; -export const defaultFormatWithTZ = `${defaultFormat} z`; -export const defaultTZFormat = "z (Z)"; +import { DEFAULT_DATETIME_FORMAT, DEFAULT_DATETIME_FORMAT_WITH_TZ } from "src/utils/datetimeUtils"; dayjs.extend(utc); dayjs.extend(tz); @@ -38,7 +35,7 @@ type Props = { readonly showTooltip?: boolean; } & SpanProps; -const Time = ({ datetime, format = defaultFormat, showTooltip = true, ...rest }: Props) => { +const Time = ({ datetime, format = DEFAULT_DATETIME_FORMAT, showTooltip = true, ...rest }: Props) => { const { selectedTimezone } = useTimezone(); const time = dayjs(datetime); @@ -47,7 +44,7 @@ const Time = ({ datetime, format = defaultFormat, showTooltip = true, ...rest }: } const formattedTime = time.tz(selectedTimezone).format(format); - const utcTime = time.tz("UTC").format(defaultFormatWithTZ); + const utcTime = time.tz("UTC").format(DEFAULT_DATETIME_FORMAT_WITH_TZ); return ( <chakra.span dir="ltr" {...rest}> diff --git a/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx b/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx index 8bd3484ca33..8c92730714d 100644 --- a/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx +++ b/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx @@ -27,6 +27,7 @@ import { useDagParams } from "src/queries/useDagParams"; import { useParamStore } from "src/queries/useParamStore"; import { useTogglePause } from "src/queries/useTogglePause"; import { useTrigger } from "src/queries/useTrigger"; +import { DEFAULT_DATETIME_FORMAT } from "src/utils/datetimeUtils"; import ConfigForm from "../ConfigForm"; import { DateTimeInput } from "../DateTimeInput"; @@ -65,7 +66,7 @@ const TriggerDAGForm = ({ dagDisplayName, dagId, isPaused, onClose, open }: Trig conf, dagRunId: "", // Default logical date to now, show it in the selected timezone - logicalDate: dayjs().format("YYYY-MM-DDTHH:mm:ss.SSS"), + logicalDate: dayjs().format(DEFAULT_DATETIME_FORMAT), note: "", }, }); diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx index fe623727377..655e86e19ce 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx @@ -48,7 +48,7 @@ import { useGridStructure } from "src/queries/useGridStructure"; import { useGridTiSummaries } from "src/queries/useGridTISummaries"; import { getComputedCSSVariableValue } from "src/theme"; import { isStatePending, useAutoRefresh } from "src/utils"; -import { formatDate } from "src/utils/datetimeUtils"; +import { DEFAULT_DATETIME_FORMAT, formatDate } from "src/utils/datetimeUtils"; import { createHandleBarClick, createChartOptions } from "./utils"; @@ -147,8 +147,8 @@ export const Gantt = ({ limit }: Props) => { state: gridSummary.state, taskId: gridSummary.task_id, x: [ - formatDate(gridSummary.min_start_date, selectedTimezone, "YYYY-MM-DD HH:mm:ss.SSS"), - formatDate(gridSummary.max_end_date, selectedTimezone, "YYYY-MM-DD HH:mm:ss.SSS"), + formatDate(gridSummary.min_start_date, selectedTimezone, DEFAULT_DATETIME_FORMAT), + formatDate(gridSummary.max_end_date, selectedTimezone, DEFAULT_DATETIME_FORMAT), ], y: gridSummary.task_id, }; @@ -163,8 +163,8 @@ export const Gantt = ({ limit }: Props) => { state: taskInstance.state, taskId: taskInstance.task_id, x: [ - formatDate(taskInstance.start_date, selectedTimezone, "YYYY-MM-DD HH:mm:ss.SSS"), - formatDate(taskInstance.end_date, selectedTimezone, "YYYY-MM-DD HH:mm:ss.SSS"), + formatDate(taskInstance.start_date, selectedTimezone, DEFAULT_DATETIME_FORMAT), + formatDate(taskInstance.end_date, selectedTimezone, DEFAULT_DATETIME_FORMAT), ], y: taskInstance.task_id, }; diff --git a/airflow-core/src/airflow/ui/src/layouts/Nav/TimezoneSelector.tsx b/airflow-core/src/airflow/ui/src/layouts/Nav/TimezoneSelector.tsx index 7a458f668c3..1ab91744cb3 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Nav/TimezoneSelector.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Nav/TimezoneSelector.tsx @@ -25,6 +25,7 @@ import React, { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { useTimezone } from "src/context/timezone"; +import { DEFAULT_DATETIME_FORMAT } from "src/utils/datetimeUtils"; import type { Option as TimezoneOption } from "src/utils/option"; dayjs.extend(utc); @@ -60,7 +61,7 @@ const TimezoneSelector: React.FC = () => { useEffect(() => { const updateTime = () => { - setCurrentTime(dayjs().tz(selectedTimezone).format("YYYY-MM-DD HH:mm:ss")); + setCurrentTime(dayjs().tz(selectedTimezone).format(DEFAULT_DATETIME_FORMAT)); }; updateTime(); diff --git a/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLResponseForm.tsx b/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLResponseForm.tsx index 30610756121..e703683ce9b 100644 --- a/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLResponseForm.tsx +++ b/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLResponseForm.tsx @@ -27,6 +27,7 @@ import { FlexibleForm } from "src/components/FlexibleForm/FlexibleForm"; import Time from "src/components/Time"; import { useParamStore } from "src/queries/useParamStore"; import { useUpdateHITLDetail } from "src/queries/useUpdateHITLDetail"; +import { DEFAULT_DATETIME_FORMAT } from "src/utils/datetimeUtils"; import { getHITLParamsDict, getHITLFormData, getPreloadHITLFormData } from "src/utils/hitl"; type HITLResponseFormProps = { @@ -97,7 +98,7 @@ export const HITLResponseForm = ({ hitlDetail }: HITLResponseFormProps) => { {hitlDetail.response_received ? ( <Text color="fg.muted" fontSize="sm"> {translate("response.received")} - <Time datetime={hitlDetail.response_at} format="YYYY-MM-DD, HH:mm:ss" /> + <Time datetime={hitlDetail.response_at} format={DEFAULT_DATETIME_FORMAT} /> </Text> ) : undefined} <Accordion.Root diff --git a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts index bcc1f657316..f1871b4d4fe 100644 --- a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts +++ b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts @@ -23,6 +23,9 @@ import tz from "dayjs/plugin/timezone"; dayjs.extend(dayjsDuration); dayjs.extend(tz); +export const DEFAULT_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss"; +export const DEFAULT_DATETIME_FORMAT_WITH_TZ = `${DEFAULT_DATETIME_FORMAT} z`; + export const renderDuration = (durationSeconds: number | null | undefined): string => { if ( durationSeconds === null || @@ -51,7 +54,7 @@ export const getDuration = (startDate?: string | null, endDate?: string | null) export const formatDate = ( date: number | string | null | undefined, timezone: string, - format: string = "YYYY-MM-DD HH:mm:ss", + format: string = DEFAULT_DATETIME_FORMAT, ) => { if (date === null || date === undefined || !dayjs(date).isValid()) { return dayjs().tz(timezone).format(format);
