DateTime¶
The DateTime class is the heart of Carbonic, providing a comprehensive, immutable datetime object with timezone support and a fluent API.
Overview¶
The DateTime class wraps Python's standard datetime.datetime while providing additional functionality and a more intuitive interface. Every DateTime object is immutable, meaning all operations return new instances.
from carbonic import DateTime
# Create a DateTime instance
dt = DateTime(2024, 1, 15, 14, 30, 0, tz="UTC")
print(dt)  # 2024-01-15T14:30:00+00:00
Get current time¶
Creating DateTime Objects¶
Constructor¶
from carbonic import DateTime
# Full specification
dt = DateTime(
    year=2024,
    month=1,
    day=15,
    hour=14,
    minute=30,
    second=45,
    microsecond=123456,
    tz="UTC"
)
# Minimal specification (defaults to midnight UTC)
dt = DateTime(2024, 1, 15)  # 2024-01-15T00:00:00+00:00
# Without timezone (naive - not recommended)
naive = DateTime(2024, 1, 15, tz=None)
Factory Methods¶
import datetime
from carbonic import DateTime
# Current time
now_utc = DateTime.now()  # UTC by default
now_local = DateTime.now("America/New_York")
# From standard library datetime
stdlib_dt = datetime.datetime(2024, 1, 15, 14, 30, tzinfo=datetime.timezone.utc)
carbonic_dt = DateTime.from_datetime(stdlib_dt)
# From ISO string
iso_dt = DateTime.parse("2024-01-15T14:30:00Z")
# From custom format
custom_dt = DateTime.parse("15/01/2024 14:30", "d/m/Y H:i")
# Relative date/time functions
today_dt = DateTime.today()       # Today at midnight UTC
tomorrow_dt = DateTime.tomorrow() # Tomorrow at midnight UTC
yesterday_dt = DateTime.yesterday() # Yesterday at midnight UTC
# Future dates/times
next_hour = DateTime.next("hour")        # 1 hour from now
next_day = DateTime.next("day")          # 1 day from now (tomorrow)
next_week = DateTime.next("week", 2)     # 2 weeks from now
next_month = DateTime.next("month")      # 1 month from now
next_quarter = DateTime.next("quarter")  # 3 months from now
# Past dates/times
prev_minute = DateTime.previous("minute", 30)  # 30 minutes ago
prev_day = DateTime.previous("day")             # 1 day ago (yesterday)
prev_week = DateTime.previous("week")           # 1 week ago
prev_month = DateTime.previous("month", 3)      # 3 months ago
Date and Time Arithmetic¶
Adding Time¶
from carbonic import DateTime, Duration
dt = DateTime(2024, 1, 15, 10, 0)
# Using fluent methods
future = dt.add(years=1).add(months=2).add(days=3).add(hours=4)
# Using Duration objects
duration = Duration(days=7, hours=12, minutes=30)
future = dt + duration
# Specific unit methods
next_hour = dt.add(hours=1)
next_day = dt.add(days=1)
next_month = dt.add(months=1)
next_year = dt.add(years=1)
Subtracting Time¶
from carbonic import DateTime, Duration
dt = DateTime(2024, 1, 15, 10, 0)
# Using fluent methods
past = dt.subtract(days=5).subtract(hours=2)
# Using Duration objects
duration = Duration(days=3, hours=6)
past = dt - duration
# Specific unit methods
last_hour = dt.subtract(hours=1)
yesterday = dt.subtract(days=1)
last_month = dt.subtract(months=1)
last_year = dt.subtract(years=1)
Date Boundaries and Navigation¶
Start and End Points¶
from carbonic import DateTime
dt = DateTime(2024, 3, 15, 14, 30, 45)
# Day boundaries
start_of_day = dt.start_of("day")        # 2024-03-15T00:00:00+00:00
end_of_day = dt.end_of("day")            # 2024-03-15T23:59:59.999999+00:00
# Week boundaries
start_of_week = dt.start_of("week")      # Previous Monday
end_of_week = dt.end_of("week")          # Next Sunday
# Month boundaries
start_of_month = dt.start_of("month")    # 2024-03-01T00:00:00+00:00
end_of_month = dt.end_of("month")        # 2024-03-31T23:59:59.999999+00:00
# Quarter boundaries
start_of_quarter = dt.start_of("quarter") # 2024-01-01T00:00:00+00:00
end_of_quarter = dt.end_of("quarter")     # 2024-03-31T23:59:59.999999+00:00
# Year boundaries
start_of_year = dt.start_of("year")      # 2024-01-01T00:00:00+00:00
end_of_year = dt.end_of("year")          # 2024-12-31T23:59:59.999999+00:00
Navigation by Weekday¶
from carbonic import DateTime
import datetime
dt = DateTime(2024, 1, 15, 14, 30, tz="UTC")  # Monday
# Check current weekday (Monday=0, Sunday=6)
weekday = dt.to_datetime().weekday()
print(f"Weekday: {weekday}")  # 0 (Monday)
# Calculate next Friday (4 is Friday)
days_until_friday = (4 - weekday) % 7
if days_until_friday == 0:  # If today is Friday, get next Friday
    days_until_friday = 7
