Python Package Index

local Python Package Index (PyPI)

corelibs-datetime (1.2.0)

Published 2026-01-06 09:43:24 +09:00 by clemens

Installation

pip install --index-url  --extra-index-url https://pypi.org/simple corelibs-datetime

About this package

CoreLibs DateTime handlers and helpers

CoreLibs DateTime

This is part of the Python CoreLibs

Overview

Date and time checking and manupilation, time strings

Install

uv add --index opj-pypi=https://git.egplusww.jp/api/packages/PyPI/pypi/simple/ corelibs-datetime

Usage

This module contains these sets

  • corelibs_datetime.timestamp_convert
  • corelibs_datetime.datetime_helpers
  • corelibs_datetime.timestamp_strings

DateTime usage

from corelibs_datetime.datetime_helpers import (
    create_time, get_datetime_iso8601, get_system_timezone, parse_timezone_data, validate_date,
    parse_flexible_date, compare_dates, find_newest_datetime_in_list,
    parse_day_of_week_range, parse_time_range, times_overlap_or_connect, is_time_in_range,
    reorder_weekdays_from_today
)
from corelibs_datetime.timestamp_convert import (
    convert_timestamp, seconds_to_string, convert_to_seconds, TimeParseError, TimeUnitError
)
from corelibs_datetime.timestamp_strings import TimestampStrings

tz = get_datetime_iso8601('Asia/Tokyo')
date = parse_flexible_date('2025/1/1')
tss = TimestampStrings('Asia/Tokyo')
today = tss.today

datetime_helper methods

create_time

Take a timestamp and print it out in a human readable format, the default its YYYY-mm-dd HH:MM:SS

def create_time(timestamp: float, timestamp_format: str = "%Y-%m-%d %H:%M:%S") -> str:
date_time_str = create_time(float_timestamp)

get_datetime_iso8601

Wrapper to create a iso type string with zone info if set

If no zone info is set: "YYYY-MM-DDTHH:MM:SS.mmmmmm"

With zone info set "YYYY-MM-DDTHH:MM:SS.mmmmmm+HH:MM"

The "sep" has to be one character long string

On error this will throw ValueError for invalid timespec or TypeError for invalid sep

def get_datetime_iso8601(
    timezone_tz: str | ZoneInfo | None = None, sep: str = 'T', timespec: str = 'microseconds'
) -> str:

Example

ISO Timezone: None -> 2025-11-12T13:13:07.600594
ISO Timezone:  -> 2025-11-12T13:13:07.600749+09:00
ISO Timezone: Asia/Tokyo -> 2025-11-12T13:13:07.600914+09:00
ISO Timezone: UTC -> 2025-11-12T04:13:07.600987+00:00
ISO Timezone: Europe/Vienna -> 2025-11-12T05:13:07.601477+01:00
ISO Timezone: America/New_York -> 2025-11-11T23:13:07.601774-05:00
ISO Timezone: Australia/Sydney -> 2025-11-12T15:13:07.602067+11:00
ISO Timezone: invalid -> 2025-11-12T13:13:07.637322+09:00
ISO timespec: invalid -> Error: Invalid timespec provided: invalid: Unknown timespec value
ISO timespec: auto -> 2025-11-12T13:13:07.637353
ISO timespec: hours -> 2025-11-12T13
ISO timespec: minutes -> 2025-11-12T13:13
ISO timespec: seconds -> 2025-11-12T13:13:07
ISO timespec: milliseconds -> 2025-11-12T13:13:07.637
ISO timespec: microseconds -> 2025-11-12T13:13:07.637367
ISO Sep: 'T' -> 2025-11-12T13:13:07.637370
ISO Sep: ' ' -> 2025-11-12 13:13:07.637372

get_system_timezone

Get the system timezone as TzInfo and plain string, this is "Region/City" format

def get_system_timezone() -> tuple[_TzInfo | None, str]:

parse_timezone_data

Try to get a "Region/City" based ZoneInfo from a timezone tz string like "JST" or "CEST"

On error this will throw ZoneInfoNotFoundError if no zone found i ZoneInfo or IndexError if short name not in the tz_mapping dict

def parse_timezone_data(timezone_tz: str = '') -> ZoneInfo:

