corelibs-datetime (1.2.0)
Installation
pip install --index-url --extra-index-url https://pypi.org/simple corelibs-datetimeAbout 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-FriMon,Thu,SatMon-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>