Preserve original timezone vs. automatic UTC conversion in Matplotlib date handling
0 reputation · 13 Nov 2022, 22:18 UTC
0 reputation · 13 Nov 2022, 22:18 UTC
Determine whether Matplotlib should retain the original timezone information of datetime inputs on the Axis object to simplify local‑time tick labeling, rather than requiring users to write custom Formatters that convert UTC‑based numbers back to a desired zone.
The current implementation converts all timezone‑aware datetimes to UTC via date2num, stripping tzinfo and storing only the UTC‑based floating‑point day value. Changing this behavior must preserve backward compatibility, avoid breaking existing scripts that rely on the UTC representation, and correctly handle daylight‑saving transitions when mixing naïve and aware timestamps.
Key considerations are the added complexity of storing tzinfo per axis, the potential need for a new axis.timezone attribute, and whether a built‑in local‑time formatter would be preferable to exposing raw tzinfo.
axis.timezone attribute to preserve the original zone?33275 reputation · 14 Nov 2022, 07:11 UTC
Matplotlib currently normalizes all timezone-aware datetime inputs to UTC floating-point day numbers via date2num, stripping the original tzinfo before storage. It does not expose an axis.timezone attribute today. Adding one is a reasonable, backward-compatible enhancement if it is confined to the tick-formatting path and leaves the underlying UTC floats unchanged.
Confirmed: When you pass an aware datetime to plot(), Matplotlib converts it to a UTC-based float. The default AutoDateFormatter renders those floats as UTC. To display another zone today, you must supply a custom formatter that calls num2date(value, tz=desired_zone).
Proposed (design): An optional axis.timezone attribute would let built-in formatters apply the stored zone during rendering. This reduces boilerplate without altering the internal representation.
Changing date2num to retain local-time floats would break every script that assumes the stored numbers are UTC days. Keeping the conversion in UTC and consulting axis.timezone only when formatting ticks preserves the existing contract. Users who never set the attribute see identical output.
A deterministic policy is required before shipping such a feature. The least surprising rule is to treat naïve timestamps as UTC and emit a warning when they are mixed with aware timestamps on the same axis. Storing per-point timezone metadata would complicate the Axis model and increase memory overhead for little gain.
You can verify the current UTC behavior and the custom-formatter workaround with these scoped checks:
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from datetime import datetime, timezone, timedelta
# Current behavior: aware datetime becomes UTC float
dt = datetime(2026, 10, 15, 12, 0, tzinfo=timezone(timedelta(hours=-4)))
num = mdates.date2num(dt)
print(mdates.num2date(num)) # UTC-based datetime
# Workaround: local-time ticks via FuncFormatter
fig, ax = plt.subplots()
ax.plot([dt], [1])
ax.xaxis.set_major_formatter(
mdates.DateFormatter('%Y-%m-%d %H:%M %Z')
)
# For a specific zone, use FuncFormatter with num2date(tz=...)
plt.show()
Adopt axis.timezone as an opt-in formatting hint, not a data-storage change. Default it to None (current UTC behavior). When set, AutoDateFormatter should pass it to num2date. For mixed inputs, treat naïve values as UTC and warn. This keeps the API surface small, preserves backward compatibility, and eliminates the most common timezone boilerplate.
Missing detail: Are you proposing an upstream API change or working within a released Matplotlib version? If the latter, use the FuncFormatter workaround now; if the former, the formatting-only axis.timezone design above is the backward-compatible path.
Assumption: This reflects the long-standing Matplotlib 3.x architecture. If you are targeting a version where an axis.timezone attribute already exists, confirm whether it is formatting-only before relying on it.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.