Note that this convert is very limited and error prone as short time zone strings have duplicate entries

validate_date

Check that a date is in a valid format and date limit. If no date limits are set the date is only checked if it is Y-m-d or Y/m/d formatted and parsable

The date limit formats are inclusive, so the date given matches or is after the start and matches or is smaller the end date.

def validate_date(date: str, not_before: datetime | None = None, not_after: datetime | None = None) -> bool:

parse_flexible_date

Tries to parse a date string through a given set of formates to get a datetime value back.

Optional time zone can be set to attach a time zone to a string

With the shift_time_zone set to True (default) the date will be shifted to the correct time zone if a time zone is set in the original date Else the time zone is just added

def parse_flexible_date(
    date_str: str,
    timezone_tz: str | ZoneInfo | None = None,
    shift_time_zone: bool = True
) -> datetime | None:

Example for timezone and shift

With timezone set

no new timezone no shift: 2025-01-01T12:18:10.566+02:00 -> 2025-01-01 12:18:10.566000+02:00
new timezone +09:00 and shift: 2025-01-01T12:18:10.566+02:00 -> 2025-01-01 19:18:10.566000+09:00
new timezone +09:00 and no shift: 2025-01-01T12:18:10.566+02:00 -> 2025-01-01 12:18:10.566000+09:00

Without a timezone set

no new timezone no shift: 2025-01-01T12:18:10.566 -> 2025-01-01 12:18:10.566000
new timezone +09:00 and shift: 2025-01-01T12:18:10.566 -> 2025-01-01 12:18:10.566000+09:00
new timezone +09:00 and no shift: 2025-01-01T12:18:10.566 -> 2025-01-01 12:18:10.566000+09:00

compare_dates

Compare two dates and check if the first date is after the second date, returns None on error. Dates are parsed with parse_flexible_date

def compare_dates(date1_str: str, date2_str: str) -> None | bool:

For example check that end date is after start date:

if not compare_dates(end_date, start_date):
    print("End date not after start date")

find_newest_datetime_in_list

In a list of dates or timestamps (parsed with parse_flexible_date) find the newest entry

def find_newest_datetime_in_list(date_list: list[str]) -> None | str:

parse_day_of_week_range

Parses a string value entry of weekdays in long or short formart to a list of tuples for each day found in the list. The list can in the format of:

  • Mon-Fri
  • Mon,Thu,Sat
  • Mon-Thu,Sun

long names are full english day names like Monday

On error this will throw ValueError if we have duplicat entries, KeyError if dow not found in the constant lists

def parse_day_of_week_range(dow_days: str) -> list[tuple[int, str]]:

Examples:

Day range 'Mon-Fri' -> [(1, 'Mon'), (2, 'Tue'), (3, 'Wed'), (4, 'Thu'), (5, 'Fri')]
Day range 'Saturday-Sunday' -> [(6, 'Sat'), (7, 'Sun')]
Day range 'Wed-Mon' -> [(3, 'Wed'), (4, 'Thu'), (5, 'Fri'), (6, 'Sat'), (7, 'Sun'), (1, 'Mon')]
Day range 'Fri-Fri' -> [(5, 'Fri')]
Day range 'mon-tue' -> [(1, 'Mon'), (2, 'Tue')]

parse_time_range

Parse a time range and return a tuple with time values for start and end time

On error this will throw IndexError if we have not two time entries in the "time_str" parameter, ValueError if the dates cannot be parsed, KeyError if the start time is after the end time or start time equal end time

def parse_time_range(time_str: str, time_format: str = "%H:%M") -> tuple[time, time]:

Example

Time range '08:00-17:00' -> [08:00:00, 17:00:00]

times_overlap_or_connect

Checks if a time range tuple (start, end) overlaps or touches a second time range. Unless "allow_touching" is set, if the end time matches the start time of the next block an invalid set is found

The "time1" and "time2" blocks can be created with parse_time_range

def times_overlap_or_connect(time1: tuple[time, time], time2: tuple[time, time], allow_touching: bool = False) -> bool:

Example

