Core Concepts¶
Understanding these fundamental concepts will help you work effectively with Carbonic.
Immutability¶
One of Carbonic's core principles is immutability. All datetime objects are frozen dataclasses that cannot be modified after creation.
Why Immutability?¶
from carbonic import DateTime
original = DateTime(2024, 1, 15, 10, 0, tz="UTC")
# This creates a NEW datetime object
modified = original.add(hours=2)
print(original)  # 2024-01-15T10:00:00+00:00 (unchanged!)
print(modified)  # 2024-01-15T12:00:00+00:00 (new object)
# This would raise an error:
# original.hour = 12  # FrozenInstanceError!
Benefits:
- Thread Safety: Immutable objects can be safely shared between threads
- Predictable Code: No unexpected mutations breaking your logic
- Easier Debugging: Values can't change unexpectedly
- Functional Programming: Natural fit for functional programming patterns
Working with Immutability¶
Since all operations return new objects, you can chain them fluently:
from carbonic import DateTime
dt = DateTime(2024, 1, 15, 9, 0, tz="UTC")
# Chain operations - each returns a new object
result = (dt
    .add(days=1)
    .add(hours=2)
    .start_of("hour")
)
# Or store intermediate results
next_day = dt.add(days=1)
with_hours = next_day.add(hours=2)
final = with_hours.start_of("hour")
Timezone Handling¶
Carbonic uses Python's standard library zoneinfo for robust timezone support.
Timezone-Aware by Default¶
from carbonic import DateTime
# Always specify timezone (UTC is default)
utc_time = DateTime(2024, 1, 15, 14, 30, tz="UTC")
local_time = DateTime(2024, 1, 15, 14, 30, tz="America/New_York")
# Current time methods default to UTC
current_utc = DateTime.now()  # UTC
current_local = DateTime.now("America/New_York")  # Local timezone
Timezone Conversions¶
from carbonic import DateTime
# Create in one timezone
paris_time = DateTime(2024, 1, 15, 15, 30, tz="Europe/Paris")
# Represent the same moment in different timezones
# Paris time: 15:30 CET (UTC+1)
tokyo_time = DateTime(2024, 1, 15, 23, 30, tz="Asia/Tokyo")   # Same moment, different timezone
utc_time = DateTime(2024, 1, 15, 14, 30, tz="UTC")           # Same moment in UTC
print(f"Paris: {paris_time}")  # 2024-01-15T15:30:00+01:00
print(f"Tokyo: {tokyo_time}")  # 2024-01-15T23:30:00+09:00
print(f"UTC:   {utc_time}")    # 2024-01-15T14:30:00+00:00
Naive vs Aware¶
Carbonic strongly encourages timezone-aware datetimes:
from carbonic import DateTime
# Timezone-aware (recommended)
aware = DateTime(2024, 1, 15, 14, 30, tz="UTC")
print(aware.tzinfo)  # <ZoneInfo 'UTC'>
# Naive datetime (discouraged)
naive = DateTime(2024, 1, 15, 14, 30, tz=None)
print(naive.tzinfo)  # None
# Converting naive to aware - create new DateTime with timezone
aware_from_naive = DateTime(naive.year, naive.month, naive.day, naive.hour, naive.minute, naive.second, tz="UTC")
Avoid Naive Datetimes
Naive datetimes (without timezone info) can lead to bugs and confusion. Always specify a timezone when possible.
Fluent API Design¶
Carbonic's API is designed to read naturally and chain operations:
Method Naming Convention¶
Methods are named to be readable and self-documenting:
from carbonic import DateTime
dt = DateTime(2024, 1, 15, 10, 30, tz="UTC")
# Descriptive method names
next_monday = dt.add(days=7)  # Simplified: add 7 days for next week
end_of_month = dt.end_of("month")
business_day = dt.to_date().add_business_days(1).to_datetime()
midnight = dt.start_of("day")
# Boolean methods start with "is_"
print(dt.to_date().is_weekend())      # False
print(dt.to_date().is_weekday())      # True (business day)
print(dt < DateTime.now())  # True (if current time is later)
# Navigation methods
midnight = dt.start_of("day")
Chaining Operations¶
The fluent API allows natural chaining:
from carbonic import DateTime
# Readable chains
dt = DateTime.now("UTC")
date_obj = dt.to_date()
next_business_date = date_obj.add_business_days(1)
next_business_dt = DateTime(
    next_business_date.year,
    next_business_date.month,
    next_business_date.day,
    0,
    0,
    0,
    tz="UTC",
)
next_business_end = next_business_dt.end_of("day").subtract(hours=1)
# Equivalent to:
now = DateTime.now("UTC")
next_business_date = now.to_date().add_business_days(1)
next_business = DateTime(
    next_business_date.year,
    next_business_date.month,
    next_business_date.day,
    0,
    0,
    0,
    tz="UTC",
)
end_of_day = next_business.end_of("day")
result = end_of_day.subtract(hours=1)
Type Safety¶
Carbonic is designed with strong typing throughout:
Full Type Annotations¶
from carbonic import DateTime, Duration, Date
# All methods are fully typed
dt: DateTime = DateTime.now()
duration: Duration = Duration(hours=2)
future: DateTime = dt + duration  # Type checker knows this is DateTime
# Date operations return Date objects
date: Date = dt.to_date()
tomorrow: Date = date.add(days=1)
Generic Return Types¶
Methods return the appropriate types:
from carbonic import DateTime, Date
dt = DateTime(2024, 1, 15, 14, 30, tz="UTC")
date = Date(2024, 1, 15)
# DateTime methods return DateTime
new_dt: DateTime = dt.add(hours=2)
# Date methods return Date
new_date: Date = date.add(days=1)
# Conversion methods return appropriate types
converted_date: Date = dt.to_date()
converted_datetime: DateTime = date.to_datetime(tz="UTC")
IDE Support¶
Strong typing enables excellent IDE support:
- Autocomplete: See all available methods
- Type Checking: Catch errors before runtime
- Refactoring: Safe renaming and restructuring
- Documentation: Inline help and parameter hints
Performance Considerations¶
Carbonic is designed for both ease of use and performance:
Efficient Immutability¶
from carbonic import DateTime
# Objects use __slots__ for memory efficiency  
dt = DateTime.now()
print(f"DateTime object: {dt}")
# Internal datetime is reused when possible
dt1 = DateTime(2024, 1, 15, 10, 0, tz="UTC")
dt2 = DateTime(dt1.year, dt1.month, dt1.day, dt1.hour, 30, dt1.second, tz="UTC")  # Create new with different minute
Lazy Evaluation¶
Expensive operations are performed only when needed:
from carbonic import DateTime
dt = DateTime.now()
# Formatting is lazy - only computed when string is accessed
formatted = dt.format("l, F j, Y")  # Not computed yet
print(formatted)  # Now it's computed
Optional Acceleration¶
Use fast parsing when available:
# Install with: pip install carbonic[fast]
from carbonic import DateTime
# Uses ciso8601 if available, falls back to stdlib
dt = DateTime.parse("2024-01-15T14:30:00Z")  # Fast parsing
Error Handling¶
Carbonic uses specific exceptions for different error conditions:
from carbonic import DateTime
from carbonic.core.exceptions import ParseError
try:
    # Invalid timezone will raise ZoneInfoNotFoundError from zoneinfo
    dt = DateTime(2024, 1, 15, tz="Invalid/Timezone")
