DateTimeRange is a Python library to handle a time range, providing operations for checking containment, intersection, iteration, and other datetime range manipulations.
npx @tessl/cli install tessl/pypi-date-time-range@2.3.0DateTimeRange is a Python library to handle a time range. It provides comprehensive functionality for working with datetime intervals including checking containment, calculating intersections and unions, truncating ranges, splitting ranges, and iterating through time periods with custom intervals.
pip install DateTimeRangefrom datetimerange import DateTimeRangeimport datetime
from datetimerange import DateTimeRange
# Create a time range from string representations
time_range = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
print(time_range) # 2015-03-22T10:00:00+0900 - 2015-03-22T10:10:00+0900
# Create from datetime objects
start = datetime.datetime(2015, 3, 22, 10, 0, 0)
end = datetime.datetime(2015, 3, 22, 10, 10, 0)
time_range = DateTimeRange(start, end)
# Check if a time is within the range
check_time = "2015-03-22T10:05:00+0900"
print(check_time in time_range) # True
# Get intersection of two ranges
range1 = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
range2 = DateTimeRange("2015-03-22T10:05:00+0900", "2015-03-22T10:15:00+0900")
intersection = range1.intersection(range2)
print(intersection) # 2015-03-22T10:05:00+0900 - 2015-03-22T10:10:00+0900
# Iterate through time range with step intervals
import datetime
for time_point in time_range.range(datetime.timedelta(minutes=2)):
print(time_point)Create DateTimeRange instances from various input formats including datetime objects, ISO strings, and range text.
class DateTimeRange:
def __init__(
self,
start_datetime: Union[datetime.datetime, str, None] = None,
end_datetime: Union[datetime.datetime, str, None] = None,
start_time_format: Optional[str] = None,
end_time_format: Optional[str] = None,
timezone: Optional[datetime.tzinfo] = None,
) -> None:
"""
Initialize a DateTimeRange instance.
Args:
start_datetime: Start time as datetime object or ISO string
end_datetime: End time as datetime object or ISO string
start_time_format: Custom format string for start time display
end_time_format: Custom format string for end time display
timezone: Timezone information for the range
"""
@classmethod
def from_range_text(
cls,
range_text: str,
separator: str = r"\s+\-\s+",
start_time_format: Optional[str] = None,
end_time_format: Optional[str] = None,
timezone: Optional[datetime.tzinfo] = None,
) -> "DateTimeRange":
"""
Create DateTimeRange from text representation.
Args:
range_text: Text containing datetime range (e.g., "2021-01-23T10:00:00+0400 - 2021-01-23T10:10:00+0400")
separator: Regex pattern separating start and end times
start_time_format: Custom format for start time
end_time_format: Custom format for end time
timezone: Timezone information
Returns:
DateTimeRange: New instance from parsed text
"""Access datetime range properties and configure display formats.
@property
def start_datetime(self) -> Optional[datetime.datetime]:
"""Start time of the range."""
@property
def end_datetime(self) -> Optional[datetime.datetime]:
"""End time of the range."""
@property
def timezone(self) -> Optional[datetime.tzinfo]:
"""Timezone information of the range."""
@property
def timedelta(self) -> datetime.timedelta:
"""Time difference between end and start as timedelta."""
# Instance attributes
start_time_format: str # Format string for start time display (default: "%Y-%m-%dT%H:%M:%S%z")
end_time_format: str # Format string for end time display (default: "%Y-%m-%dT%H:%M:%S%z")
is_output_elapse: bool # Whether to show elapsed time in string representation
separator: str # Separator string for display (default: " - ")Set and modify datetime range boundaries with validation.
def set_start_datetime(
self,
value: Union[datetime.datetime, str, None],
timezone: Optional[datetime.tzinfo] = None
) -> None:
"""
Set the start time of the range.
Args:
value: New start datetime or ISO string
timezone: Timezone for the datetime
Raises:
ValueError: If value is invalid datetime
"""
def set_end_datetime(
self,
value: Union[datetime.datetime, str, None],
timezone: Optional[datetime.tzinfo] = None
) -> None:
"""
Set the end time of the range.
Args:
value: New end datetime or ISO string
timezone: Timezone for the datetime
Raises:
ValueError: If value is invalid datetime
"""
def set_time_range(
self,
start: Union[datetime.datetime, str, None],
end: Union[datetime.datetime, str, None],
timezone: Optional[datetime.tzinfo] = None,
) -> None:
"""
Set both start and end times.
Args:
start: Start datetime or ISO string
end: End datetime or ISO string
timezone: Timezone for both times
"""Validate time ranges and query their properties.
def is_set(self) -> bool:
"""
Check if both start and end times are set.
Returns:
bool: True if both times are not None
"""
def is_time_inversion(self, allow_timezone_mismatch: bool = True) -> bool:
"""
Check if start time is after end time.
Args:
allow_timezone_mismatch: Whether to ignore timezone differences
Returns:
bool: True if start > end
"""
def validate_time_inversion(self, allow_timezone_mismatch: bool = True) -> None:
"""
Validate time order, raising exception if inverted.
Args:
allow_timezone_mismatch: Whether to ignore timezone differences
Raises:
ValueError: If start time > end time
TypeError: If times are not set properly
"""
def is_valid_timerange(self) -> bool:
"""
Check if range is valid (set and not inverted).
Returns:
bool: True if valid non-null, non-inverted range
"""Convert datetime ranges to formatted string representations.
def get_start_time_str(self) -> str:
"""
Get formatted start time string.
Returns:
str: Formatted start time or "NaT" if invalid
"""
def get_end_time_str(self) -> str:
"""
Get formatted end time string.
Returns:
str: Formatted end time or "NaT" if invalid
"""
def get_timedelta_second(self) -> float:
"""
Get time difference in seconds.
Returns:
float: Duration in seconds
"""Perform set-like operations on datetime ranges including intersection, union, and subtraction.
def intersection(
self,
x: "DateTimeRange",
intersection_threshold: Union[datetime.timedelta, relativedelta, None] = None,
) -> "DateTimeRange":
"""
Create intersection of two time ranges.
Args:
x: Other DateTimeRange to intersect with
intersection_threshold: Minimum overlap duration required
Returns:
DateTimeRange: Overlapping portion or empty range if no overlap
"""
def is_intersection(
self,
x: "DateTimeRange",
intersection_threshold: Union[datetime.timedelta, relativedelta, None] = None,
) -> bool:
"""
Check if ranges intersect.
Args:
x: Other DateTimeRange to check
intersection_threshold: Minimum overlap duration required
Returns:
bool: True if ranges overlap
"""
def subtract(self, x: "DateTimeRange") -> list["DateTimeRange"]:
"""
Remove overlapping portion from this range.
Args:
x: DateTimeRange to subtract
Returns:
list[DateTimeRange]: List of remaining ranges after subtraction
"""
def encompass(self, x: "DateTimeRange") -> "DateTimeRange":
"""
Create range that encompasses both ranges.
Args:
x: Other DateTimeRange to encompass
Returns:
DateTimeRange: Range spanning from earliest start to latest end
"""Split, truncate, and modify datetime ranges.
def split(self, separator: Union[str, datetime.datetime]) -> list["DateTimeRange"]:
"""
Split range at specific datetime.
Args:
separator: Datetime to split at (included in both resulting ranges)
Returns:
list[DateTimeRange]: List of split ranges (1 if separator outside range, 2 if inside)
"""
def truncate(self, percentage: float) -> None:
"""
Remove percentage/2 from each end of the range.
Args:
percentage: Percentage to truncate (distributed equally from both ends)
Raises:
ValueError: If percentage < 0
"""Iterate through datetime ranges with custom step intervals.
def range(self, step: Union[datetime.timedelta, relativedelta]) -> Iterator[datetime.datetime]:
"""
Iterate through range with specified step.
Args:
step: Time interval for iteration steps
Returns:
Iterator[datetime.datetime]: Iterator yielding datetime objects
Raises:
ValueError: If step is zero or invalid direction for inverted ranges
"""Test if datetimes or ranges are contained within this range.
def __contains__(self, x: Union[datetime.datetime, "DateTimeRange", str]) -> bool:
"""
Check if datetime or range is within this range.
Args:
x: Datetime, DateTimeRange, or ISO string to test
Returns:
bool: True if x is contained within this range
"""Add and subtract time intervals from datetime ranges.
def __add__(self, other: Union[datetime.timedelta, relativedelta]) -> "DateTimeRange":
"""
Add time interval to both start and end times.
Args:
other: Time interval to add
Returns:
DateTimeRange: New range shifted forward by interval
"""
def __iadd__(self, other: Union[datetime.timedelta, relativedelta]) -> "DateTimeRange":
"""
Add time interval in-place.
Args:
other: Time interval to add
Returns:
DateTimeRange: Self with modified times
"""
def __sub__(self, other: Union[datetime.timedelta, relativedelta]) -> "DateTimeRange":
"""
Subtract time interval from both start and end times.
Args:
other: Time interval to subtract
Returns:
DateTimeRange: New range shifted backward by interval
"""
def __isub__(self, other: Union[datetime.timedelta, relativedelta]) -> "DateTimeRange":
"""
Subtract time interval in-place.
Args:
other: Time interval to subtract
Returns:
DateTimeRange: Self with modified times
"""Get string representation and compare datetime ranges.
def __repr__(self) -> str:
"""
Get string representation of the datetime range.
Returns:
str: Formatted range with optional elapsed time if is_output_elapse is True
"""
def __eq__(self, other: object) -> bool:
"""
Check equality with another DateTimeRange.
Args:
other: Object to compare with
Returns:
bool: True if both ranges have same start and end times
"""
def __ne__(self, other: object) -> bool:
"""
Check inequality with another DateTimeRange.
Args:
other: Object to compare with
Returns:
bool: True if ranges differ in start or end times
"""# Class constant for invalid time representation
NOT_A_TIME_STR: str = "NaT"from typing import Union, Optional, Iterator
import datetime
from dateutil.relativedelta import relativedelta
# Type aliases used throughout the API
TimeValue = Union[datetime.datetime, str, None]
TimeDelta = Union[datetime.timedelta, relativedelta]# From ISO strings
range1 = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
# From datetime objects
start = datetime.datetime(2015, 3, 22, 10, 0, 0)
end = datetime.datetime(2015, 3, 22, 10, 10, 0)
range2 = DateTimeRange(start, end)
# From range text
range3 = DateTimeRange.from_range_text("2015-03-22T10:00:00+0900 - 2015-03-22T10:10:00+0900")# Intersection
overlap = range1.intersection(range2)
# Union (encompass)
combined = range1.encompass(range2)
# Subtraction
remaining = range1.subtract(range2)
# Membership testing
if "2015-03-22T10:05:00+0900" in range1:
print("Time is in range")# Daily iteration
for day in date_range.range(datetime.timedelta(days=1)):
print(day)
# Hourly iteration using relativedelta
from dateutil.relativedelta import relativedelta
for hour in date_range.range(relativedelta(hours=1)):
print(hour)import pytz
# Create with timezone
tz = pytz.timezone('America/New_York')
range_tz = DateTimeRange(
"2015-03-22T10:00:00",
"2015-03-22T12:00:00",
timezone=tz
)