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}
/>
);