Extensions to the standard Python datetime module
—
General convenience functions for common date and time operations including current day calculation, timezone defaulting, and date comparison within tolerance.
def today(tzinfo=None):
"""
Return current date at midnight with optional timezone.
Parameters:
- tzinfo (tzinfo, optional): Timezone to attach and use for current day determination
Returns:
datetime: Current date at midnight (00:00:00) in specified timezone
"""Usage Examples:
from dateutil.utils import today
from dateutil.tz import gettz
from datetime import datetime
# Current day at midnight in local timezone
today_local = today()
print(f"Today (local): {today_local}")
# Current day at midnight in specific timezone
utc_tz = gettz('UTC')
today_utc = today(utc_tz)
print(f"Today (UTC): {today_utc}")
# Compare with datetime.now()
now = datetime.now(utc_tz)
today_utc_alt = today(utc_tz)
print(f"Now: {now}")
print(f"Today: {today_utc_alt}") # Same date, but time is 00:00:00def default_tzinfo(dt, tzinfo):
"""
Set timezone on naive datetimes only, leaving aware datetimes unchanged.
Useful when dealing with datetimes that may have implicit or explicit timezones,
such as when parsing timezone strings or working with mixed timezone data.
Parameters:
- dt (datetime): Datetime object to potentially modify
- tzinfo (tzinfo): Timezone to assign if dt is naive
Returns:
datetime: Aware datetime (either original if already aware, or with tzinfo applied)
"""Usage Examples:
from dateutil.utils import default_tzinfo
from dateutil.tz import gettz
from dateutil.parser import parse
from datetime import datetime
eastern_tz = gettz('America/New_York')
# Apply timezone to naive datetime
naive_dt = datetime(2023, 12, 25, 14, 30)
aware_dt = default_tzinfo(naive_dt, eastern_tz)
print(f"Naive -> Aware: {aware_dt}")
# Aware datetime remains unchanged
already_aware = datetime(2023, 12, 25, 14, 30, tzinfo=gettz('UTC'))
still_aware = default_tzinfo(already_aware, eastern_tz)
print(f"Still UTC: {still_aware}") # Keeps original UTC timezone
# Practical example with parsing
def parse_with_default_tz(date_str, default_tz):
"""Parse date string and apply default timezone if none specified."""
parsed_dt = parse(date_str)
return default_tzinfo(parsed_dt, default_tz)
# Apply default timezone only when needed
dt1 = parse_with_default_tz("2023-12-25 14:30", eastern_tz) # Gets Eastern
dt2 = parse_with_default_tz("2023-12-25 14:30 UTC", eastern_tz) # Stays UTCdef within_delta(dt1, dt2, delta):
"""
Check if two datetimes are within a specified tolerance of each other.
Useful for comparing datetimes that may have negligible differences
due to precision, network delays, or processing time.
Parameters:
- dt1 (datetime): First datetime
- dt2 (datetime): Second datetime
- delta (timedelta): Maximum allowed difference (tolerance)
Returns:
bool: True if absolute difference between dt1 and dt2 is <= delta
"""Usage Examples:
from dateutil.utils import within_delta
from datetime import datetime, timedelta
# Basic tolerance checking
dt1 = datetime(2023, 12, 25, 14, 30, 0, 0) # Exactly 14:30:00.000
dt2 = datetime(2023, 12, 25, 14, 30, 0, 500000) # 14:30:00.500 (500ms later)
tolerance = timedelta(seconds=1)
are_close = within_delta(dt1, dt2, tolerance)
print(f"Within 1 second: {are_close}") # True
# More strict tolerance
strict_tolerance = timedelta(milliseconds=100)
are_very_close = within_delta(dt1, dt2, strict_tolerance)
print(f"Within 100ms: {are_very_close}") # False
# Practical application: Event synchronization
def events_are_simultaneous(event1_time, event2_time, tolerance_seconds=5):
"""Check if two events occurred within tolerance of each other."""
tolerance = timedelta(seconds=tolerance_seconds)
return within_delta(event1_time, event2_time, tolerance)
# Example usage
event_a = datetime(2023, 12, 25, 14, 30, 15)
event_b = datetime(2023, 12, 25, 14, 30, 18)
simultaneous = events_are_simultaneous(event_a, event_b)
print(f"Events are simultaneous: {simultaneous}") # True (within 5 seconds)from dateutil.utils import today
from dateutil.tz import gettz
from datetime import datetime
def business_day_start(timezone_name):
"""Get start of business day in specified timezone."""
tz = gettz(timezone_name)
return today(tz).replace(hour=9) # 9 AM start
def is_same_business_day(dt1, dt2, timezone_name):
"""Check if two datetimes fall on same business day."""
tz = gettz(timezone_name)
# Convert both to same timezone and compare dates
dt1_local = dt1.astimezone(tz) if dt1.tzinfo else dt1.replace(tzinfo=tz)
dt2_local = dt2.astimezone(tz) if dt2.tzinfo else dt2.replace(tzinfo=tz)
return dt1_local.date() == dt2_local.date()
# Example usage
ny_start = business_day_start('America/New_York')
london_start = business_day_start('Europe/London')
print(f"NY business day starts: {ny_start}")
print(f"London business day starts: {london_start}")from dateutil.utils import default_tzinfo, within_delta
from dateutil.tz import gettz
from dateutil.parser import parse
from datetime import datetime, timedelta
class DateTimeProcessor:
"""Robust datetime processing with defaults and validation."""
def __init__(self, default_timezone='UTC', tolerance_seconds=1):
self.default_tz = gettz(default_timezone)
self.tolerance = timedelta(seconds=tolerance_seconds)
def normalize_datetime(self, dt_input):
"""Convert various datetime inputs to standardized format."""
if isinstance(dt_input, str):
dt = parse(dt_input)
else:
dt = dt_input
# Apply default timezone if naive
return default_tzinfo(dt, self.default_tz)
def are_equivalent(self, dt1, dt2):
"""Check if two datetime inputs represent equivalent times."""
norm_dt1 = self.normalize_datetime(dt1)
norm_dt2 = self.normalize_datetime(dt2)
return within_delta(norm_dt1, norm_dt2, self.tolerance)
# Usage example
processor = DateTimeProcessor('America/New_York', tolerance_seconds=5)
# Various input formats
inputs = [
"2023-12-25 14:30:00",
datetime(2023, 12, 25, 19, 30, 3, tzinfo=gettz('UTC')), # Same time in UTC
"Dec 25, 2023 2:30:02 PM EST"
]
# Normalize all inputs
normalized = [processor.normalize_datetime(inp) for inp in inputs]
for i, norm_dt in enumerate(normalized):
print(f"Input {i+1}: {norm_dt}")
# Check equivalence
print(f"All equivalent: {all(processor.are_equivalent(normalized[0], dt) for dt in normalized[1:])}")from dateutil.utils import within_delta
from datetime import datetime, timedelta
def validate_timestamp_sequence(timestamps, max_gap_minutes=60):
"""
Validate that timestamps form a reasonable sequence.
Parameters:
- timestamps: List of datetime objects
- max_gap_minutes: Maximum allowed gap between consecutive timestamps
Returns:
dict: Validation results with issues found
"""
issues = {
'out_of_order': [],
'large_gaps': [],
'duplicates': []
}
max_gap = timedelta(minutes=max_gap_minutes)
duplicate_tolerance = timedelta(seconds=1)
for i in range(len(timestamps) - 1):
current = timestamps[i]
next_ts = timestamps[i + 1]
# Check ordering
if current > next_ts:
issues['out_of_order'].append((i, i+1))
# Check for large gaps
gap = next_ts - current
if gap > max_gap:
issues['large_gaps'].append((i, i+1, gap))
# Check for duplicates (within tolerance)
if within_delta(current, next_ts, duplicate_tolerance):
issues['duplicates'].append((i, i+1))
return issues
# Example usage
test_timestamps = [
datetime(2023, 12, 25, 10, 0),
datetime(2023, 12, 25, 10, 15),
datetime(2023, 12, 25, 10, 15, 1), # Near duplicate
datetime(2023, 12, 25, 12, 0), # Large gap
datetime(2023, 12, 25, 11, 30), # Out of order
]
validation_results = validate_timestamp_sequence(test_timestamps)
print("Validation results:", validation_results)from datetime import datetime, timedelta, tzinfo
# Function signatures
def today(tzinfo: tzinfo | None = None) -> datetime: ...
def default_tzinfo(dt: datetime, tzinfo: tzinfo) -> datetime: ...
def within_delta(dt1: datetime, dt2: datetime, delta: timedelta) -> bool: ...
# Parameter types
DateTimeInput = datetime | str # For functions that accept multiple datetime formats
TimezoneInput = tzinfo | str | None # Timezone specifications
ToleranceInput = timedelta # Time tolerance specificationsInstall with Tessl CLI
npx tessl i tessl/pypi-python-dateutil