Welcome to tempora documentation!

For Enterprise

Professional support for tempora is available as part of the Tidelift Subscription. Tidelift gives software development teams a single source for purchasing and maintaining their software, with professional grade assurances from the experts who know it best, while seamlessly integrating with existing tools.

Learn more Request a Demo

Objects and routines pertaining to date and time (tempora)

tempora.calculate_prorated_values() None
>>> monkeypatch = getfixture('monkeypatch')
>>> import builtins
>>> monkeypatch.setattr(builtins, 'input', lambda prompt: '3/hour')
>>> calculate_prorated_values()
per minute: 0.05
per hour: 3.0
per day: 72.0
per month: 2191.454166666667
per year: 26297.45
tempora.date_range(start=None, stop=None, step=None) Iterator[datetime]

Much like the built-in function range, but works with dates

>>> range_items = date_range(
...     datetime.datetime(2005,12,21),
...     datetime.datetime(2005,12,25),
... )
>>> my_range = tuple(range_items)
>>> datetime.datetime(2005,12,21) in my_range
True
>>> datetime.datetime(2005,12,22) in my_range
True
>>> datetime.datetime(2005,12,25) in my_range
False
>>> from_now = date_range(stop=datetime.datetime(2099, 12, 31))
>>> next(from_now)
datetime.datetime(...)
tempora.datetime_mod(dt: datetime, period, start=None) datetime

Find the time which is the specified date/time truncated to the time delta relative to the start date/time. By default, the start time is midnight of the same day as the specified date/time.

>>> datetime_mod(datetime.datetime(2004, 1, 2, 3),
...     datetime.timedelta(days = 1.5),
...     start = datetime.datetime(2004, 1, 1))
datetime.datetime(2004, 1, 1, 0, 0)
>>> datetime_mod(datetime.datetime(2004, 1, 2, 13),
...     datetime.timedelta(days = 1.5),
...     start = datetime.datetime(2004, 1, 1))
datetime.datetime(2004, 1, 2, 12, 0)
>>> datetime_mod(datetime.datetime(2004, 1, 2, 13),
...     datetime.timedelta(days = 7),
...     start = datetime.datetime(2004, 1, 1))
datetime.datetime(2004, 1, 1, 0, 0)
>>> datetime_mod(datetime.datetime(2004, 1, 10, 13),
...     datetime.timedelta(days = 7),
...     start = datetime.datetime(2004, 1, 1))
datetime.datetime(2004, 1, 8, 0, 0)
tempora.datetime_round(dt, period: timedelta, start=None) datetime

Find the nearest even period for the specified date/time.

>>> datetime_round(datetime.datetime(2004, 11, 13, 8, 11, 13),
...     datetime.timedelta(hours = 1))
datetime.datetime(2004, 11, 13, 8, 0)
>>> datetime_round(datetime.datetime(2004, 11, 13, 8, 31, 13),
...     datetime.timedelta(hours = 1))
datetime.datetime(2004, 11, 13, 9, 0)
>>> datetime_round(datetime.datetime(2004, 11, 13, 8, 30),
...     datetime.timedelta(hours = 1))
datetime.datetime(2004, 11, 13, 9, 0)
tempora.ensure_datetime(ob: datetime | date | time) datetime

Given a datetime or date or time object from the datetime module, always return a datetime using default values.

tempora.get_date_format_string(period) str

For a given period (e.g. ‘month’, ‘day’, or some numeric interval such as 3600 (in secs)), return the format string that can be used with strftime to format that time to specify the times across that interval, but no more detailed. For example,

>>> get_date_format_string('month')
'%Y-%m'
>>> get_date_format_string(3600)
'%Y-%m-%d %H'
>>> get_date_format_string('hour')
'%Y-%m-%d %H'
>>> get_date_format_string(None)
Traceback (most recent call last):
    ...
TypeError: period must be a string or integer
>>> get_date_format_string('garbage')
Traceback (most recent call last):
    ...
ValueError: period not in (second, minute, hour, day, month, year)
tempora.get_nearest_year_for_day(day) int

Returns the nearest year to now inferred from a Julian date.