next_friday = dt.add(days=days_until_friday)
# Calculate next Monday
days_until_monday = (0 - weekday) % 7  # 0 is Monday
if days_until_monday == 0:  # If today is Monday, get next Monday
    days_until_monday = 7
next_monday = dt.add(days=days_until_monday)
# Weekday checks using datetime methods
print(dt.to_datetime().weekday() == 0)     # True (Monday)
print(dt.to_datetime().weekday() == 4)     # False (Friday)
print(dt.to_datetime().weekday() >= 5)     # False (not weekend)
print(dt.to_datetime().weekday() < 5)      # True (is weekday)
Business Day Operations¶
from carbonic import DateTime
dt = DateTime(2024, 1, 15, 14, 30, tz="UTC")  # Monday
# Business day checks using weekday
def is_business_day(dt):
    return dt.to_datetime().weekday() < 5  # Monday=0 to Friday=4
def add_business_days(dt, days):
    current = dt
    added = 0
    while added < days:
        current = current.add(days=1)
        if is_business_day(current):
            added += 1
    return current
# Business day navigation
print(f"Is business day: {is_business_day(dt)}")  # True (Monday)
# Add 5 business days
five_business_days_later = add_business_days(dt, 5)
print(f"5 business days later: {five_business_days_later}")
# Count business days between dates
start_date = DateTime(2024, 1, 10, tz="UTC")  # Wednesday
end_date = DateTime(2024, 1, 20, tz="UTC")    # Saturday
current = start_date
business_days = 0
while current <= end_date:
    if is_business_day(current):
        business_days += 1
    current = current.add(days=1)