Time ranges 08:00:00-12:00:00 and 11:00:00-15:00:00 overlap/connect: True/True
Time ranges 22:00:00-02:00:00 and 01:00:00-05:00:00 overlap/connect: False/False
Time ranges 10:00:00-12:00:00 and 12:00:00-14:00:00 overlap/connect: True/False
Time ranges 09:00:00-11:00:00 and 12:00:00-14:00:00 overlap/connect: False/False

is_time_in_range

Checks if "current_time" is within the start/end time range. equal to start and end time is still seen as valid

def is_time_in_range(current_time: str, start_time: str, end_time: str) -> bool:

Example

Time 10:00:00 in range 09:00:00-11:00:00: True
Time 23:30:00 in range 22:00:00-01:00:00: True
Time 05:00:00 in range 06:00:00-10:00:00: False
Time 12:00:00 in range 12:00:00-12:00:00: True

reorder_weekdays_from_today

Return a full week of weekdays where the strt day of the week is the "base_day". Returned is dictionary where the key is the day of week number and the value is the short day of week name.

On error this will throw KeyError if day cannot be found in constant list

def reorder_weekdays_from_today(base_day: str) -> dict[int, str]:

Example

Reordered weekdays from Tue: {2: 'Tue', 3: 'Wed', 4: 'Thu', 5: 'Fri', 6: 'Sat', 7: 'Sun', 1: 'Mon'}
Reordered weekdays from Wed: {3: 'Wed', 4: 'Thu', 5: 'Fri', 6: 'Sat', 7: 'Sun', 1: 'Mon', 2: 'Tue'}
Reordered weekdays from Sunday: {7: 'Sun', 1: 'Mon', 2: 'Tue', 3: 'Wed', 4: 'Thu', 5: 'Fri', 6: 'Sat'}
Reordered weekdays from Fri: {5: 'Fri', 6: 'Sat', 7: 'Sun', 1: 'Mon', 2: 'Tue', 3: 'Wed', 4: 'Thu'}

corelibs_datetime.timestamp_convert methods

convert_timestamp

Convert any time string with units to seconds, if int or float number will return as is

results are always int, microseconds are not parsed, but rounded to int

The following units are parsed

  • Y/year/years
  • M/month/months
  • d/day/days
  • h/hour/hours
  • m/minute/minutes/min
  • s/second/seconds/sec
def convert_to_seconds(time_string: str | int | float) -> int:

Example

Human readable to seconds: 5M 6d => 156038400
Human readable to seconds: 2h 30m 45s => 9045
Human readable to seconds: 1Y 2M 3d => 94003200
Human readable to seconds: 1h => 3600
Human readable to seconds: 30m => 1800
Human readable to seconds: 2 hours 15 minutes => 8100
Human readable to seconds: 1d 12h => 129600
Human readable to seconds: 3M 2d 4h => 93499200
Human readable to seconds: 45s => 45
Human readable to seconds: -45s => -45
Human readable to seconds: -1h => -3600
Human readable to seconds: -30m => -1800
Human readable to seconds: -2h 30m 45s => -9045
Human readable to seconds: -1d 12h => -129600
Human readable to seconds: -3M 2d 4h => -93499200
Human readable to seconds: -1Y 2M 3d => -94003200
Human readable to seconds: -2 hours 15 minutes => -8100
Human readable to seconds: -1 year 2 months => -93744000
Human readable to seconds: -2Y 6M 15d 8h 30m 45s => -251022645
Human readable to seconds: 1 year 2 months => 93744000
Human readable to seconds: 2Y 6M 15d 8h 30m 45s => 251022645

seconds_to_string

Convert seconds into a human readable interval format, unless "show_microseconds" is set any microseconds are not shown (default off)

Microseconds are added with "." and the value

def seconds_to_string(seconds: str | int | float, show_microseconds: bool = False) -> str:

Example, with microseconds on