>>> freezer = getfixture('freezer')
>>> freezer.move_to('2019-05-20')
>>> get_nearest_year_for_day(20)
2019
>>> get_nearest_year_for_day(340)
2018
>>> freezer.move_to('2019-12-15')
>>> get_nearest_year_for_day(20)
2020
tempora.get_period_seconds(period) int

return the number of seconds in the specified period

>>> get_period_seconds('day')
86400
>>> get_period_seconds(86400)
86400
>>> get_period_seconds(datetime.timedelta(hours=24))
86400
>>> get_period_seconds('day + os.system("rm -Rf *")')
Traceback (most recent call last):
...
ValueError: period not in (second, minute, hour, day, month, year)
tempora.gregorian_date(year, julian_day) date

Gregorian Date is defined as a year and a julian day (1-based index into the days of the year).

>>> gregorian_date(2007, 15)
datetime.date(2007, 1, 15)
tempora.infer_datetime(ob: datetime | date | time | Tuple[int, ...] | struct_time) datetime
tempora.osc_per_year = 290091329207984000

mean vernal equinox year expressed in oscillations of atomic cesium at the year 2000 (see http://webexhibits.org/calendars/timeline.html for more info).

tempora.parse(*args, **kwargs) datetime

Parse the input using dateutil.parser.parse with friendly tz support.

>>> parse('2024-07-26 12:59:00 EDT')
datetime.datetime(...America/New_York...)
tempora.parse_timedelta(str) timedelta

Take a string representing a span of time and parse it to a time delta. Accepts any string of comma-separated numbers each with a unit indicator.

>>> parse_timedelta('1 day')
datetime.timedelta(days=1)
>>> parse_timedelta('1 day, 30 seconds')
datetime.timedelta(days=1, seconds=30)
>>> parse_timedelta('47.32 days, 20 minutes, 15.4 milliseconds')
datetime.timedelta(days=47, seconds=28848, microseconds=15400)

Supports weeks, months, years

>>> parse_timedelta('1 week')
datetime.timedelta(days=7)
>>> parse_timedelta('1 year, 1 month')
datetime.timedelta(days=395, seconds=58685)

Note that months and years strict intervals, not aligned to a calendar:

>>> date = datetime.datetime.fromisoformat('2000-01-01')
>>> later = date + parse_timedelta('1 year')
>>> diff = later.replace(year=date.year) - date
>>> diff.seconds
20940
>>> parse_timedelta('foo')
Traceback (most recent call last):
...
ValueError: Unexpected 'foo'
>>> parse_timedelta('14 seconds foo')
Traceback (most recent call last):
...
ValueError: Unexpected 'foo'

Supports abbreviations:

>>> parse_timedelta('1s')
datetime.timedelta(seconds=1)
>>> parse_timedelta('1sec')
datetime.timedelta(seconds=1)
>>> parse_timedelta('5min1sec')
datetime.timedelta(seconds=301)
>>> parse_timedelta('1 ms')
datetime.timedelta(microseconds=1000)
>>> parse_timedelta('1 µs')
datetime.timedelta(microseconds=1)
>>> parse_timedelta('1 us')
datetime.timedelta(microseconds=1)

And supports the common colon-separated duration:

>>> parse_timedelta('14:00:35.362')
datetime.timedelta(seconds=50435, microseconds=362000)

TODO: Should this be 14 hours or 14 minutes?

>>> parse_timedelta('14:00')
datetime.timedelta(seconds=50400)
>>> parse_timedelta('14:00 minutes')
Traceback (most recent call last):
...
ValueError: Cannot specify units with composite delta

Nanoseconds get rounded to the nearest microsecond:

>>> parse_timedelta('600 ns')
datetime.timedelta(microseconds=1)
>>> parse_timedelta('.002 µs, 499 ns')
datetime.timedelta(microseconds=1)

Expect ValueError for other invalid inputs.

>>> parse_timedelta('13 feet')
Traceback (most recent call last):
...
ValueError: Invalid unit feets
tempora.strftime(fmt: str, t: datetime | date | time | tuple | struct_time) str

Portable strftime.

In the stdlib, strftime has known portability problems. This function aims to smooth over those issues and provide a consistent experience across the major platforms.

>>> strftime('%Y', datetime.datetime(1890, 1, 1))
'1890'
>>> strftime('%Y', datetime.datetime(900, 1, 1))
'0900'

Supports time.struct_time, tuples, and datetime.datetime objects.

>>> strftime('%Y-%m-%d', (1976, 5, 7))
'1976-05-07'

Also supports date objects

>>> strftime('%Y', datetime.date(1976, 5, 7))
'1976'

Also supports milliseconds using %s.

>>> strftime('%s', datetime.time(microsecond=20000))
'020'

Also supports microseconds (3 digits) using %µ

>>> strftime('%µ', datetime.time(microsecond=123456))
'456'

Historically, %u was used for microseconds, but now it honors the value rendered by stdlib.

>>> strftime('%u', datetime.date(1976, 5, 7))
'5'

Also supports microseconds (6 digits) using %f

>>> strftime('%f', datetime.time(microsecond=23456))
'023456'

Even supports time values on date objects (discouraged):

>>> strftime('%f', datetime.date(1976, 1, 1))
'000000'
>>> strftime('%µ', datetime.date(1976, 1, 1))
'000'
>>> strftime('%s', datetime.date(1976, 1, 1))
'000'

And vice-versa:

>>> strftime('%Y', datetime.time())
'1900'

Timing

class tempora.timing.BackoffDelay(delay: float = 0, factor=1, limit: Callable[[float], float] | float = float('inf'), jitter: Callable[[], float] | float = 0)

Bases: Iterator

Exponential backoff delay.

Useful for defining delays between retries. Consider for use with jaraco.functools.retry_call as the cleanup.

Default behavior has no effect; a delay or jitter must be supplied for the call to be non-degenerate.

>>> bd = BackoffDelay()
>>> bd()
>>> bd()

The following instance will delay 10ms for the first call, 20ms for the second, etc.

>>> bd = BackoffDelay(delay=0.01, factor=2)
>>> bd()
>>> bd()

Inspect and adjust the state of the delay anytime.

>>> bd.delay
0.04
>>> bd.delay = 0.01

Set limit to prevent the delay from exceeding bounds.

>>> bd = BackoffDelay(delay=0.01, factor=2, limit=0.015)
>>> bd()
>>> bd.delay
0.015

To reset the backoff, simply call .reset():

>>> bd.reset()
>>> bd.delay
0.01

Iterate on the object to retrieve/advance the delay values.

>>> next(bd)
0.01
>>> next(bd)
0.015
>>> import itertools
>>> tuple(itertools.islice(bd, 3))
(0.015, 0.015, 0.015)

Limit may be a callable taking a number and returning the limited number.

>>> at_least_one = lambda n: max(n, 1)
>>> bd = BackoffDelay(delay=0.01, factor=2, limit=at_least_one)
>>> next(bd)
0.01
>>> next(bd)
1

Pass a jitter to add or subtract seconds to the delay.

>>> bd = BackoffDelay(jitter=0.01)
>>> next(bd)
0
>>> next(bd)
0.01

Jitter may be a callable. To supply a non-deterministic jitter between -0.5 and 0.5, consider:

>>> import random
>>> jitter=functools.partial(random.uniform, -0.5, 0.5)
>>> bd = BackoffDelay(jitter=jitter)
>>> next(bd)
0
>>> 0 <= next(bd) <= 0.5
True
bump() None
factor = 1

Multiplier applied to delay

jitter: Callable[[], float]

Callable returning extra seconds to add to delay

reset()
class tempora.timing.IntervalGovernor(min_interval)

Bases: object

Decorate a function to only allow it to be called once per min_interval. Otherwise, it returns None.

>>> gov = IntervalGovernor(30)
>>> gov.min_interval.total_seconds()
30.0
decorate(func)
class tempora.timing.Stopwatch

Bases: object

A simple stopwatch that starts automatically.

>>> w = Stopwatch()
>>> _1_sec = datetime.timedelta(seconds=1)
>>> w.split() < _1_sec
True
>>> time.sleep(1.0)
>>> w.split() >= _1_sec
True
>>> w.stop() >= _1_sec
True
>>> w.reset()
>>> w.start()
>>> w.split() < _1_sec
True

Launch the Stopwatch in a context:

>>> with Stopwatch() as watch:
...     assert isinstance(watch.split(), datetime.timedelta)

After exiting the context, the watch is stopped; read the elapsed time directly:

>>> watch.elapsed
datetime.timedelta(...)
>>> watch.elapsed.seconds
0
reset() None
split() timedelta
start() None
stop() timedelta
class tempora.timing.Timer(target=float('Inf'))

Bases: Stopwatch

Watch for a target elapsed time.

>>> t = Timer(0.1)
>>> t.expired()
False
>>> __import__('time').sleep(0.15)
>>> t.expired()
True
expired() bool

Schedule

Classes for calling functions a schedule. Has time zone support.

For example, to run a job at 08:00 every morning in ‘Asia/Calcutta’:

>>> import zoneinfo
>>> job = lambda: print("time is now", datetime.datetime())
>>> time = datetime.time(8, tzinfo=zoneinfo.ZoneInfo('Asia/Calcutta'))
>>> cmd = PeriodicCommandFixedDelay.daily_at(time, job)
>>> sched = InvokeScheduler()
>>> sched.add(cmd)
>>> while True:
...     sched.run_pending()
...     time.sleep(.1)

By default, the scheduler uses timezone-aware times in UTC. A client may override the default behavior by overriding now and from_timestamp functions.

>>> now()
datetime.datetime(...utc)
>>> from_timestamp(1718723533.7685602)
datetime.datetime(...utc)
class tempora.schedule.CallbackScheduler(dispatch)

Bases: Scheduler

Command targets are passed to a dispatch callable on schedule.

run(command: DelayedCommand) None

Run the command

class tempora.schedule.DelayedCommand

Bases: datetime

A command to be executed after some delay (seconds or timedelta).

classmethod after(delay, target) Self
classmethod at_time(at, target) Self

Construct a DelayedCommand to come due at at, where at may be a datetime or timestamp.

delay: timedelta = datetime.timedelta(0)
due() bool
classmethod from_datetime(other) Self
target: Any
class tempora.schedule.InvokeScheduler

Bases: Scheduler

Command targets are functions to be invoked on schedule.

run(command: DelayedCommand) None

Run the command

class tempora.schedule.PeriodicCommand

Bases: DelayedCommand

Like a delayed command, but expect this command to run every delay seconds.

next() Self
class tempora.schedule.PeriodicCommandFixedDelay

Bases: PeriodicCommand

Like a periodic command, but don’t calculate the delay based on the current time. Instead use a fixed delay following the initial run.

classmethod at_time(at, delay, target) Self
>>> cmd = PeriodicCommandFixedDelay.at_time(0, 30, None)
>>> cmd.delay.total_seconds()
30.0
classmethod daily_at(at, target) Self

Schedule a command to run at a specific time each day.

>>> from tempora import utc
>>> noon = utc.time(12, 0)
>>> cmd = PeriodicCommandFixedDelay.daily_at(noon, None)
>>> cmd.delay.total_seconds()
86400.0
class tempora.schedule.Scheduler

Bases: object

A rudimentary abstract scheduler accepting DelayedCommands and dispatching them on schedule.

add(command: DelayedCommand) None
abstractmethod run(command: DelayedCommand) None

Run the command

run_pending() None
tempora.schedule.from_timestamp()

timestamp[, tz] -> tz’s local time from POSIX timestamp.

UTC

Facilities for common time operations in UTC.

Inspired by the utc project.

>>> dt = now()
>>> dt == fromtimestamp(dt.timestamp())
True
>>> dt.tzinfo
datetime.timezone.utc
>>> from time import time as timestamp
>>> now().timestamp() - timestamp() < 0.1
True
>>> (now() - fromtimestamp(timestamp())).total_seconds() < 0.1
True
>>> datetime(2018, 6, 26, 0).tzinfo
datetime.timezone.utc
>>> time(0, 0).tzinfo
datetime.timezone.utc

Now should be affected by freezegun.

>>> freezer = getfixture('freezer')
>>> freezer.move_to('1999-12-31 17:00:00 -0700')
>>> print(now())
2000-01-01 00:00:00+00:00
tempora.utc.fromtimestamp()

timestamp[, tz] -> tz’s local time from POSIX timestamp.

tempora.utc.now() datetime

Indices and tables