print(f"Business days: {business_days}")
Timezone Operations¶
Creating with Timezones¶
from carbonic import DateTime
# Different timezone formats
utc_dt = DateTime(2024, 1, 15, 14, 30, tz="UTC")
ny_dt = DateTime(2024, 1, 15, 14, 30, tz="America/New_York")
london_dt = DateTime(2024, 1, 15, 14, 30, tz="Europe/London")
print(f"UTC: {utc_dt}")
print(f"New York: {ny_dt}")
print(f"London: {london_dt}")
Using Timezone Objects¶
# Using timezone objects
from carbonic import DateTime
from zoneinfo import ZoneInfo
# Create with timezone string (DateTime internally creates ZoneInfo)
tokyo_dt = DateTime(2024, 1, 15, 14, 30, tz="Asia/Tokyo")
print(f"Tokyo: {tokyo_dt}")
# Or use ZoneInfo for other operations
tokyo_tz = ZoneInfo("Asia/Tokyo")
print(f"Timezone object: {tokyo_tz}")
Converting Timezones¶
from carbonic import DateTime
# Create in one timezone
paris_time = DateTime(2024, 1, 15, 15, 30, tz="Europe/Paris")
print(paris_time)  # 2024-01-15T15:30:00+01:00
# Convert to different timezones using as_timezone()
utc_time = paris_time.as_timezone("UTC")
tokyo_time = paris_time.as_timezone("Asia/Tokyo")
ny_time = paris_time.as_timezone("America/New_York")
print(f"UTC:   {utc_time}")    # 2024-01-15T14:30:00+00:00
print(f"Tokyo: {tokyo_time}")  # 2024-01-15T23:30:00+09:00
print(f"NY:    {ny_time}")     # 2024-01-15T09:30:00-05:00
# All represent the same moment in time
assert paris_time == utc_time == tokyo_time == ny_time
# Convert to naive datetime (removes timezone info)
naive_time = paris_time.as_timezone(None)
print(f"Naive: {naive_time}")  # 2024-01-15T15:30:00 (no timezone)
Working with Naive Datetimes¶
from carbonic import DateTime
# Naive datetime (no timezone)
naive = DateTime(2024, 1, 15, 14, 30, tz=None)
print(naive.tzinfo)  # None
# Convert naive to timezone-aware by creating new instance
aware = DateTime(naive.year, naive.month, naive.day, naive.hour, 
                naive.minute, naive.second, naive.microsecond, tz="UTC")
print(aware.tzinfo)  # <ZoneInfo 'UTC'>
# Check if naive using tzinfo
print(naive.tzinfo is None)   # True (is naive)
print(aware.tzinfo is not None)   # True (is aware)
Comparisons¶
Basic Comparisons¶
from carbonic import DateTime
dt1 = DateTime(2024, 1, 15, 10, 0, tz="UTC")
dt2 = DateTime(2024, 1, 15, 14, 0, tz="UTC")
dt3 = DateTime(2024, 1, 16, 10, 0, tz="UTC")
# Standard operators
print(dt1 < dt2)   # True
print(dt2 > dt3)   # False
print(dt1 == dt1)  # True
print(dt1 != dt2)  # True
# Using comparison operators instead of fluent methods
print(dt1 < dt2)               # is_before equivalent: True
print(dt2 > dt1)               # is_after equivalent: True
print(dt1 == dt1)              # is_same_instant equivalent: True
Date-Level Comparisons¶
from carbonic import DateTime
dt1 = DateTime(2024, 1, 15, 10, 0, tz="UTC")
dt2 = DateTime(2024, 1, 15, 20, 0, tz="UTC")  # Same day, different time
dt3 = DateTime(2024, 1, 16, 5, 0, tz="UTC")   # Different day
# Same date checks using date properties
print(dt1.to_date() == dt2.to_date())  # is_same_day equivalent: True
print(dt1.to_date() == dt3.to_date())  # is_same_day equivalent: False
print(dt1.year == dt2.year and dt1.month == dt2.month)  # is_same_month equivalent: True
print(dt1.year == dt2.year)  # is_same_year equivalent: True
# Timezone-aware comparisons
ny_time = DateTime(2024, 1, 15, 5, 0, tz="America/New_York")  # Same as 10:00 UTC
print(dt1 == ny_time)  # is_same_instant equivalent: True
# For same day comparison, convert to same timezone first
ny_utc = DateTime.from_datetime(ny_time.to_datetime().astimezone())
print(dt1.to_date() == ny_utc.to_date())  # Different local dates check
Temporal Relationships¶
from carbonic import DateTime
dt = DateTime(2024, 1, 15, 14, 30, tz="UTC")
current = DateTime.now("UTC")
# Relative to current time using comparison operators
print(dt < current)       # is_past equivalent: True (if current time is after)
print(dt > current)       # is_future equivalent: False (if current time is after)
# Between dates using comparison operators
start = DateTime(2024, 1, 10, tz="UTC")
end = DateTime(2024, 1, 20, tz="UTC")
print(start <= dt <= end)  # is_between equivalent: True
Differences and Durations¶
Calculating Differences¶
from carbonic import DateTime
start = DateTime(2024, 1, 15, 10, 0, tz="UTC")
end = DateTime(2024, 1, 17, 14, 30, tz="UTC")
# Get Duration object
duration = end - start
print(duration)  # Duration(days=2, hours=4, minutes=30)
# Get difference using the diff method (returns Duration)
duration = start.diff(end)
# Access duration properties
print(f"Days: {duration.in_days()}")
print(f"Hours: {duration.in_hours()}")
print(f"Minutes: {duration.in_minutes()}")
print(f"Seconds: {duration.in_seconds()}")
Working with Durations¶
from carbonic import DateTime
dt = DateTime(2024, 1, 15, 14, 30, tz="UTC")
current = DateTime.now("UTC")
# Get duration object
duration = current.diff(dt)
# Access duration properties for humanization
days = duration.in_days()
if abs(days) >= 1:
    if days > 0:
        human = f"{int(days)} days ago"
    else:
        human = f"in {int(abs(days))} days"
