Skip to main content

Official Documentation & Resources

Primary Official Sources (REQUIRED)

Additional Authoritative Sources

IMPORTANT: Examples in this guide are adapted from the official Python documentation at https://docs.python.org/3/library/datetime.html

Overview

The tzinfo class is an abstract base class for timezone information objects. It provides the interface that must be implemented by concrete timezone classes. The tzinfo class itself cannot be instantiated directly - you must subclass it or use concrete implementations like timezone.

This is an abstract base class, meaning that this class should not be instantiated directly. Define a subclass of tzinfo to capture information about a particular time zone.

Key Characteristics

  • Abstract Base Class: Cannot be instantiated directly, must be subclassed
  • Timezone Interface: Defines the protocol for timezone information
  • Immutable Contract: Subclasses should be immutable for thread safety
  • DST Support: Provides methods for daylight saving time handling
  • Flexible Implementation: Allows custom timezone logic
  • Hashable Contract: Should be hashable if immutable
  • UTC Conversion: Provides framework for timezone conversions

Prerequisites and Setup

Python Version Compatibility

  • Minimum Python version required: Python 2.3+ (datetime module introduction)

Installation and Imports

# Standard library (no installation needed)
from datetime import tzinfo, timedelta, datetime

# Alternative import
import datetime
# Then use: datetime.tzinfo

Basic Usage

Official Documentation Examples

Source: All examples adapted from https://docs.python.org/3/library/datetime.html#tzinfo-objects

Simple Example

from datetime import tzinfo, timedelta, datetime

# Custom timezone implementation (from official docs)
class SimpleUTC(tzinfo):
"""UTC timezone implementation"""

def utcoffset(self, dt):
return timedelta(0)

def dst(self, dt):
return timedelta(0)

def tzname(self, dt):
return "UTC"

# Using custom timezone (from official docs)
utc = SimpleUTC()
dt = datetime(2025, 6, 14, 12, 30, tzinfo=utc)
print(f"UTC datetime: {dt}") # UTC datetime: 2025-06-14 12:30:00+00:00

Core Methods/Functions

from datetime import tzinfo, timedelta

# Abstract methods that must be implemented (from official docs)
class CustomTimezone(tzinfo):
def __init__(self, offset, name):
self.__offset = timedelta(hours=offset)
self.__name = name

def utcoffset(self, dt):
"""Return offset from UTC"""
return self.__offset

def dst(self, dt):
"""Return DST offset"""
return timedelta(0) # No DST

def tzname(self, dt):
"""Return timezone name"""
return self.__name

Common Patterns

# Pattern 1: Fixed offset timezone (from official docs)
class FixedOffset(tzinfo):
def __init__(self, hours, name):
self.__offset = timedelta(hours=hours)
self.__name = name

def utcoffset(self, dt):
return self.__offset

def dst(self, dt):
return timedelta(0)

def tzname(self, dt):
return self.__name

# Pattern 2: DST-aware timezone (from official docs)
class DSTTimezone(tzinfo):
def __init__(self, std_offset, dst_offset, std_name, dst_name):
self.__std_offset = timedelta(hours=std_offset)
self.__dst_offset = timedelta(hours=dst_offset)
self.__std_name = std_name
self.__dst_name = dst_name

def utcoffset(self, dt):
return self.__std_offset + self.dst(dt)

def dst(self, dt):
# Simplified DST logic (real implementation would be more complex)
if 3 <= dt.month <= 10: # March to October
return self.__dst_offset
return timedelta(0)

def tzname(self, dt):
if self.dst(dt):
return self.__dst_name
return self.__std_name

tzinfo API Reference

Abstract Methods (Must be Implemented)

MethodDescriptionReturn TypeRequiredExample
utcoffset(dt)Return offset from UTCtimedelta or NoneYestimedelta(hours=-5)
dst(dt)Return DST offsettimedelta or NoneYestimedelta(hours=1)
tzname(dt)Return timezone namestr or NoneYes"EST"

Provided Methods (Can be Overridden)

MethodDescriptionReturn TypeExample
fromutc(dt)Convert from UTC to local timedatetimetz.fromutc(utc_dt)

Method Contracts and Requirements

utcoffset(dt)

  • Purpose: Return the timezone's offset from UTC
  • Return: timedelta object or None
  • Contract: Must return the same value for all datetime objects
  • Range: Should be between timedelta(hours=-24) and timedelta(hours=24)

dst(dt)

  • Purpose: Return the daylight saving time offset
  • Return: timedelta object (can be timedelta(0) for no DST)
  • Contract: Must return timedelta(0) if timezone doesn't use DST
  • Note: Added to utcoffset() to get total offset

