Skip to content

Calendar view does not respect user-selected timezone #67477

@pyzo-ryzo

Description

@pyzo-ryzo

Under which category would you file this issue?

Airflow Core

Apache Airflow version

3.2.1

What happened and how to reproduce it?

The Calendar view on the DAG detail page does not respect the user's selected timezone from the UI timezone selector. All dates — both in the API query parameters and in the rendered calendar grid — are computed using either browser-local time or hardcoded UTC, while the rest of the UI (e.g. the "Start Date" column on the Runs tab) correctly converts timestamps to the user-selected timezone.

Observed behavior: Changing the timezone via the UI timezone selector has no effect on the Calendar view. Runs are grouped into the wrong day/hour cells for users whose selected timezone differs from UTC.

Comparison with Runs tab: On the Runs tab, the Start Date column properly updates when the user changes their timezone setting, confirming the timezone selector itself works correctly.

How to reproduce

  1. Set your Airflow UI timezone to something significantly offset from UTC (e.g. Asia/Tokyo — UTC+9, or America/Los_Angeles — UTC-7).
  2. Navigate to a DAG that has runs near midnight UTC.
  3. Open the Calendar tab → observe that runs are grouped by their UTC date/hour, not by the selected timezone.
  4. Switch to the Runs tab → observe that the Start Date column correctly shows times in the selected timezone.

Root cause analysis

The entire Calendar feature tree (Calendar.tsx, calendarUtils.ts, HourlyCalendarView.tsx, DailyCalendarView.tsx) was written using plain dayjs() without any awareness of the useTimezone() / TimezoneContext system that the rest of the Airflow UI uses. The dayjs/plugin/timezone and dayjs/plugin/utc plugins are not even imported in any of these files.

There are three specific problem areas:

1. Calendar.tsx — API query dates hardcode a literal "Z" suffix

// Current code (Calendar.tsx)
const gte = startDate.format("YYYY-MM-DD[T]HH:mm:ss[Z]");
const lte = endDate.format("YYYY-MM-DD[T]HH:mm:ss[Z]");

[Z] in a dayjs format string is a literal character escape — it unconditionally appends the letter Z (meaning "UTC") to whatever the browser-local time value is. This tells the API the timestamp is UTC when it is not. The component never calls useTimezone().

Compare with the Time component (used by the Runs tab), which correctly does:

// Time.tsx — correct pattern
const { selectedTimezone } = useTimezone();
const formattedTime = time.tz(selectedTimezone).format(format);

2. calendarUtils.ts — Run grouping uses raw UTC string slicing

// createDailyDataMap
const dateStr = run.date.slice(0, 10);  // "YYYY-MM-DD" — always UTC
 
// createHourlyDataMap
const hourStr = run.date.slice(0, 13);  // "YYYY-MM-DDTHH" — always UTC

If the API returns a UTC timestamp like 2024-01-15T23:30:00Z, this groups the run into January 15. But for a user in Asia/Tokyo (UTC+9), that run occurred on January 16 at 08:30 and should appear in that day's cell.

The generateDailyCalendarData and generateHourlyCalendarData functions also build their calendar grids using plain dayjs() (browser-local), with no timezone parameter.

3. HourlyCalendarView.tsx / DailyCalendarView.tsx — Display formatting uses plain dayjs

Both views format display labels (day number, weekday name, month abbreviation) with plain dayjs(day.day) calls that don't go through timezone conversion.

Suggested fix

Thread selectedTimezone from the useTimezone() hook through the component tree:

  • Calendar.tsx: Import useTimezone, compute the date range in the selected timezone, convert to UTC for the API query.
  • calendarUtils.ts: Add a timezone parameter to createDailyDataMap, createHourlyDataMap, generateDailyCalendarData, generateHourlyCalendarData, and calculateDataBounds. Use dayjs(run.date).tz(timezone) instead of string slicing for grouping.
  • Views: Pass the timezone through and use it for display formatting.
    This follows the same pattern already established by src/components/Time.tsx and src/utils/datetimeUtils.ts.

Affected files

  • airflow-core/src/airflow/ui/src/pages/Dag/Calendar/Calendar.tsx
  • airflow-core/src/airflow/ui/src/pages/Dag/Calendar/calendarUtils.ts
  • airflow-core/src/airflow/ui/src/pages/Dag/Calendar/HourlyCalendarView.tsx
  • airflow-core/src/airflow/ui/src/pages/Dag/Calendar/DailyCalendarView.tsx

What you think should happen instead?

The Calendar view should respect the user-selected timezone (from useTimezone() / TimezoneContext) consistently with the rest of the UI. Specifically:

  1. The date range sent to the API should be computed in the selected timezone and then converted to UTC.
  2. Runs should be grouped into day/hour buckets based on the selected timezone, not by raw UTC string slicing.
  3. Display labels (day numbers, weekday names, month headers) should reflect the selected timezone.

Operating System

Red Hat Enterprise Linux 9.7

Deployment

Virtualenv installation

Apache Airflow Provider(s)

No response

Versions of Apache Airflow Providers

No response

Official Helm Chart version

Not Applicable

Kubernetes Version

Not Applicable

Helm Chart configuration

Not Applicable

Docker Image customizations

No response

Anything else?

The CalendarCell.tsx tooltip display may also need updating if it shows raw date strings, but the four files listed above are the core of the issue.

Are you willing to submit PR?

  • Yes I am willing to submit a PR!

Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:UIRelated to UI/UX. For Frontend Developers.kind:bugThis is a clearly a bugneeds-triagelabel for new issues that we didn't triage yet

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions