# How Herbie Handles Time Zone Conversions and Datetimes for Forecasts

> Learn how Herbie handles time zone conversions and datetimes for forecasts. Herbie standardizes times to UTC for consistent calculations and user localization.

- Repository: [Brian Blaylock/herbie](https://github.com/blaylockbk/herbie)
- Tags: how-to-guide
- Published: 2026-02-26

---

**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`](https://github.com/blaylockbk/herbie/blob/main/src/herbie/core.py). The library intentionally removes timezone awareness to create a consistent baseline.

```python
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`](https://github.com/blaylockbk/herbie/blob/main/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:

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

```

This validation appears at lines 390-392 of [`src/herbie/core.py`](https://github.com/blaylockbk/herbie/blob/main/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`](https://github.com/blaylockbk/herbie/blob/main/src/herbie/latest.py) and [`src/herbie/fast.py`](https://github.com/blaylockbk/herbie/blob/main/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.

```python

# 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:

```python
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:

```python
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`](https://github.com/blaylockbk/herbie/blob/main/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`](https://github.com/blaylockbk/herbie/blob/main/src/herbie/latest.py)** – Implements `HerbieLatest` and `HerbieWait` functions that generate UTC timestamps and strip timezone info before instantiation.
- **[`src/herbie/fast.py`](https://github.com/blaylockbk/herbie/blob/main/src/herbie/fast.py)** – Fast-lookup version `Herbie_latest` using `pd.Timestamp.now("utc").tz_localize(None)` for UTC normalization.
- **[`tests/util.py`](https://github.com/blaylockbk/herbie/blob/main/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`](https://github.com/blaylockbk/herbie/blob/main/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`](https://github.com/blaylockbk/herbie/blob/main/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.