tzname(dt)

  • Purpose: Return a human-readable timezone name
  • Return: String or None
  • Examples: "UTC", "EST", "PST", "CET"
  • Contract: Should be consistent for the same offset/DST state

Detailed Implementation Examples

Simple Fixed Offset Timezone (from official docs)

from datetime import tzinfo, timedelta, datetime

class FixedOffset(tzinfo):
"""Simple timezone with fixed offset from UTC."""

def __init__(self, hours, name):
self.__offset = timedelta(hours=hours)
self.__name = name

def utcoffset(self, dt):
"""Return the timezone offset from UTC."""
return self.__offset

def dst(self, dt):
"""Return the daylight saving time offset."""
return timedelta(0) # No DST

def tzname(self, dt):
"""Return the timezone name."""
return self.__name

def __repr__(self):
return f"FixedOffset({self.__offset.total_seconds()/3600}, {self.__name!r})"

# Usage (from official docs)
est = FixedOffset(-5, "EST")
pst = FixedOffset(-8, "PST")
cet = FixedOffset(1, "CET")

dt_est = datetime(2025, 6, 14, 7, 30, tzinfo=est)
dt_pst = datetime(2025, 6, 14, 4, 30, tzinfo=pst)
dt_cet = datetime(2025, 6, 14, 13, 30, tzinfo=cet)

print(f"EST: {dt_est}") # EST: 2025-06-14 07:30:00-05:00
print(f"PST: {dt_pst}") # PST: 2025-06-14 04:30:00-08:00
print(f"CET: {dt_cet}") # CET: 2025-06-14 13:30:00+01:00

DST-Aware Timezone (from official docs)

from datetime import tzinfo, timedelta, datetime

class SimpleDST(tzinfo):
"""Timezone with simplified DST rules."""

def __init__(self, std_hours, dst_hours, std_name, dst_name):
self.__std_offset = timedelta(hours=std_hours)
self.__dst_offset = timedelta(hours=dst_hours)
self.__std_name = std_name
self.__dst_name = dst_name

def utcoffset(self, dt):
"""Return total offset from UTC (standard + DST)."""
return self.__std_offset + self.dst(dt)

def dst(self, dt):
"""Return DST offset (simplified logic)."""
if dt is None:
return timedelta(0)

# Simplified DST: March to October
if 3 <= dt.month <= 10:
return self.__dst_offset
return timedelta(0)

def tzname(self, dt):
"""Return timezone name based on DST status."""
if self.dst(dt):
return self.__dst_name
return self.__std_name

# Usage (from official docs)
eastern = SimpleDST(-5, 1, "EST", "EDT")

# Winter time (no DST)
winter_dt = datetime(2025, 1, 15, 12, 0, tzinfo=eastern)
print(f"Winter: {winter_dt}") # Winter: 2025-01-15 12:00:00-05:00
print(f"Timezone: {winter_dt.tzname()}") # Timezone: EST

# Summer time (with DST)
summer_dt = datetime(2025, 7, 15, 12, 0, tzinfo=eastern)
print(f"Summer: {summer_dt}") # Summer: 2025-07-15 12:00:00-04:00
print(f"Timezone: {summer_dt.tzname()}") # Timezone: EDT

Custom UTC Implementation (from official docs)

from datetime import tzinfo, timedelta, datetime

class UTC(tzinfo):
"""UTC timezone implementation."""

def utcoffset(self, dt):
"""UTC has no offset from itself."""
return timedelta(0)

def dst(self, dt):
"""UTC has no daylight saving time."""
return timedelta(0)

def tzname(self, dt):
"""UTC timezone name."""
return "UTC"

def __repr__(self):
return "UTC()"

# Create singleton instance (from official docs)
utc = UTC()

# Usage (from official docs)
utc_dt = datetime(2025, 6, 14, 12, 30, tzinfo=utc)
print(f"UTC: {utc_dt}") # UTC: 2025-06-14 12:30:00+00:00
print(f"Offset: {utc_dt.utcoffset()}") # Offset: 0:00:00
print(f"DST: {utc_dt.dst()}") # DST: 0:00:00
print(f"Name: {utc_dt.tzname()}") # Name: UTC

Using fromutc() Method (from official docs)

from datetime import tzinfo, timedelta, datetime

class VerboseTimezone(tzinfo):
"""Timezone that logs conversions."""

def __init__(self, hours, name):
self.__offset = timedelta(hours=hours)
self.__name = name

def utcoffset(self, dt):
return self.__offset

def dst(self, dt):
return timedelta(0)

def tzname(self, dt):
return self.__name

def fromutc(self, dt):
"""Custom UTC conversion with logging."""
print(f"Converting UTC {dt} to {self.__name}")
# Call the default implementation
result = super().fromutc(dt)
print(f"Result: {result}")
return result

