How Herbie Handles Time Zone Conversions and Datetimes for Forecasts

Herbie normalizes all model run times and forecast-valid times to naïve UTC timestamps internally, stripping timezone information to ensure consistent arithmetic while allowing users to localize results as needed.

The blaylockbk/herbie repository provides a Python interface for downloading numerical weather prediction data. Understanding how Herbie handles time zone conversions and datetimes for forecasts is essential for accurate data retrieval, as the library enforces a strict UTC-only internal representation to avoid ambiguity across global model sources.

Internal UTC Normalization in Herbie

Converting User Input to Naïve UTC

When instantiating a Herbie object, any supplied date or valid_date parameter is processed through pandas.to_datetime in src/herbie/core.py. The library intentionally removes timezone awareness to create a consistent baseline.

self.date = pd.to_datetime(date)                 # ← naïve UTC

self.valid_date = pd.to_datetime(valid_date)     # ← naïve UTC

This conversion occurs at lines 36-44 of src/herbie/core.py, ensuring that regardless of whether the user passes a string, a datetime object, or a timezone-aware timestamp, the internal representation remains a naïve UTC timestamp.

Validating Run Times Against UTC Now

Herbie validates that requested model runs are not in the future by comparing against the current UTC time. The code explicitly strips timezone information from the current timestamp to maintain consistency with the internal naïve representation:

assert self.date < pd.Timestamp.utcnow().tz_localize(None)

This validation appears at lines 390-392 of src/herbie/core.py, preventing users from requesting forecasts that have not yet been initialized.

How Herbie Latest and Wait Functions Handle Time Zones

HerbieLatest and UTC Timestamp Generation

The HerbieLatest class and related helper functions in src/herbie/latest.py and src/herbie/fast.py automatically generate candidate run times using UTC timestamps, then explicitly remove timezone awareness before passing values to the core Herbie class.


# From src/herbie/latest.py

pd.Timestamp.utcnow().floor(freq).tz_localize(None)

# From src/herbie/fast.py

pd.Timestamp.now("utc").tz_localize(None).floor(freq)

These patterns ensure that even when searching for the most recent available model run, the resulting Herbie object maintains the expected naïve UTC timestamp in its date attribute.

HerbieWait and Polling Intervals

The HerbieWait function extends this pattern by waiting for future model runs to become available. It generates target times using UTC references:

pd.Timestamp("now", tz="utc").floor('1h').replace(tzinfo=None)

This approach allows the polling mechanism to check remote data sources using consistent UTC-based filenames and directory structures while maintaining internal consistency with the naïve timestamp requirement.

Working with Herbie Datetimes in Local Time Zones

While Herbie enforces naïve UTC internally, you can convert output timestamps to localized time zones for display or analysis. Use pytz or zoneinfo to attach timezone information after retrieval:

import pytz
from herbie import Herbie

h = Herbie(date="2024-04-15 12:00", model="gfs")
utc_ts = h.date  # Naïve UTC timestamp

# Convert to local time zone

local_ts = utc_ts.tz_localize(pytz.UTC).astimezone(pytz.timezone("America/Chicago"))
print(local_ts)  # 2024-04-15 07:00:00-05:00

This pattern preserves Herbie's internal consistency while allowing integration with timezone-aware applications.

Key Source Files for Time Zone Handling

  • src/herbie/core.py – Central Herbie class that parses, validates, and stores run/valid datetimes as naïve UTC (lines 36-44, 390-392).
  • src/herbie/latest.py – Implements HerbieLatest and HerbieWait functions that generate UTC timestamps and strip timezone info before instantiation.
  • src/herbie/fast.py – Fast-lookup version Herbie_latest using pd.Timestamp.now("utc").tz_localize(None) for UTC normalization.
  • tests/util.py – Contains is_time_between utility that works with timezone-aware datetime.now() but normalizes to naïve time objects for comparison.

Summary

  • Herbie converts all user-supplied dates to naïve UTC timestamps using pandas.to_datetime, removing any timezone awareness.
  • The library validates model run times against pd.Timestamp.utcnow().tz_localize(None) to prevent future-date requests.
  • Helper functions like HerbieLatest and HerbieWait generate candidate times from UTC sources, then explicitly strip timezone info before passing to the core class.
  • Users must manually localize timestamps after retrieval if local time zone display is required, using tz_localize() and astimezone().

Frequently Asked Questions

Does Herbie support timezone-aware datetime objects?

Herbie accepts timezone-aware datetimes as input but immediately converts them to naïve UTC timestamps internally. The pandas.to_datetime call in src/herbie/core.py strips timezone information, ensuring consistent internal representation regardless of input format.

How do I convert Herbie's naïve UTC timestamps to my local time zone?

Attach UTC timezone information using tz_localize('UTC') or pytz.UTC, then convert to your target zone with astimezone(). For example: h.date.tz_localize('UTC').astimezone('America/Denver') returns a timezone-aware datetime in Mountain Time.

What happens if I pass a future date to Herbie?

Herbie raises an AssertionError if the requested model run time is not earlier than the current UTC moment. The validation at lines 390-392 of src/herbie/core.py compares self.date against pd.Timestamp.utcnow().tz_localize(None) to prevent requests for forecasts that do not yet exist.

Which Herbie functions automatically generate UTC timestamps?

The HerbieLatest, Herbie_latest, and HerbieWait functions automatically generate candidate run times using pd.Timestamp.utcnow() or pd.Timestamp.now("utc"), then strip timezone awareness with .tz_localize(None) or .replace(tzinfo=None) before instantiating the core Herbie class.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →