Skip to main content

unit_of_time

PyPI - Version Coverage

unit_of_time is a small package to represents time ranges through an int, this means we can easily store these, for example in a database. It also offers functions to determine the previous and next time range.

Time units

The package by default comes with a year, quarter, month, week and day as time units. We want to be able to convert a certain week into an int, and back. For this, we have defined the following rule:

  • a time range should encode to one integer, not per se the other way around;
  • the time ranges should be orderable such that we can easily order and sort items in the database; and
  • it should be easy for humans to read the integers.

For this we have defined the following format:

YYYYMMDDK

Here YYYY-MM-DD is the start date of the time range, and K is the "kind" of time range. The longer a time unit, the lower its kind.

So a year for example has kind 1, this means that we encode the year 2025 as 202501011, the month january 2025 is encoded as 202501015.

If we thus order the time units, we first sort on the start of the time unit, and the ordering will put longer time units first, so the "year 2025 starts 'earlier' than the quarter 2025Q1 which starts earlier than 2025-01".

Utility functions

The package provides some utility functions to make working with the time units more convenient.

Moving forward and backward

For example when we construct a year with

from datetime import date
from unit_of_time import Year

year1958 = Year(date(1958, 3, 25))

we can go to the next and previous year with:

year1959 = year1958.next
year1957 = year1958.previous

one can also use .ancestors and .successors which are generators that will keep proposing previous and next time units respectively, so we can walk over the years since 1958 with:

for year in year1958.successors:
  print(year)

Membership checks

We can also determine if a date, or another time range is fully enclosed by another one, for example:

from datetime import date
from unit_of_time import Month, Year

Month(date(1958, 3, 25)) in Year(date(1958, 3, 25))  # True
Month(date(2019, 11, 25)) in Year(date(1958, 3, 25))  # False

or check if there is overlap between two time units, especially since weeks are not always fully enclosed by the month, quarter, etc. when the week starts or ends. For example:

from datetime import date
from unit_of_time import Week, Year

Week(date(1957, 12, 31)).overlaps_with(Year(date(1958, 1, 1)))  # True

since the week with 1957-12-31 starts on December 30th.

Ordering

We can also check if one time unit starts before another time unit if these are of the same kind, like:

from datetime import date
from unit_of_time import Week, Week

Week(date(1957, 12, 31)) <= Week(date(1958, 3, 25))

Time units as a collection of dates

A time unit itself is iterable: it will yield all dates contained by the time unit. For example we can get all dates of 1958Q1 with:

from datetime import date
from unit_of_time import Quarter

for dt in Quarter(date(1958, 3, 25)):
  print(dt)  # 1958-01-01 to 1958-03-31

we can also convert such collection to a list.

Subscripting