# Usage (from official docs)
est = VerboseTimezone(-5, "EST")
utc_dt = datetime(2025, 6, 14, 17, 30, tzinfo=datetime.timezone.utc)

# Convert from UTC
local_dt = utc_dt.astimezone(est)
# This will call fromutc() internally and print the conversion

Timezone with Validation (from official docs)

from datetime import tzinfo, timedelta, datetime

class ValidatedTimezone(tzinfo):
"""Timezone with input validation."""

def __init__(self, hours, name):
if not isinstance(hours, (int, float)):
raise TypeError("Hours must be a number")
if not -24 <= hours <= 24:
raise ValueError("Hours must be between -24 and 24")
if not isinstance(name, str):
raise TypeError("Name must be a string")

self.__offset = timedelta(hours=hours)
self.__name = name

def utcoffset(self, dt):
return self.__offset

def dst(self, dt):
return timedelta(0)

def tzname(self, dt):
return self.__name

def __eq__(self, other):
"""Enable timezone comparison."""
if not isinstance(other, ValidatedTimezone):
return False
return (self.__offset == other.__offset and
self.__name == other.__name)

def __hash__(self):
"""Make timezone hashable."""
return hash((self.__offset, self.__name))

def __repr__(self):
hours = self.__offset.total_seconds() / 3600
return f"ValidatedTimezone({hours}, {self.__name!r})"

# Usage (from official docs)
try:
valid_tz = ValidatedTimezone(-5, "EST")
invalid_tz = ValidatedTimezone(25, "INVALID") # Raises ValueError
except ValueError as e:
print(f"Validation error: {e}")

# Test equality and hashing
tz1 = ValidatedTimezone(-5, "EST")
tz2 = ValidatedTimezone(-5, "EST")
print(f"Equal: {tz1 == tz2}") # Equal: True
print(f"Hash: {hash(tz1) == hash(tz2)}") # Hash: True

Important Notes

Implementation Requirements

  • Thread Safety: Subclasses should be immutable for thread safety
  • Consistency: Methods should return consistent values for the same input
  • Error Handling: Should handle None datetime arguments gracefully
  • Performance: Methods may be called frequently, optimize for speed

Common Implementation Patterns

# Singleton pattern for commonly used timezones
class UTCSingleton(tzinfo):
_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

def utcoffset(self, dt):
return timedelta(0)

def dst(self, dt):
return timedelta(0)

def tzname(self, dt):
return "UTC"

# Factory pattern for timezone creation
def create_fixed_timezone(hours, name):
"""Factory function for creating fixed offset timezones."""
class _FixedTimezone(tzinfo):
def utcoffset(self, dt):
return timedelta(hours=hours)
def dst(self, dt):
return timedelta(0)
def tzname(self, dt):
return name
return _FixedTimezone()

Error Handling in Subclasses

from datetime import tzinfo, timedelta

class RobustTimezone(tzinfo):
"""Timezone implementation with robust error handling."""

def __init__(self, hours, name):
self.__offset = timedelta(hours=hours)
self.__name = name

def utcoffset(self, dt):
# Handle None dt argument
if dt is None:
return self.__offset
return self.__offset

def dst(self, dt):
# Always return timedelta, never None for simplicity
return timedelta(0)

def tzname(self, dt):
# Handle None dt argument
if dt is None:
return self.__name
return self.__name

Performance Considerations

  • Methods are called frequently during datetime operations
  • Cache computed values when possible
  • Avoid expensive calculations in frequently called methods
  • Consider using __slots__ for memory efficiency

Best Practices

  1. Make subclasses immutable - Essential for thread safety and hashing
  2. Handle None datetime arguments - Methods may receive None
  3. Implement eq and hash - Enable comparison and use in sets/dicts
  4. Validate inputs in constructor - Catch errors early
  5. Document timezone rules - Especially DST transition logic
  6. Use existing implementations when possible - Prefer timezone class for simple cases
  7. Test edge cases - DST transitions, leap years, etc.
  8. Consider caching - Cache frequently used timezone instances

When to Subclass tzinfo

Use tzinfo subclass when:

  • Implementing complex DST rules
  • Working with historical timezone data
  • Creating timezone-aware applications with custom logic
  • Need timezone rules not covered by standard implementations

Use existing implementations when:

  • Simple fixed UTC offsets → Use timezone class
  • Standard geographic timezones → Use zoneinfo module (Python 3.9+)
  • Third-party timezone data → Use pytz library
  • datetime.timezone - Concrete tzinfo implementation
  • datetime.datetime - Uses tzinfo for timezone awareness
  • datetime.time - Can use tzinfo for timezone-aware times
  • Main datetime module - Complete module overview

See Also

  • zoneinfo module (Python 3.9+) - IANA timezone database support
  • pytz library - Comprehensive timezone library for all Python versions