whenever Integration

activemodel has built-in support for the whenever datetime library. Five types are supported:

whenever type

SQLAlchemy column type

Notes

Instant

TIMESTAMP WITH TIME ZONE

UTC-normalized point in time

ZonedDateTime

TIMESTAMP WITH TIME ZONE

IANA timezone name is not preserved in the DB

PlainDateTime

TIMESTAMP WITHOUT TIME ZONE

No timezone; matches SQLite/naive datetime

Date

DATE

Calendar date with no time component

Time

TIME

Time of day with no date or timezone

Defining fields on a model

Import and annotate fields with the bare whenever type. The SQLAlchemy column type is resolved automatically via the get_sqlalchemy_type patch that activemodel applies on import.

from whenever import Instant, PlainDateTime, ZonedDateTime
from activemodel import BaseModel
from activemodel.mixins import TypeIDPrimaryKey

class Event(BaseModel, table=True):
    id: str = TypeIDPrimaryKey("event")
    occurred_at: Instant
    scheduled_at: ZonedDateTime | None = None
    local_time: PlainDateTime | None = None

Instant and ZonedDateTime both map to TIMESTAMP WITH TIME ZONE on Postgres. The difference is that Instant is always UTC-normalized whereas ZonedDateTime carries a timezone at the application level. Neither preserves the original IANA timezone name in the database — on read, ZonedDateTime is reconstructed from the stored UTC offset.

PlainDateTime maps to TIMESTAMP WITHOUT TIME ZONE and has no timezone at all, matching the behavior of SQLite and Postgres naive datetimes.

Reading and writing

Values round-trip transparently through SQLAlchemy:

from whenever import Instant, ZonedDateTime

event = Event(
    occurred_at=Instant.now(),
    scheduled_at=ZonedDateTime.now_in_system_tz(),
).save()

fetched = Event.get(event.id)
assert isinstance(fetched.occurred_at, Instant)
assert isinstance(fetched.scheduled_at, ZonedDateTime)

Factories

ActiveModelFactory registers providers for all three types automatically, so any factory subclass generates valid values without extra configuration:

from activemodel.pytest.factories import ActiveModelFactory
from my_app.models import Event

class EventFactory(ActiveModelFactory[Event]):
    __model__ = Event

event = EventFactory.build()
assert isinstance(event.occurred_at, Instant)

Generated values use the system timezone:

  • InstantInstant.now()

  • ZonedDateTimeZonedDateTime.now_in_system_tz()

  • PlainDateTimeZonedDateTime.now_in_system_tz().to_plain()

To override a field with a specific value, pass it as a keyword argument:

from whenever import Instant

past_event = EventFactory.build(occurred_at=Instant.now().subtract(hours=24))

Pydantic JSON schema

Date and Time validate and serialize correctly in Pydantic models but will not carry a "format" hint in the generated JSON schema — they produce {"type": "string"} rather than {"type": "string", "format": "date"}. This is a limitation of whenever.Date and whenever.Time being Rust extension types; Python cannot add __get_pydantic_json_schema__ to them.