Seconds to human readable: -172800.001234 => -2d 0.001234s
Seconds to human readable: -90061.789 => -1d 1h 1m 1.789s
Seconds to human readable: -3661.456 => -1h 1m 1.456s
Seconds to human readable: -65.123 => -1m 5.123s
Seconds to human readable: -1.5 => -1.5s
Seconds to human readable: -0.001 => -0.001s
Seconds to human readable: -1e-06 => -0.000001s
Seconds to human readable: 0 => 0s / 0s 0ms
Seconds to human readable: 1e-06 => 0.000001s
Seconds to human readable: 0.001 => 0.001s
Seconds to human readable: 1.5 => 1.5s
Seconds to human readable: 65.123 => 1m 5.123s
Seconds to human readable: 3661.456 => 1h 1m 1.456s
Seconds to human readable: 90061.789 => 1d 1h 1m 1.789s
Seconds to human readable: 172800.001234 => 2d 0.001234s

convert_to_seconds

Alternative way to convert a timestamp to to a human readable format, unless "show_microseconds" is set any microseconds are not shown (default on)

Microseconds are added as "ms" unit

def convert_timestamp(timestamp: float | int | str, show_microseconds: bool = True) -> str:

Example

Seconds to human readable: -172800.001234 => -2d 0h 0m 0s 12ms
Seconds to human readable: -90061.789 => -1d 1h 1m 1s 789ms
Seconds to human readable: -3661.456 => -1h 1m 1s 456ms
Seconds to human readable: -65.123 => -1m 5s 123ms
Seconds to human readable: -1.5 => -1s 5ms
Seconds to human readable: -0.001 => 0s 1ms
Seconds to human readable: -1e-06 => 0s 0ms
Seconds to human readable: 0 => 0s 0ms
Seconds to human readable: 1e-06 => 0s 0ms
Seconds to human readable: 0.001 => 0s 1ms
Seconds to human readable: 1.5 => 1s 5ms
Seconds to human readable: 65.123 => 1m 5s 123ms
Seconds to human readable: 3661.456 => 1h 1m 1s 456ms
Seconds to human readable: 90061.789 => 1d 1h 1m 1s 789ms
Seconds to human readable: 172800.001234 => 2d 0h 0m 0s 12ms

TimeParseError

Used in convert_to_seconds if one unit appears more than once

TimeUnitError

Used in convert_to_seconds if unit is not a valid name

corelibs_datetime.timestamp_strings class

Init the class with a time zone, either as as a string, eg 'Asia/Tokyo' or as a ZoneInfo entry: Zoneinfo('Asia/Tokyo). If none is set it sets it to "Asia/Tokyo" defined in TimestampStrings.TIME_ZONE_DEFAULT

While helpers are set, any date can be collectes from timestamp_now or timestamp_now_tz

TimestampStrings.timestamp_now

datetime.now() when the class was initialized

TimestampStrings.time_zone

The set timezone as string

TimestampStrings.time_zone_zi

timezone as ZoneInfo value

TimestampStrings.timstamp_now_tz

datetime.now() with timezone set

TimestampStrings.today

YYYY-mm-dd

TimestampStrings.timestamp

YYYY-mm-dd HH:MM:SS

TimestampStrings.timestamp_tz_name

Adds the timestamp name to the string

TimestampStrings.timestamp_tz

Timestamp as above but with the zone time change in +/- HH:MM format

YYYY-mm-dd HH:MM:SS +/-HH:SS

TimestampStrings.timestamp_tz_iso

In default ISO format with timezone info as YYYY-mm-ddTHH:MM:SS.mmmmmm+HH:MM

TimestampStrings.timestamp_file

For file writing in the format YYYYmmdd_HHMMSS

Development

UV setup

uv must be installed

Python venv setup

After clone, run the command below to install all dependenciss

uv sync

Build and Publish

uv build
uv publish --index opj-pypi --token <gitea token>

Python tests

All python tests are the tests/ folder. They are structured by the source folder layout

run them with

uv run pytest

Get a coverate report

uv run pytest --cov=corelibs_datetime
uv run pytest --cov=corelibs_datetime --cov-report=term-missing

Other tests

In the test-run folder usage and run tests are located, runt them below

uv run test-run/<script>

Requirements

Requires Python: >=3.13
Details
PyPI
2026-01-06 09:43:24 +09:00
0
Clemens Schwaighofer
25 KiB
Assets (2)
Versions (5) View all
1.2.0 2026-01-06
1.1.0 2025-12-15
1.0.1 2025-11-14
1.0.0 2025-11-14
0.2.0 2025-11-06