Examples

This page contains small, practical examples of using whenever. For more in-depth information, refer to the Guide.

Get the current time in UTC

>>> from whenever import Instant
>>> Instant.now()
Instant("2025-04-19 19:02:56.39569Z")

Convert UTC to the system timezone

>>> from whenever import Instant
>>> i = Instant.now()
>>> i.to_system_tz()
ZonedDateTime("2025-04-19 21:02:56.39569+02:00[Europe/Berlin]")

Convert from one timezone to another

>>> from whenever import ZonedDateTime
>>> d = ZonedDateTime(2025, 4, 19, hour=15, tz="America/New_York")
>>> d.to_tz("Europe/Berlin")
ZonedDateTime("2025-04-19 21:00:00+02:00[Europe/Berlin]")

Convert a date to datetime

>>> from whenever import Date, Time
>>> date = Date(2023, 10, 1)
>>> date.at(Time(12, 30))
PlainDateTime("2023-10-01 12:30:00")

Calculate somebody’s age

>>> from whenever import Date
>>> birth_date = Date(2023, 11, 2)
>>> today = Date.today_in_system_tz()
>>> today.since(birth_date, total="years")
2.3753424657534246
>>> years, months = today.since(birth_date, in_units=("years", "months")).values()
(2, 4)

Assign a timezone to a datetime

>>> from whenever import PlainDateTime
>>> datetime = PlainDateTime(2023, 10, 1, 12, 30)
>>> datetime.assume_tz("America/New_York")
ZonedDateTime("2023-10-01 12:30:00-04:00[America/New_York]")

Integrate with the standard library

>>> import datetime
>>> py_dt = datetime.datetime.now(datetime.UTC)
>>> from whenever import Instant
>>> # create an Instant from any aware datetime
>>> i = Instant(py_dt)
Instant("2025-04-19 19:02:56.39569Z")
>>> zdt = i.to_tz("America/New_York")
ZonedDateTime("2025-04-19 15:02:56.39569-04:00[America/New_York]")
>>> # convert back to the standard library
>>> zdt.to_stdlib()
datetime.datetime(2025, 4, 19, 15, 2, 56, 395690, tzinfo=ZoneInfo('America/New_York'))

Parse an ISO8601 datetime string

>>> from whenever import Instant
>>> Instant("2025-04-19T19:02+04:00")
Instant("2025-04-19 15:02:00Z")

Or, if you want to keep the offset value:

>>> from whenever import OffsetDateTime
>>> OffsetDateTime("2025-04-19T19:02+04:00")
OffsetDateTime("2025-04-19 19:02:00+04:00")

Determine the start of the hour

>>> d = ZonedDateTime.now("America/New_York")
ZonedDateTime("2025-04-19 15:46:41-04:00[America/New_York]")
>>> d.round("hour", mode="floor")
ZonedDateTime("2025-04-19 15:00:00-04:00[America/New_York]")

The round() method can be used for so much more! See its documentation for more details.

Get the current unix timestamp

>>> from whenever import Instant
>>> i = Instant.now()
>>> i.timestamp()
1745090505

Note that this is always in whole seconds. If you need additional precision:

>>> i.timestamp_millis()
1745090505629
>>> i.timestamp_nanos()
1745090505629346833

Get a date and time from a timestamp

>>> from whenever import ZonedDateTime
>>> ZonedDateTime.from_timestamp(1745090505, tz="America/New_York")
ZonedDateTime("2025-04-19 15:21:45-04:00[America/New_York]")

Find the duration between two datetimes

>>> from whenever import ZonedDateTime
>>> d = ZonedDateTime(2025, 1, 3, hour=15, tz="America/New_York")
>>> d2 = ZonedDateTime(2025, 1, 5, hour=8, minute=24, tz="Europe/Paris")
>>> d2 - d
TimeDelta("PT35h24m")

Move a date by six months

>>> from whenever import Date
>>> date = Date(2023, 10, 31)
>>> date.add(months=6)
Date("2024-04-30")

Discard fractional seconds

>>> from whenever import Instant
>>> i = Instant.now()
Instant("2025-04-19 19:02:56.39569Z")
>>> i.round()
Instant("2025-04-19 19:02:56Z")

Use the arguments of round() to customize the rounding behavior.

Handling ambiguous datetimes

Due to daylight saving time, some date and time values don’t exist, or occur twice in a given timezone. In the example below, the clock was set forward by one hour at 2:00 AM, so the time 2:30 AM doesn’t exist.

>>> from whenever import ZonedDateTime
>>> # set up the date and time for the example
>>> dt = PlainDateTime(2023, 2, 26, hour=2, minute=30)

The default behavior (take the first offset) is consistent with other modern libraries and industry standards:

