More human readable duration strings - #72350
Conversation
ryanahamilton
left a comment
There was a problem hiding this comment.
Read through the whole diff and ran the checks locally. The core band logic in getDurationParts is solid — I probed the boundaries hard (0.9996, 59.95, 3599.6, 86399.4, …) and the carry is correct at every one of them. I also pushed all 21 shipped locales through both Intl.NumberFormat(style: "unit") and Intl.ListFormat, narrow and long, with no throws. The move off zero-padded clock strings is a clear improvement, and driving the format from CLDR with no new translation keys is the right way to get there.
Local results, on 4b6f540:
| Check | Result |
|---|---|
pnpm exec tsc --p tsconfig.app.json |
pass |
pnpm exec eslint --quiet |
pass |
pnpm exec vitest run |
1150/1151 — the single failure, DateTimeInput.test.tsx, is an unrelated 5s timeout that passes in isolation |
One thing I'd call a bug, on getRelativeTime — it prints 60 minutes ago / 24 hours ago / 12 months ago where fromNow() said an hour ago / a day ago / a year ago. Details inline. The rest are layout budgets that were tuned for the always-English "1h 2m" and are now tight in the wider locales, plus some cleanup.
Three notes that don't anchor to a changed line:
src/utils/index.ts:21still re-exportsgetDuration/renderDuration. After this PR nothing imports them from the barrel, anduseDurationFormat's own doc says components must not use the raw functions — dropping the re-export would close the escape hatch the hook exists to eliminate.Backfills.tsxtakesformatElapsedas a third positional argument togetColumns, where the siblingDagRuns/TaskInstancescolumn builders take an options object. Trivial, but the three read differently now.- Newsfragment — there isn't one. This changes how every duration in the UI renders, so it may be worth an
airflow-core/newsfragments/72350.improvement.rst; entirely your call on whether it clears the user-facing bar.
Zero-padded clock strings buried the one number that mattered: a task that took 83ms read as 00:00:00.083, and anything under a second rendered as 00:00:00 in chart tooltips and Gantt axis labels, which is indistinguishable from no time at all. Someone scanning a column of durations wants the magnitude at a glance, and roughly three significant digits gives it to them at every scale from milliseconds to days. Unit names, decimal separators and plural forms now come from CLDR through Intl rather than being hardcoded English, so durations follow the language the rest of the UI is already using. This needs no new translation keys, and it reproduces Intl.DurationFormat's narrow style without requiring it, since that API is above this package's Node floor. Formatting goes through a hook bound to the active language because reading the language straight off the i18next singleton put it outside anything React tracks: a component with no reason of its own to subscribe to a language change kept rendering the previous locale indefinitely.
Relative times picked their unit from the unrounded gap and then rounded inside it, so anything just short of the next unit printed that unit's own ceiling: "60 minutes ago" where "an hour ago" was meant, and the same at 24 hours and 12 months. Promoting after the rounding matches what the duration path already does a few hundred lines up. Two layout budgets were sized when every duration was the English "1h 2m" and are too tight now that CLDR decides the width: the bar-end labels on the slowest-task chart reserved a fixed 64px, and the Gantt axis derived its tick spacing from an eight-character "HH:MM:SS" estimate. German narrow needs roughly half again as much room. The Gantt axis also placed ticks at evenly divided raw values, which the old truncating format hid; at this precision a seven-second span read 0s | 1.17s | 2.33s. Snapping to the same counted units the other duration axes use keeps short spans legible. Rounding to two units leaves "1h 2m" covering a full minute, which is too coarse to tell two similar runs apart in a list. The exact value now rides along in a title on the duration columns, so the compact form stays scannable without giving up the precise number.
4b6f540 to
da399ed
Compare
ryanahamilton
left a comment
There was a problem hiding this comment.
Had one more small comment and looks like you have a merge conflict. Overall looks like a nice enhancement.
| import { Text, type TextProps } from "@chakra-ui/react"; | ||
|
|
||
| import { renderDuration } from "src/utils"; | ||
| import { useDurationFormat } from "src/utils/useDurationFormat"; |
There was a problem hiding this comment.
It looks like src/utils has a barrel export file that didn't get amended for this new util. Would probably be ideal to add it and keep this pattern consistent.
| import { useDurationFormat } from "src/utils/useDurationFormat"; | |
| import { useDurationFormat } from "src/utils"; |
Problem:
our duration format was dd:hh:mm:ss.ms, which for short durations had a lot of excessive zeros or with many ms had floating point overflow.
Solution:
Switch to a more human readable format string that can be set per locale
Was generative AI tooling used to co-author this PR?
Claude Opus 5
{pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.