This is an automated email from the ASF dual-hosted git repository.
bbovenzi pushed a commit to branch v3-2-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-2-test by this push:
new 727f12c4d0c Fix millisecond floating point duration bug (#66560)
(#66915)
727f12c4d0c is described below
commit 727f12c4d0ce2e5564ef741bf5a4a5edfa9f82e9
Author: Rahul Vats <[email protected]>
AuthorDate: Thu May 14 19:53:04 2026 +0530
Fix millisecond floating point duration bug (#66560) (#66915)
* Fix millisecond floating point duration bug
(cherry picked from commit 52098c2b89f18ff77049aa4f1dcab4b922579d42)
Co-authored-by: Brent Bovenzi <[email protected]>
Co-authored-by: hojeong park <[email protected]>
---
.../src/airflow/ui/src/utils/datetimeUtils.test.ts | 24 ++++++++++++++++++---
.../src/airflow/ui/src/utils/datetimeUtils.ts | 25 ++++++++++++++--------
2 files changed, 37 insertions(+), 12 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.test.ts
b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.test.ts
index a0a39211257..dac233789d5 100644
--- a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.test.ts
+++ b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.test.ts
@@ -16,11 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
+import dayjs from "dayjs";
+import dayjsDuration from "dayjs/plugin/duration";
import { describe, it, expect, vi, beforeAll, afterAll } from "vitest";
import { getDuration, renderDuration, getRelativeTime } from "./datetimeUtils";
-describe("getDuration", () => {
+dayjs.extend(dayjsDuration);
+
+describe("getDuration & formatDuration", () => {
it("handles durations less than 60 seconds", () => {
const start = "2024-03-14T10:00:00.000Z";
const end = "2024-03-14T10:00:05.5111111Z";
@@ -72,11 +76,25 @@ describe("getDuration", () => {
const start = "2024-03-14T10:00:00.000Z";
// eslint-disable-next-line unicorn/no-null
- expect(getDuration(start, null)).toBe("00:00:10.000");
- expect(getDuration(start, undefined)).toBe("00:00:10.000");
+ expect(getDuration(start, null)).toBe("00:00:10");
+ expect(getDuration(start, undefined)).toBe("00:00:10");
vi.useRealTimers();
});
+
+ it("handles both numbers and duration objects", () => {
+ expect(renderDuration(dayjs.duration(10, "seconds"))).toBe("00:00:10");
+ expect(renderDuration(10)).toBe("00:00:10");
+ });
+
+ it("handles floating point milliseconds", () => {
+ expect(renderDuration(dayjs.duration(10.000_499_738,
"seconds"))).toBe("00:00:10");
+ expect(renderDuration(10.000_499_738)).toBe("00:00:10");
+ expect(renderDuration(dayjs.duration(10.000_500,
"seconds"))).toBe("00:00:10.001");
+ expect(renderDuration(10.000_500)).toBe("00:00:10.001");
+ expect(renderDuration(dayjs.duration(10.838_999_738,
"seconds"))).toBe("00:00:10.839");
+ expect(renderDuration(10.838_999_738)).toBe("00:00:10.839");
+ });
});
describe("getRelativeTime", () => {
diff --git a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts
b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts
index 773e6d2b072..77e08fa0a88 100644
--- a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts
+++ b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts
@@ -29,22 +29,29 @@ 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,
+ durationSeconds: dayjsDuration.Duration | number | null | undefined,
withMilliseconds: boolean = true,
): string | undefined => {
- if (durationSeconds === null || durationSeconds === undefined ||
durationSeconds <= 0.01) {
+ if (durationSeconds === null || durationSeconds === undefined) {
+ return undefined;
+ }
+
+ // Handle floating point milliseconds
+ const duration = dayjs.isDuration(durationSeconds)
+ ? dayjs.duration(Math.round(durationSeconds.asMilliseconds()))
+ : dayjs.duration(Number(durationSeconds.toFixed(3)), "seconds");
+
+ if (duration.asMilliseconds() < 1) {
return undefined;
}
// If under 60 seconds, render milliseconds
- if (durationSeconds < 60 && withMilliseconds) {
- return dayjs.duration(Number(durationSeconds.toFixed(3)),
"seconds").format("HH:mm:ss.SSS");
+ if (duration.asSeconds() < 60 && duration.milliseconds() > 0 &&
withMilliseconds) {
+ return duration.format("HH:mm:ss.SSS");
}
// If under 1 day, render as HH:mm:ss otherwise include the number of days
- return durationSeconds < 86_400
- ? dayjs.duration(durationSeconds, "seconds").format("HH:mm:ss")
- : dayjs.duration(durationSeconds, "seconds").format("D[d]HH:mm:ss");
+ return duration.asSeconds() < 86_400 ? duration.format("HH:mm:ss") :
duration.format("D[d]HH:mm:ss");
};
export const getDuration = (
@@ -57,9 +64,9 @@ export const getDuration = (
}
const end = endDate ?? dayjs().toISOString();
- const seconds = dayjs.duration(dayjs(end).diff(startDate)).asSeconds();
+ const milliseconds = dayjs.duration(dayjs(end).diff(startDate));
- return renderDuration(seconds, withMilliseconds);
+ return renderDuration(milliseconds, withMilliseconds);
};
export const formatDate = (