else:
    hours = duration.in_hours()
    if hours > 0:
        human = f"{int(hours)} hours ago"
    else:
        human = f"in {int(abs(hours))} hours"
print(human)
Formatting and Output¶
Built-in Formats¶
from carbonic import DateTime
dt = DateTime(2024, 1, 15, 14, 30, 45, 123456, tz="UTC")
# Standard formats
print(dt.to_iso_string())      # "2024-01-15T14:30:45.123456+00:00"
print(dt.to_date_string())     # "2024-01-15"
print(dt.to_time_string())     # "14:30:45"
print(dt.to_datetime_string()) # "2024-01-15 14:30:45"
# With timezone conversion
ny_dt = dt.as_timezone("America/New_York")
print(ny_dt.to_iso_string())   # "2024-01-15T09:30:45.123456-05:00"
Custom Formatting¶
from carbonic import DateTime
dt = DateTime(2024, 1, 15, 14, 30, 45, tz="UTC")
# Carbon-style formatting
print(dt.format("Y-m-d H:i:s"))     # "2024-01-15 14:30:45"
print(dt.format("l, F j, Y"))       # "Monday, January 15, 2024"
print(dt.format("D M j G:i A"))     # "Mon Jan 15 14:30 PM"
# Python strftime (also supported)
print(dt.strftime("%Y-%m-%d %H:%M:%S"))  # "2024-01-15 14:30:45"
print(dt.strftime("%A, %B %d, %Y"))      # "Monday, January 15, 2024"
Format Tokens¶
Carbonic supports Carbon-style format tokens:
| Token | Description | Example | 
|---|---|---|
| Y | 4-digit year | 2024 | 
| y | 2-digit year | 24 | 
| m | Month (01-12) | 01 | 
| n | Month (1-12) | 1 | 
| M | Short month name | Jan | 
| F | Full month name | January | 
| d | Day (01-31) | 15 | 
| j | Day (1-31) | 15 | 
| D | Short day name | Mon | 
| l | Full day name | Monday | 
| H | Hour 24-format (00-23) | 14 | 
| G | Hour 24-format (0-23) | 14 | 
| h | Hour 12-format (01-12) | 02 | 
| g | Hour 12-format (1-12) | 2 | 
| i | Minutes (00-59) | 30 | 
| s | Seconds (00-59) | 45 | 
| A | AM/PM | PM | 
| a | am/pm | pm | 
Conversion Methods¶
To Other Types¶
from carbonic import DateTime
dt = DateTime(2024, 1, 15, 14, 30, 45, tz="UTC")
# To Python datetime
stdlib_dt = dt.to_datetime()
print(type(stdlib_dt))  # <class 'datetime.datetime'>
# To Date object
date_obj = dt.to_date()
print(date_obj)  # 2024-01-15
# To timestamp
timestamp = dt.to_datetime().timestamp()
print(timestamp)  # 1705330245.0
# To naive datetime (removes timezone)
naive_dt = dt.to_datetime().replace(tzinfo=None)
print(naive_dt.tzinfo)  # None
String Representations¶
from carbonic import DateTime
dt = DateTime(2024, 1, 15, 14, 30, 45, tz="UTC")
# Default string representation
print(str(dt))   # "2024-01-15T14:30:45+00:00"
print(repr(dt))  # "DateTime(2024, 1, 15, 14, 30, 45, tz='UTC')"
# For debugging - convert to standard datetime if needed
print(str(dt.to_datetime()))  # Shows as standard datetime format
Property Access¶
Date Components¶
from carbonic import DateTime
dt = DateTime(2024, 1, 15, 14, 30, 45, 123456, tz="UTC")
# Date properties
print(dt.year)         # 2024
print(dt.month)        # 1
print(dt.day)          # 15
print(dt.to_datetime().weekday())  # 0
print(dt.to_datetime().timetuple().tm_yday)  # 15
print(dt.to_datetime().isocalendar().week)  # 3
# Time properties
print(dt.hour)         # 14
print(dt.minute)       # 30
print(dt.second)       # 45
print(dt.microsecond)  # 123456
# Timezone properties
print(dt.tzinfo)       # <ZoneInfo 'UTC'>
print(dt.to_datetime().utcoffset())  # datetime.timedelta(0)
Derived Properties¶
from carbonic import DateTime
dt = DateTime(2024, 1, 15, 14, 30, 45, tz="UTC")
# Calculate quarter information
quarter = (dt.month - 1) // 3 + 1
print(f"Quarter: {quarter}")  # 1
# Week information using underlying datetime
print(f"Week of year: {dt.to_datetime().isocalendar().week}")  # 3
# Month information using calendar module
import calendar
print(f"Days in month: {calendar.monthrange(dt.year, dt.month)[1]}")  # 31
# Timezone information
print(f"Timezone: {dt.tzinfo}")  # UTC
Best Practices¶
Always Use Timezones¶
from carbonic import DateTime
# Good - explicit timezone
dt = DateTime(2024, 1, 15, 14, 30, tz="UTC")
# Better - use factory methods for current time
current = DateTime.now("America/New_York")
# Avoid - naive datetime can cause bugs
naive = DateTime(2024, 1, 15, 14, 30, tz=None)
Chain Operations Fluently¶
from carbonic import DateTime
from zoneinfo import ZoneInfo
# Good - readable chains
result = (DateTime.now()
    .add(days=1)
    .start_of("day")
    .format("Y-m-d H:i:s")
)
# For timezone conversion, use as_timezone() method
result = (DateTime.now()
    .add(days=1)
    .start_of("day")
    .as_timezone("America/New_York")
    .format("Y-m-d H:i:s")
)
# Also good - intermediate variables for clarity
tomorrow = DateTime.now().add(days=1)
start_of_tomorrow = tomorrow.start_of("day")
ny_time = start_of_tomorrow.as_timezone("America/New_York")
formatted = ny_time.format("Y-m-d H:i:s")
Handle Edge Cases¶
from carbonic import DateTime
from zoneinfo import ZoneInfo
# Handle invalid dates
try:
    invalid = DateTime(2024, 2, 30, tz="UTC")  # February 30th
except ValueError as e:
    print(f"Invalid date: {e}")
# Handle timezone conversions carefully
dt = DateTime(2024, 3, 31, 2, 30, tz="America/New_York")  # During DST transition
utc_time = dt.as_timezone("UTC")  # This works correctly
Performance Tips¶
Reuse Base Objects¶
from carbonic import DateTime
# Create a base and derive from it
base = DateTime(2024, 1, 1, tz="UTC")
dates = [base.add(days=i) for i in range(365)]  # Efficient
Use Appropriate Methods¶
from carbonic import DateTime
dt = DateTime.now()
# Efficient - single operation
tomorrow = dt.add(days=1)
# Less efficient - multiple object creations
tomorrow = dt.add(hours=24)  # Still one operation, but conceptually less clear