This is an automated email from the ASF dual-hosted git repository.

bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new f8c9c517d69 UI Fix: Date time input 'year' field unmodifiable (#63885)
f8c9c517d69 is described below

commit f8c9c517d696d726b9beab83af09d5387522209f
Author: Tomi <[email protected]>
AuthorDate: Thu Mar 26 19:27:15 2026 +0200

    UI Fix: Date time input 'year' field unmodifiable (#63885)
    
    * avoid passing parsed input back to component
    
    * on change, update component and debounce utc parsing
    
    * typo, linting fixes
    
    * longer type delay, removed redundant isValid check
---
 .../airflow/ui/src/components/DateTimeInput.tsx    | 47 +++++++++++++---------
 1 file changed, 29 insertions(+), 18 deletions(-)

diff --git a/airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx 
b/airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx
index 60fdf807916..add6addb8dd 100644
--- a/airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx
@@ -19,43 +19,54 @@
 import { Input, type InputProps } from "@chakra-ui/react";
 import dayjs from "dayjs";
 import tz from "dayjs/plugin/timezone";
-import { forwardRef } from "react";
+import { forwardRef, type ChangeEvent, useState } from "react";
+import { useDebouncedCallback } from "use-debounce";
 
 import { useTimezone } from "src/context/timezone";
 import { DEFAULT_DATETIME_FORMAT } from "src/utils/datetimeUtils";
 
 dayjs.extend(tz);
 
+const debounceDelay = 1000;
+
 type Props = {
   readonly value: string;
 } & InputProps;
 
 export const DateTimeInput = forwardRef<HTMLInputElement, Props>(({ onChange, 
value, ...rest }, ref) => {
   const { selectedTimezone } = useTimezone();
+  const [displayDate, setDisplayDate] = useState(value);
+
+  const onDateChange = (event: ChangeEvent<HTMLInputElement>) => {
+    const valid = dayjs(event.target.value).isValid();
+    // UI Timezone -> Utc -> yyyy-mm-ddThh:mmZ
+    const utc = valid ? dayjs.tz(event.target.value, 
selectedTimezone).toISOString() : "";
+    const local = Boolean(utc) ? 
dayjs(utc).tz(selectedTimezone).format(DEFAULT_DATETIME_FORMAT) : "";
 
-  // Convert UTC value to local time for display
-  const displayValue =
-    Boolean(value) && dayjs(value).isValid()
-      ? dayjs(value).tz(selectedTimezone).format(DEFAULT_DATETIME_FORMAT)
-      : "";
+    // Set display value to be from utc to local to avoid year mismatch
+    // As dayjs() parses years before 1000 incorrectly, see dayjs/issues/1237
+    setDisplayDate(local);
+    onChange?.({ ...event, target: { ...event.target, value: utc } });
+  };
+
+  const debouncedOnDateChange = useDebouncedCallback(
+    (event: ChangeEvent<HTMLInputElement>) => onDateChange(event),
+    debounceDelay,
+  );
 
   return (
     <Input
       data-testid="datetime-input"
-      onChange={(event) =>
-        onChange?.({
-          ...event,
-          target: {
-            ...event.target,
-            value: dayjs(event.target.value).isValid()
-              ? dayjs.tz(event.target.value, selectedTimezone).toISOString() 
// UI Timezone -> Utc -> yyyy-mm-ddThh:mm
-              : "",
-          },
-        })
-      }
+      onChange={(event) => {
+        const local = dayjs(event.target.value).isValid() ? event.target.value 
: "";
+
+        setDisplayDate(local);
+        // Parse input to UTC once user finishes typing
+        debouncedOnDateChange(event);
+      }}
       ref={ref}
       type="datetime-local"
-      value={displayValue}
+      value={displayDate}
       {...rest}
     />
   );

Reply via email to