>>> zoned = dt.assume_tz("Europe/Berlin")
ZonedDateTime("2023-02-26 03:30:00+02:00[Europe/Berlin]")

But it’s also possible to “refuse to guess” and choose the “earlier” or “later” occurrence explicitly:

>>> zoned = dt.assume_tz("Europe/Berlin", disambiguate="earlier")
ZonedDateTime("2023-02-26 01:30:00+02:00[Europe/Berlin]")

Or, you can even reject ambiguous datetimes altogether:

>>> zoned = dt.assume_tz("Europe/Berlin", disambiguate="raise")

“Same time tomorrow” across DST

Adding a day keeps the wall-clock time, even when a DST transition makes the day shorter or longer than 24 hours:

>>> from whenever import ZonedDateTime
>>> # The night before Spring Forward in Amsterdam
>>> eve = ZonedDateTime(2025, 3, 30, hour=1, tz="Europe/Amsterdam")
>>> eve.add(days=1)     # same wall-clock time
ZonedDateTime("2025-03-31 01:00:00+02:00[Europe/Amsterdam]")
>>> eve.add(hours=24)   # exactly 24 hours — one hour later on the clock
ZonedDateTime("2025-03-31 02:00:00+02:00[Europe/Amsterdam]")

Countdown to New Year’s

>>> from whenever import ZonedDateTime
>>> now = ZonedDateTime(2025, 12, 28, hour=14, tz="America/New_York")
>>> new_year = ZonedDateTime(2026, 1, 1, tz="America/New_York")
>>> days, hours = new_year.since(now, in_units=("days", "hours")).values()
(3, 10)

Flight itinerary across time zones

>>> from whenever import OffsetDateTime
>>> departure = OffsetDateTime(2025, 7, 1, hour=9, offset=-4)   # New York
>>> arrival = OffsetDateTime(2025, 7, 1, hour=22, offset=2)     # Amsterdam
>>> flight_time = arrival - departure
>>> flight_time.total("hours")
7.0

Recurring monthly event

When a monthly recurrence lands on a day that doesn’t exist in the target month, the date is truncated to the last valid day:

>>> from whenever import Date
>>> meeting = Date(2025, 1, 31)
>>> meeting.add(months=1)  # February doesn't have 31 days
Date("2025-02-28")
>>> meeting.add(months=2)
Date("2025-03-31")

Sort a list of datetimes

All exact types can be compared and sorted amongst each other:

>>> from whenever import Instant, ZonedDateTime, OffsetDateTime
>>> times = [
...     ZonedDateTime(2025, 6, 1, hour=12, tz="Asia/Tokyo"),
...     Instant.from_utc(2025, 6, 1, hour=2),
...     OffsetDateTime(2025, 6, 1, hour=6, offset=4),
... ]
>>> sorted(times)  # all represent the same moment—sorted by the underlying instant
[...]

“Plain” datetimes cannot be mixed with exact types. This will be flagged by type checking.

Custom format patterns

For formats beyond ISO 8601, use pattern strings:

>>> from whenever import Date, PlainDateTime, OffsetDateTime
>>> Date.parse("15 Mar 2024", format="DD MMM YYYY")
Date("2024-03-15")
>>> PlainDateTime.parse("03/15/2024 02:30 PM", format="MM/DD/YYYY ii:mm aa")
PlainDateTime("2024-03-15 14:30:00")
>>> OffsetDateTime.parse("2024-03-15 14:30+02:00", format="YYYY-MM-DD hh:mmxxx")
OffsetDateTime("2024-03-15 14:30:00+02:00")

If your input doesn’t include an offset or timezone, parse with PlainDateTime.parse() and convert:

>>> from whenever import PlainDateTime
>>> pdt = PlainDateTime.parse("2024-03-15 14:30", format="YYYY-MM-DD hh:mm")
>>> pdt.assume_utc()
Instant("2024-03-15 14:30:00Z")

It also integrates nicely with the standard library’s formatting protocol (__format__), so you can use pattern strings in f-strings:

>>> from whenever import Date
>>> d = Date(2024, 3, 15)
>>> f"{d:DD/MM/YYYY}"
'15/03/2024'
>>> f"{d}"  # empty spec falls back to str()
'2024-03-15'

Roundtrip: datetime → string → datetime

Every whenever type has a reversible string representation:

>>> from whenever import ZonedDateTime
>>> d = ZonedDateTime(2025, 6, 15, hour=14, minute=30, tz="Europe/Amsterdam")
>>> s = str(d)
>>> s
'2025-06-15 14:30:00+02:00[Europe/Amsterdam]'
>>> ZonedDateTime(s) == d
True

For ISO 8601 exchange:

>>> iso = d.format_iso()
>>> ZonedDateTime.parse_iso(iso) == d
True