except Exception as e:
    print(f"Timezone error: {e}")
try:
    # Invalid date format
    dt = DateTime.parse("invalid", "Y-m-d")
except ParseError as e:
    print(f"Parse error: {e}")
Design Philosophy¶
Understanding Carbonic's design philosophy helps you use it effectively:
Principle of Least Surprise¶
Methods do what their names suggest:
from carbonic import DateTime
dt = DateTime(2024, 1, 15, 14, 30, tz="UTC")
# These do exactly what you'd expect
print(dt.start_of("day"))    # 2024-01-15T00:00:00+00:00
print(dt.end_of("month"))    # 2024-01-31T23:59:59.999999+00:00
print(dt.add(days=1))        # 2024-01-16T14:30:00+00:00
Explicit Over Implicit¶
When there's ambiguity, Carbonic requires explicit choices:
from carbonic import DateTime
# Explicit timezone specification
dt = DateTime(2024, 1, 15, tz="UTC")  # Clear intent
# Explicit conversion
naive_dt = DateTime(2024, 1, 15, tz=None)
aware_dt = DateTime(naive_dt.year, naive_dt.month, naive_dt.day, tz="UTC")  # Explicit conversion
Modern Python Features¶
Carbonic leverages modern Python capabilities:
- Dataclasses: Clean, efficient object definitions
- Type Hints: Full PEP 561 compliance
- Slots: Memory-efficient objects
- Union Types: Flexible parameter types (Python 3.12+ syntax)
Next Steps¶
Now that you understand the core concepts:
- Practice: Try the examples in the Quick Start
- Explore: Read the detailed User Guide
- Apply: Check out real-world Examples
- Reference: Use the API Documentation for details