The Day, Week, Month, etc. classes have .get_index_for_date(…)and.get_date_from_index(…)methods, which allow to determine how many days, weeks, months, quarters and years are betweendate.min` and the date given, and convert this back to a date. For example:

Week.get_index_for_date(date(1958, 3, 25))  # 102123
Week.get_date_from_index(102123)  # date(1958, 3, 24)

so 1958-03-25 is the 102'123 week since 0001-01-01, and that week starts the 24th of March, 1958.

We can also use the index to get a TimUnit with:

Week[102123]  # Week(date(1958, 3, 24))

moreover a week itself can be subscripted, for example:

Week(date(1958, 3, 24))[2]  # date(1958, 3, 26)

one can also slice to created an object that is a sliced "view" that generates Weeks or dates in the week respectively. This view can then be sliced or indexed further. For example:

Week[102123:105341:2]

is a collection of Week objects between 1958-03-24 and 2019-11-25 each time with one week in between.

The Week class itself is also iterable, for example:

for week in Week:
    print(week)

will start enumerating over all weeks since 0001-01-01.

A time unit also has a length: the number of time units that can be represented, so:

len(Week)  # 521722

means the software can represent 521'722 weeks from 0001-01-01 to 9999-12-26.

Shifting units of time

The units of time can also be shifted, for example:

from datetime import date
from unit_of_time import Day, Month, Quarter, Week, Year

Year(date(1958, 3, 25)) << 1  # Year(date(1959, 1, 1))
Quarter(date(1958, 3, 25)) >> 2  # Quarter(date(1957, 7, 1))
3 >> Month(date(1958, 3, 25))  # Month(date(1958, 6, 1))
4 << Week(date(1958, 3, 25))  # Week(date(1958, 2, 24))

Year(date(1958, 3, 25)) << -1  # Year(date(1957, 1, 1))
Quarter(date(1958, 3, 25)) >> -2  # Quarter(date(1958, 7, 1))
-3 >> Month(date(1958, 3, 25))  # Month(date(1958, 12, 1))
-4 << Week(date(1958, 3, 25))  # Week(date(1958, 4, 21))

so we can add or subtract a given number of years, quarters, months, weeks, and days from a given unit of time.

Hash and index

A time unit is hashable, it uses the int representation as hash. It is also indexable, and uses the int representation.

We can thus make a (very) long list, and work with:

from datetime import date
from unit_of_time import Day

specials_unit_of_times = [False] * 202512319
specials_unit_of_times[Day(date(1958, 3, 25))] = True

we can even use this to slice, although it probably is not very useful.

Registering a new time unit

We can register a new time unit. For example, a decade with:

from datetime import date
from unit_of_time import TimeunitKind

class Decade(TimeunitKind):
    kind_int = 0
    formatter = "%Ys"

    @classmethod
    def truncate(cls, dt):
        return date(dt.year - dt.year % 10, 1, 1)

    @classmethod
    def _next(cls, dt):
        return date(dt.year + 10, 1, 1)

Subclassing TimeunitKind will automatically register it. One has to fill in the kind_int, which is an integer, preferrably between 0 and 9, although one can register outside the range. If that is the case, the "kind" will take two or more digits when converting to an int.

One can also implement a formatter. This is strictly speaking not necessary, since one can also implement a .to_str() method:

from unit_of_time import TimeunitKind

class Decade(TimeunitKind):
    kind_int = 0
    
    def to_str(cls, dt):
        return dt.strftime('%Ys')

    # ...

this might be useful if the formatting is more advanced than what Python's date formatter can handle.

Furthermore, one implements the .truncate(…) class method to convert a date to the start of the date range, and the _next(…) which returns the first date for the next decade.

With these functions, we have registered a new time unit.

Pre-installed time units

The package ships with the following time units:

  • 1: year;
  • 3: quarter;
  • 5: month;
  • 7: week; and
  • 9: day.

There is deliberately always one integer between the two time units, such that one can always put a customized one between any of the two.

195803259 – 201911259

Metadata

Release files for unit-of-time 0.3.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for unit-of-time 0.3.0.0
File Size Uploaded Provenance
unit_of_time-0.3.0.0.tar.gz 18.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for unit-of-time 0.3.0.0
File Interpreter ABI Platform
unit_of_time-0.3.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 30.1 kB

Release files / unit_of_time-0.3.0.0.tar.gz

Download URL unit_of_time-0.3.0.0.tar.gz
Size 18.0 kB
Tags Source
SHA-256 checksum
How to use checksums
e73dd737ff657354638ec53328dcb1f9b18a16db9cafd921941c233b045e0b05
BLAKE2b-256 checksum
How to use checksums
bb547a2ae3069bc4ad8697e2737eb61ee57e5b7d0fcd034acc4ae76ae3a9962e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release files / unit_of_time-0.3.0.0-py3-none-any.whl

Download URL unit_of_time-0.3.0.0-py3-none-any.whl
Size 12.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
080443a45529226b896ae58147682a58f9e95882efba02056f4a917a18b29b81
BLAKE2b-256 checksum
How to use checksums
aa5fcfe95995200439012fa6d801ac6530638bd55a26f3f7d9d9f41aa3b2ea64
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Security for unit-of-time 0.3.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page