# How Prefect Handles Timezone-Aware Datetime Conversions with the whenever Library

> Prefect streamlines timezone-aware datetime conversions. Discover how it leverages the whenever library for seamless handling on Python 3.13+ and maintains compatibility.

- Repository: [Prefect/prefect](https://github.com/PrefectHQ/prefect)
- Tags: how-to-guide
- Published: 2026-07-13

---

**Prefect centralizes timezone-aware datetime handling in [`src/prefect/types/_datetime.py`](https://github.com/PrefectHQ/prefect/blob/main/src/prefect/types/_datetime.py), automatically switching to the `whenever` library on Python 3.13+ while maintaining backward compatibility through abstraction helpers that convert between `whenever` objects and standard library `datetime` instances.**

The Prefect workflow orchestration library (PrefectHQ/prefect) recently modernized its datetime internals to leverage the `whenever` package for robust timezone and DST handling. By isolating conversion logic behind version-agnostic utility functions, Prefect ensures that flow runs, schedules, and task executions behave consistently across Python versions. This implementation guarantees correct UTC coercion and DST transitions regardless of whether the underlying runtime uses `pendulum` or `whenever`.

## Detecting the whenever API Version

Prefect dynamically detects which `whenever` API is available at import time to determine the appropriate code paths.

The boolean flag `_WHENEVER_NEW_API` checks for the presence of the `to_stdlib` method on `whenever.ZonedDateTime` (lines 15-25 in [`src/prefect/types/_datetime.py`](https://github.com/PrefectHQ/prefect/blob/main/src/prefect/types/_datetime.py)). This detection mechanism allows Prefect to support both legacy and modern `whenever` interfaces without breaking existing deployments.

When the new API is available, Prefect uses direct method calls like `.to_stdlib()` for conversions. Otherwise, it falls back to legacy methods such as `.py_datetime()` and classmethods like `from_py_datetime`.

## Core Conversion Helpers

Prefect implements three primary helper functions to bridge `whenever` objects and Python's standard library `datetime` types.

### Converting whenever to Standard Library

The `_whenever_to_stdlib()` function (lines 59-68) handles the conversion of `whenever` objects to `datetime.datetime` instances:

```python
from whenever import ZonedDateTime
from prefect.types import _whenever_to_stdlib

zdt = ZonedDateTime.from_isoformat("2024-03-10T02:30:00-05:00")
std_dt = _whenever_to_stdlib(zdt)
print(type(std_dt))  # <class 'datetime.datetime'>

```

This helper first attempts to use the newer `to_stdlib()` method when `_WHENEVER_NEW_API` is `True`. If unavailable, it gracefully falls back to the older `py_datetime()` method.

### Creating ZonedDateTime Objects

The `_whenever_zdt_from_py()` function (lines 71-81) constructs `whenever.ZonedDateTime` instances from standard library datetimes. When the new API is present, it passes the datetime directly to the constructor. On older API versions, it invokes the `from_py_datetime` classmethod.

Similarly, `_whenever_pdt_from_py()` (lines 84-94) creates `whenever.PlainDateTime` objects from naive datetimes using the same pattern detection logic.

## Parsing and Validation

Prefect coerces all datetime inputs into timezone-aware objects through centralized parsing and validation utilities.

### String Parsing with Timezone Fallback

The `parse_datetime()` function (lines 82-90) uses `dateutil.parse` to interpret datetime strings. If the parsed result lacks timezone information, Prefect automatically assumes UTC by applying `ZoneInfo("UTC")`. For Python versions older than 3.13, this function delegates to the `pendulum` parser instead.

### UTC Coercion on Validation

The `create_datetime_instance()` validator (lines 98-105) enforces timezone awareness for Prefect's custom `DateTime` Pydantic type. When it encounters a naive datetime, it automatically inserts UTC timezone information using `ZoneInfo("UTC")` before returning the instance.

## Current Time and Timestamp Handling

Prefect provides unified interfaces for obtaining the current time and converting timestamps that work across both `whenever` and `pendulum` implementations.

### Getting "Now" in Any Timezone

The `now()` function (lines 197-210) returns the current datetime in the specified timezone:

```python
from prefect.types import now

utc_now = now("UTC")
ny_now = now("America/New_York")  # Automatically handles DST

```

For Python 3.13+, this returns `datetime.datetime.now(ZoneInfo(tz))`. On older versions, it delegates to `pendulum.now()`.

### Timestamp Conversion

The `from_timestamp()` function (lines 9-15) builds timezone-aware datetimes from Unix timestamps. It accepts a timezone parameter and constructs the result using `ZoneInfo`, ensuring consistent behavior regardless of the underlying datetime library.

## Human-Friendly Formatting

The `human_friendly_diff()` function (lines 19-56) generates natural-language time differences while normalizing timezone handling.

This utility first ensures all datetimes use valid `ZoneInfo` objects, then delegates to the `humanize` library on Python 3.13+ or `pendulum` on older versions. This abstraction allows Prefect to display consistent "2 hours ago" or "in 3 days" messaging across the UI and CLI.

```python
from prefect.types import human_friendly_diff
import datetime

past = datetime.datetime(2023, 5, 1, 9, 0, tzinfo=datetime.timezone.utc)
print(human_friendly_diff(past))  # "2 years ago"

```

## whenever in Server Schemas

Prefect leverages these datetime utilities in server-side components to ensure schedule accuracy.

In [`src/prefect/server/schemas/schedules.py`](https://github.com/PrefectHQ/prefect/blob/main/src/prefect/server/schemas/schedules.py) (lines 181-192), schedule literals are converted into `whenever.ZonedDateTime` objects. This conversion guarantees correct DST handling for cron schedules and interval-based triggers on Python 3.13+, preventing the ambiguous or non-existent time errors that traditionally plague workflow schedulers during daylight saving transitions.

## Summary

- Prefect detects `whenever` API capabilities at import time via `_WHENEVER_NEW_API` to support both legacy and modern interfaces.
- The `_whenever_to_stdlib()` and `_whenever_zdt_from_py()` helpers abstract conversions between `whenever` objects and standard library datetimes.
- All naive datetimes are automatically coerced to UTC through `create_datetime_instance()` validation.
- The `now()` and `from_timestamp()` functions provide version-agnostic ways to get current time and convert timestamps.
- Server-side schedule schemas use `whenever.ZonedDateTime` to handle DST transitions correctly on Python 3.13+.

## Frequently Asked Questions

### What is the difference between whenever and pendulum in Prefect?

**`whenever` is a modern datetime library that provides first-class support for timezone-aware arithmetic and DST handling, while `pendulum` is the legacy library used in older Python versions.** Prefect automatically uses `whenever` on Python 3.13+ and falls back to `pendulum` on earlier versions, maintaining identical behavior through abstraction layers in [`src/prefect/types/_datetime.py`](https://github.com/PrefectHQ/prefect/blob/main/src/prefect/types/_datetime.py).

### Does Prefect require Python 3.13 to use whenever?

**No, Prefect supports both libraries across Python versions, but `whenever` is the default implementation only on Python 3.13 and newer.** On older Python versions, Prefect continues to use `pendulum` while providing the same public API and timezone guarantees.

### How does Prefect handle daylight saving time transitions?

**Prefect uses `whenever.ZonedDateTime` objects in schedule calculations to properly handle DST transitions.** According to the source code in [`src/prefect/server/schemas/schedules.py`](https://github.com/PrefectHQ/prefect/blob/main/src/prefect/server/schemas/schedules.py), converting schedule literals to `ZonedDateTime` ensures that cron schedules and interval triggers execute at the correct local time even during spring-forward or fall-back transitions.

### Can I use standard library datetime objects with Prefect's DateTime type?

**Yes, Prefect automatically converts standard library `datetime` objects to its internal `DateTime` type through the `create_datetime_instance()` validator.** If you pass a naive datetime, Prefect assumes UTC. If you pass a timezone-aware datetime, it preserves the timezone information while ensuring compatibility with the underlying `whenever` or `pendulum` implementation.