Available for day contractsFrom 21st September I have availability for day and half day contracts. Please contact for more information.

Contact →
mikepreston.org

Python Pendulum

Drop-in replacement for datetime with timezone support, human-friendly parsing, and intuitive API.

Python Pendulum Cheatsheet

Drop-in replacement for datetime with timezone support, human-friendly parsing, and intuitive API.

Overview

Pendulum is a Python library that extends the standard datetime module with timezone handling, natural language parsing, and simpler date arithmetic. It provides an immutable DateTime class that's fully compatible with datetime.datetime whilst offering a more intuitive interface for common operations.

Common OperationsPendulum ObjectsDateTimeDateTimeDurationIntervalCreate/ParseTimezone ConvertAdd/SubtractFormatDiff/CompareCommon OperationsPendulum ObjectsDateTimeDateTimeDurationIntervalCreate/ParseTimezone ConvertAdd/SubtractFormatDiff/Compare

Installation

pip install pendulum
import pendulum

Creating DateTime Objects

Key Concepts

  • now() - Current datetime in specified timezone (UTC by default)
  • parse() - Parse ISO 8601 strings only (v3); use from_format() for any other pattern
  • create() - Create datetime from individual components
  • from_timestamp() - Create from Unix timestamp
  • instance() - Convert standard datetime to pendulum DateTime
parsecreatefrom_timestampinstancenow/todayStringDateTimeComponentsTimestampdatetimeNowparsecreatefrom_timestampinstancenow/todayStringDateTimeComponentsTimestampdatetimeNow

Common Patterns

import pendulum

# Current datetime
now = pendulum.now()                    # Local timezone
now_utc = pendulum.now('UTC')           # UTC
now_london = pendulum.now('Europe/London')

# Today/yesterday/tomorrow
today = pendulum.today()
yesterday = pendulum.yesterday()
tomorrow = pendulum.tomorrow()

# Parse ISO 8601 strings
dt = pendulum.parse('2024-03-15')
dt = pendulum.parse('2024-03-15T14:30:00')
# For non-ISO strings, use from_format():
dt = pendulum.from_format('15 Mar 2024 14:30', 'DD MMM YYYY HH:mm')

# Create from components
dt = pendulum.datetime(2024, 3, 15, 14, 30, 0)
dt = pendulum.datetime(2024, 3, 15, tz='Europe/London')

# From timestamp
dt = pendulum.from_timestamp(1710502245)
dt = pendulum.from_timestamp(1710502245, tz='UTC')

# Convert from standard datetime
from datetime import datetime
std_dt = datetime(2024, 3, 15, 14, 30)
pdt = pendulum.instance(std_dt)

Examples

Getting Current DateTime

import pendulum

# Current time in different timezones
now_local = pendulum.now()               # System timezone
now_utc = pendulum.now('UTC')            # UTC
now_london = pendulum.now('Europe/London')
now_tokyo = pendulum.now('Asia/Tokyo')
now_ny = pendulum.now('America/New_York')

print(f"Local:    {now_local}")
print(f"UTC:      {now_utc}")
print(f"London:   {now_london}")
print(f"Tokyo:    {now_tokyo}")
print(f"New York: {now_ny}")

# Access components
print(f"Year: {now_utc.year}")
print(f"Month: {now_utc.month}")
print(f"Day: {now_utc.day}")
print(f"Hour: {now_utc.hour}")
print(f"Minute: {now_utc.minute}")
print(f"Second: {now_utc.second}")
print(f"Microsecond: {now_utc.microsecond}")

# Day information
print(f"Day of week: {now_utc.day_of_week}")  # 0=Monday, 6=Sunday (ISO weekday)
print(f"Day of year: {now_utc.day_of_year}")
print(f"Week of year: {now_utc.week_of_year}")
print(f"Days in month: {now_utc.days_in_month}")

Creating Specific DateTimes

import pendulum

# Full datetime specification
dt = pendulum.datetime(2024, 3, 15, 14, 30, 45, 123456)
print(f"Full: {dt}")

# Date only (time defaults to midnight)
dt = pendulum.datetime(2024, 3, 15)
print(f"Date only: {dt}")  # 2024-03-15T00:00:00+00:00

# With timezone
dt_london = pendulum.datetime(2024, 3, 15, 14, 30, tz='Europe/London')
dt_utc = pendulum.datetime(2024, 3, 15, 14, 30, tz='UTC')
print(f"London: {dt_london}")
print(f"UTC: {dt_utc}")

# Create date and time objects
date = pendulum.date(2024, 3, 15)
time = pendulum.time(14, 30, 45)
print(f"Date: {date}")
print(f"Time: {time}")

# Today, yesterday, tomorrow
today = pendulum.today()
yesterday = pendulum.yesterday()
tomorrow = pendulum.tomorrow()
print(f"Today: {today}")
print(f"Yesterday: {yesterday}")
print(f"Tomorrow: {tomorrow}")

# In specific timezone
tomorrow_tokyo = pendulum.tomorrow('Asia/Tokyo')
print(f"Tomorrow in Tokyo: {tomorrow_tokyo}")

Parsing Strings

import pendulum

# ISO 8601 and similar formats supported by pendulum.parse()
dt = pendulum.parse('2024-03-15')
dt = pendulum.parse('2024-03-15T14:30:00')
dt = pendulum.parse('2024-03-15T14:30:00+00:00')
dt = pendulum.parse('2024-03-15T14:30:00Z')
dt = pendulum.parse('2024/03/15 14:30')

# Note: pendulum.parse() in v3 does NOT support natural language or
# ambiguous formats like '15 Mar 2024', 'March 15, 2024', '15/03/2024'
# Use from_format() for those instead:
dt = pendulum.from_format('15 Mar 2024', 'DD MMM YYYY')
dt = pendulum.from_format('15/03/2024', 'DD/MM/YYYY')

# With explicit timezone
dt = pendulum.parse('2024-03-15 14:30', tz='Europe/London')
print(f"Parsed with TZ: {dt}")

# From format string (like strptime)
dt = pendulum.from_format('15-03-2024', 'DD-MM-YYYY')
dt = pendulum.from_format('15 Mar 2024 14:30', 'DD MMM YYYY HH:mm')
print(f"From format: {dt}")

# Handle parsing errors
try:
    dt = pendulum.parse('invalid date')
except pendulum.parsing.exceptions.ParserError as e:
    print(f"Parse error: {e}")

Converting from Standard Library

import pendulum
from datetime import datetime, date, time

# From datetime
std_datetime = datetime(2024, 3, 15, 14, 30, 45)
pdt = pendulum.instance(std_datetime)
print(f"From datetime: {pdt}")

# From datetime with timezone
from datetime import timezone
std_utc = datetime(2024, 3, 15, 14, 30, tzinfo=timezone.utc)
pdt = pendulum.instance(std_utc)
print(f"From aware datetime: {pdt}")

# From timestamp
timestamp = 1710502245
dt = pendulum.from_timestamp(timestamp)
dt_utc = pendulum.from_timestamp(timestamp, tz='UTC')
print(f"From timestamp: {dt_utc}")

# Millisecond timestamp
ms_timestamp = 1710502245123
dt = pendulum.from_timestamp(ms_timestamp / 1000, tz='UTC')
print(f"From ms: {dt}")

Timezone Conversions

Key Concepts

  • in_timezone() - Convert to different timezone (full method name)
  • in_tz() - Shorthand alias for in_timezone
  • timezone - Access current timezone information
  • Immutable - Conversions return new objects, originals unchanged
in_timezonein_tzin_tzDateTime UTCDateTime LondonDateTime TokyoDateTime New YorkSame instant,differentrepresentationin_timezonein_tzin_tzDateTime UTCDateTime LondonDateTime TokyoDateTime New YorkSame instant,differentrepresentation

Common Patterns

import pendulum

# Create UTC datetime
dt_utc = pendulum.now('UTC')

# Convert to other timezones
dt_london = dt_utc.in_timezone('Europe/London')
dt_tokyo = dt_utc.in_tz('Asia/Tokyo')  # Shorthand
dt_ny = dt_utc.in_tz('America/New_York')

# Get timezone information
print(dt_london.timezone.name)    # Europe/London
print(dt_london.offset)           # Offset in seconds
print(dt_london.offset_hours)     # Offset in hours

# Check DST (is_dst, is_utc, is_local are methods in v3)
print(dt_london.is_dst())         # True/False

Examples

Basic Timezone Conversion

import pendulum

# Start with UTC
dt_utc = pendulum.datetime(2024, 3, 15, 12, 0, 0, tz='UTC')
print(f"UTC: {dt_utc}")

# Convert to various timezones
dt_london = dt_utc.in_timezone('Europe/London')
dt_paris = dt_utc.in_timezone('Europe/Paris')
dt_tokyo = dt_utc.in_timezone('Asia/Tokyo')
dt_ny = dt_utc.in_timezone('America/New_York')
dt_la = dt_utc.in_timezone('America/Los_Angeles')
dt_sydney = dt_utc.in_timezone('Australia/Sydney')

print(f"London:  {dt_london}")   # 12:00 or 13:00 (DST)
print(f"Paris:   {dt_paris}")    # 13:00 or 14:00 (DST)
print(f"Tokyo:   {dt_tokyo}")    # 21:00
print(f"NYC:     {dt_ny}")       # 07:00 or 08:00 (DST)
print(f"LA:      {dt_la}")       # 04:00 or 05:00 (DST)
print(f"Sydney:  {dt_sydney}")   # 23:00 or 22:00 (DST)

# Using shorthand in_tz()
dt_berlin = dt_utc.in_tz('Europe/Berlin')
print(f"Berlin:  {dt_berlin}")

Timezone Information

import pendulum

dt = pendulum.now('Europe/London')

# Timezone properties
print(f"Timezone name: {dt.timezone.name}")     # Europe/London
print(f"Offset (seconds): {dt.offset}")         # 0 or 3600
print(f"Offset (hours): {dt.offset_hours}")     # 0 or 1
print(f"Is DST: {dt.is_dst()}")                 # True/False (method in v3)
print(f"Timezone abbr: {dt.timezone_name}")     # GMT or BST

# UTC offset as string
print(f"UTC offset: {dt.format('Z')}")          # +00:00 or +01:00

# Check if local timezone (methods in v3)
print(f"Is local: {dt.is_local()}")
print(f"Is UTC: {dt.is_utc()}")

Working with Local Time

import pendulum

# Get local timezone datetime
local_now = pendulum.now()
print(f"Local now: {local_now}")
print(f"Local TZ: {local_now.timezone.name}")

# Convert local to UTC
utc_now = local_now.in_tz('UTC')
print(f"As UTC: {utc_now}")

# Create in local, convert to UTC for storage
user_input = pendulum.parse('2024-03-15 14:30')
# Assume user input is in their local timezone
utc_for_storage = user_input.in_tz('UTC')
print(f"Store as UTC: {utc_for_storage}")

# Retrieve and display in user's timezone
stored_utc = pendulum.parse('2024-03-15T14:30:00+00:00')
user_tz = 'America/New_York'
display_time = stored_utc.in_tz(user_tz)
print(f"Display to user: {display_time}")

DST Handling

import pendulum

# Summer time (BST) - clocks forward
summer = pendulum.datetime(2024, 7, 15, 12, 0, tz='Europe/London')
print(f"Summer: {summer}")
print(f"Is DST: {summer.is_dst()}")      # True (method in v3)
print(f"Offset: {summer.offset_hours}h") # 1

# Winter time (GMT) - clocks back
winter = pendulum.datetime(2024, 1, 15, 12, 0, tz='Europe/London')
print(f"Winter: {winter}")
print(f"Is DST: {winter.is_dst()}")      # False (method in v3)
print(f"Offset: {winter.offset_hours}h") # 0

# Same UTC instant, different local times
utc_time = pendulum.datetime(2024, 7, 15, 11, 0, tz='UTC')
london = utc_time.in_tz('Europe/London')
print(f"UTC: {utc_time}")    # 11:00
print(f"London: {london}")   # 12:00 (BST)

utc_time = pendulum.datetime(2024, 1, 15, 11, 0, tz='UTC')
london = utc_time.in_tz('Europe/London')
print(f"UTC: {utc_time}")    # 11:00
print(f"London: {london}")   # 11:00 (GMT)

Common Timezone Operations

import pendulum

# List available timezones
timezones = pendulum.timezones()
print(f"Total timezones: {len(timezones)}")

# Find timezones for a region
europe_tzs = [tz for tz in timezones if tz.startswith('Europe/')]
print(f"European timezones: {len(europe_tzs)}")

# Convert between multiple timezones
dt = pendulum.now('UTC')
cities = {
    'London': 'Europe/London',
    'Tokyo': 'Asia/Tokyo',
    'New York': 'America/New_York',
    'Sydney': 'Australia/Sydney',
}

for city, tz in cities.items():
    local_time = dt.in_tz(tz)
    print(f"{city}: {local_time.format('HH:mm')} ({local_time.timezone_name})")

# Business hours check across timezones
def is_business_hours(dt, tz):
    """Check if time is within 9-17 in given timezone."""
    local = dt.in_tz(tz)
    return 9 <= local.hour < 17

utc_now = pendulum.now('UTC')
for city, tz in cities.items():
    is_open = is_business_hours(utc_now, tz)
    status = "Open" if is_open else "Closed"
    print(f"{city}: {status}")

Date Arithmetic

Key Concepts

  • add() - Add time units (returns new DateTime)
  • subtract() - Subtract time units (returns new DateTime)
  • diff() - Get difference between two DateTimes
  • Immutable - All operations return new objects
  • Chainable - Methods can be chained together
addsubtractdiffTime UnitsyearsmonthsweeksdayshoursminutessecondsDateTimeFuture DateTimePast DateTimeDuration/IntervaladdsubtractdiffTime UnitsyearsmonthsweeksdayshoursminutessecondsDateTimeFuture DateTimePast DateTimeDuration/Interval

Common Patterns

import pendulum

dt = pendulum.now()

# Add time units
future = dt.add(days=7)
future = dt.add(months=1, days=15)
future = dt.add(years=1, months=2, weeks=3, days=4)

# Subtract time units
past = dt.subtract(weeks=2)
past = dt.subtract(hours=5, minutes=30)

# Calculate difference
diff = dt.diff(other_dt)
print(diff.in_days())
print(diff.in_hours())

# Chaining
result = dt.add(days=7).subtract(hours=2).add(minutes=30)

Examples

Adding Time

import pendulum

dt = pendulum.datetime(2024, 3, 15, 12, 0, 0, tz='UTC')

# Add various units
plus_1_year = dt.add(years=1)
plus_3_months = dt.add(months=3)
plus_2_weeks = dt.add(weeks=2)
plus_10_days = dt.add(days=10)
plus_5_hours = dt.add(hours=5)
plus_30_mins = dt.add(minutes=30)
plus_45_secs = dt.add(seconds=45)

print(f"Original: {dt}")
print(f"+1 year:  {plus_1_year}")
print(f"+3 months: {plus_3_months}")
print(f"+2 weeks: {plus_2_weeks}")
print(f"+10 days: {plus_10_days}")
print(f"+5 hours: {plus_5_hours}")

# Multiple units at once
combined = dt.add(years=1, months=2, days=15, hours=3)
print(f"Combined: {combined}")

# Month arithmetic handles edge cases
jan_31 = pendulum.datetime(2024, 1, 31)
plus_1_month = jan_31.add(months=1)
print(f"Jan 31 + 1 month: {plus_1_month}")  # Feb 29 (leap year)

jan_31_2023 = pendulum.datetime(2023, 1, 31)
plus_1_month = jan_31_2023.add(months=1)
print(f"Jan 31 + 1 month (2023): {plus_1_month}")  # Feb 28

Subtracting Time

import pendulum

dt = pendulum.datetime(2024, 3, 15, 12, 0, 0, tz='UTC')

# Subtract various units
minus_1_year = dt.subtract(years=1)
minus_6_months = dt.subtract(months=6)
minus_3_weeks = dt.subtract(weeks=3)
minus_5_days = dt.subtract(days=5)
minus_2_hours = dt.subtract(hours=2)

print(f"Original: {dt}")
print(f"-1 year:   {minus_1_year}")
print(f"-6 months: {minus_6_months}")
print(f"-3 weeks:  {minus_3_weeks}")
print(f"-5 days:   {minus_5_days}")
print(f"-2 hours:  {minus_2_hours}")

# Multiple units
past = dt.subtract(years=2, months=3, days=10)
print(f"Combined: {past}")

# Chaining operations
result = dt.add(months=1).subtract(days=5).add(hours=3)
print(f"Chained: {result}")

Calculating Differences

import pendulum

dt1 = pendulum.datetime(2024, 1, 1, tz='UTC')
dt2 = pendulum.datetime(2024, 12, 31, 23, 59, 59, tz='UTC')

# Get difference
diff = dt1.diff(dt2)

# Access difference in various units
print(f"Years: {diff.in_years()}")
print(f"Months: {diff.in_months()}")
print(f"Weeks: {diff.in_weeks()}")
print(f"Days: {diff.in_days()}")
print(f"Hours: {diff.in_hours()}")
print(f"Minutes: {diff.in_minutes()}")
print(f"Seconds: {diff.in_seconds()}")

# Human-readable difference
print(f"Human: {diff.in_words()}")  # "11 months 30 days 23 hours..."

# Absolute difference (always positive)
diff_abs = dt2.diff(dt1, abs=True)
print(f"Absolute days: {diff_abs.in_days()}")

# Difference from now
past_date = pendulum.datetime(2020, 1, 1, tz='UTC')
diff_from_now = past_date.diff(pendulum.now('UTC'))
print(f"Since Jan 2020: {diff_from_now.in_years()} years")

Comparison Operations

import pendulum

dt1 = pendulum.datetime(2024, 3, 15, tz='UTC')
dt2 = pendulum.datetime(2024, 6, 20, tz='UTC')
dt3 = pendulum.datetime(2024, 3, 15, tz='UTC')

# Basic comparisons
print(dt1 < dt2)   # True
print(dt1 > dt2)   # False
print(dt1 == dt3)  # True
print(dt1 != dt2)  # True
print(dt1 <= dt3)  # True

# Comparison helpers
print(dt1.is_past())         # True/False based on now
print(dt2.is_future())       # True/False based on now
print(dt1.is_today())        # True/False
print(dt1.is_birthday())     # Same month and day as today

# Date range checking (.between() removed in v3, use comparison)
start = pendulum.datetime(2024, 1, 1, tz='UTC')
end = pendulum.datetime(2024, 12, 31, tz='UTC')
check = pendulum.datetime(2024, 6, 15, tz='UTC')

is_between = start <= check <= end
print(f"Is between: {is_between}")  # True

# Closest/farthest
dates = [
    pendulum.datetime(2024, 1, 1, tz='UTC'),
    pendulum.datetime(2024, 6, 15, tz='UTC'),
    pendulum.datetime(2024, 12, 31, tz='UTC'),
]
now = pendulum.now('UTC')
closest = now.closest(*dates)
farthest = now.farthest(*dates)
print(f"Closest: {closest}")
print(f"Farthest: {farthest}")

Start and End of Periods

import pendulum

dt = pendulum.datetime(2024, 3, 15, 14, 30, 45, tz='UTC')

# Start of various periods
start_of_minute = dt.start_of('minute')
start_of_hour = dt.start_of('hour')
start_of_day = dt.start_of('day')
start_of_week = dt.start_of('week')
start_of_month = dt.start_of('month')
start_of_year = dt.start_of('year')

print(f"Start of minute: {start_of_minute}")
print(f"Start of hour:   {start_of_hour}")
print(f"Start of day:    {start_of_day}")
print(f"Start of week:   {start_of_week}")
print(f"Start of month:  {start_of_month}")
print(f"Start of year:   {start_of_year}")

# End of periods
end_of_day = dt.end_of('day')
end_of_week = dt.end_of('week')
end_of_month = dt.end_of('month')
end_of_year = dt.end_of('year')

print(f"End of day:   {end_of_day}")
print(f"End of month: {end_of_month}")
print(f"End of year:  {end_of_year}")

# Next/previous day of week
next_monday = dt.next(pendulum.MONDAY)
prev_friday = dt.previous(pendulum.FRIDAY)
print(f"Next Monday: {next_monday}")
print(f"Previous Friday: {prev_friday}")

# First/last of month
first_of_month = dt.first_of('month')
last_of_month = dt.last_of('month')
print(f"First of month: {first_of_month}")
print(f"Last of month: {last_of_month}")

Formatting and Parsing

Key Concepts

  • format() - Format with custom pattern or preset
  • to_datetime_string() - Output as 'YYYY-MM-DD HH:MM:SS'
  • to_iso8601_string() - Output as ISO 8601
  • from_format() - Parse with specific format
  • Tokens - Different from strftime (use DD not %d)

Format Tokens

Token Description Example
YYYY 4-digit year 2024
YY 2-digit year 24
MM Month (01-12) 03
MMM Short month Mar
MMMM Full month March
DD Day (01-31) 15
D Day (1-31) 15
dddd Full weekday Friday
ddd Short weekday Fri
HH Hour 24h (00-23) 14
hh Hour 12h (01-12) 02
mm Minute (00-59) 30
ss Second (00-59) 45
A AM/PM PM
Z Timezone offset +00:00
zz Timezone abbr UTC

Common Patterns

import pendulum

dt = pendulum.now('UTC')

# Common formats
iso = dt.to_iso8601_string()
datetime_str = dt.to_datetime_string()
date_str = dt.to_date_string()
time_str = dt.to_time_string()

# Custom format
custom = dt.format('DD/MM/YYYY HH:mm')
custom = dt.format('dddd, D MMMM YYYY')
custom = dt.format('YYYY-MM-DDTHH:mm:ssZ')

# Parse with format
dt = pendulum.from_format('15-03-2024', 'DD-MM-YYYY')

Examples

Basic Formatting

import pendulum

dt = pendulum.datetime(2024, 3, 15, 14, 30, 45, tz='UTC')

# Built-in format methods
print(dt.to_datetime_string())      # 2024-03-15 14:30:45
print(dt.to_date_string())          # 2024-03-15
print(dt.to_time_string())          # 14:30:45
print(dt.to_iso8601_string())       # 2024-03-15T14:30:45Z (UTC uses Z suffix)
print(dt.to_atom_string())          # 2024-03-15T14:30:45+00:00
print(dt.to_rfc822_string())        # Fri, 15 Mar 24 14:30:45 +0000
print(dt.to_cookie_string())        # Friday, 15-Mar-2024 14:30:45 UTC
print(dt.to_w3c_string())           # 2024-03-15T14:30:45+00:00

# String representation
print(str(dt))                      # 2024-03-15T14:30:45+00:00

Custom Format Strings

import pendulum

dt = pendulum.datetime(2024, 3, 15, 14, 30, 45, tz='Europe/London')

# Date formats
print(dt.format('YYYY-MM-DD'))          # 2024-03-15
print(dt.format('DD/MM/YYYY'))          # 15/03/2024
print(dt.format('D MMM YYYY'))          # 15 Mar 2024
print(dt.format('MMMM D, YYYY'))        # March 15, 2024
print(dt.format('dddd, D MMMM YYYY'))   # Friday, 15 March 2024

# Time formats
print(dt.format('HH:mm:ss'))            # 14:30:45
print(dt.format('HH:mm'))               # 14:30
print(dt.format('hh:mm A'))             # 02:30 PM
print(dt.format('h:mm a'))              # 2:30 pm

# Combined formats
print(dt.format('YYYY-MM-DD HH:mm:ss')) # 2024-03-15 14:30:45
print(dt.format('D MMM YYYY, HH:mm'))   # 15 Mar 2024, 14:30
print(dt.format('ddd DD/MM/YY HH:mm'))  # Fri 15/03/24 14:30

# With timezone
print(dt.format('YYYY-MM-DDTHH:mm:ssZ'))      # 2024-03-15T14:30:45+00:00
print(dt.format('DD MMM YYYY HH:mm zz'))      # 15 Mar 2024 14:30 GMT
print(dt.format('YYYY-MM-DD HH:mm:ss ZZ'))    # 2024-03-15 14:30:45 +0000

# Escaped text
print(dt.format('[Date:] DD/MM/YYYY'))  # Date: 15/03/2024

Parsing with Formats

import pendulum

# Parse with specific format
dt = pendulum.from_format('15-03-2024', 'DD-MM-YYYY')
print(f"Parsed: {dt}")

# Various format examples
dt = pendulum.from_format('2024/03/15 14:30', 'YYYY/MM/DD HH:mm')
dt = pendulum.from_format('15 Mar 2024 14:30:45', 'DD MMM YYYY HH:mm:ss')
dt = pendulum.from_format('March 15, 2024', 'MMMM DD, YYYY')
dt = pendulum.from_format('Fri, 15 Mar 2024', 'ddd, DD MMM YYYY')

# 12-hour format
dt = pendulum.from_format('03/15/2024 02:30 PM', 'MM/DD/YYYY hh:mm A')
print(f"12-hour parsed: {dt}")  # 14:30

# With timezone
dt = pendulum.from_format('2024-03-15T14:30:00+00:00',
                          'YYYY-MM-DDTHH:mm:ssZ')
print(f"With TZ: {dt}")

# Specify timezone for naive strings
dt = pendulum.from_format('15/03/2024 14:30', 'DD/MM/YYYY HH:mm',
                          tz='Europe/London')
print(f"With explicit TZ: {dt}")

Human-Readable Differences

import pendulum

now = pendulum.now('UTC')
past = now.subtract(hours=2, minutes=30)
future = now.add(days=3, hours=5)

# Human-readable diff
print(past.diff_for_humans())        # 2 hours ago
print(future.diff_for_humans())      # in 3 days

# More examples
yesterday = now.subtract(days=1)
last_week = now.subtract(weeks=1)
last_month = now.subtract(months=1)
last_year = now.subtract(years=1)

print(yesterday.diff_for_humans())   # 1 day ago
print(last_week.diff_for_humans())   # 1 week ago
print(last_month.diff_for_humans())  # 1 month ago
print(last_year.diff_for_humans())   # 1 year ago

# Future times
tomorrow = now.add(days=1)
next_week = now.add(weeks=1)

print(tomorrow.diff_for_humans())    # in 1 day
print(next_week.diff_for_humans())   # in 1 week

# Absolute (without "ago" or "in")
print(past.diff_for_humans(absolute=True))  # 2 hours

# Compared to other date
ref = pendulum.datetime(2024, 1, 1, tz='UTC')
dt = pendulum.datetime(2024, 3, 15, tz='UTC')
print(dt.diff_for_humans(ref))  # 2 months after

Interval and Duration Operations

Key Concepts

  • Duration - Represents a fixed length of time (like timedelta)
  • Interval - Represents the difference between two DateTime instances (was Period in v2)
  • Iteration - Intervals can be iterated over by various intervals
  • Arithmetic - Durations can be added/subtracted from DateTimes
diffiterateadd tocomponentsDateTime 1IntervalDateTime 2List of DateTimesDurationyears, months, days,etc.diffiterateadd tocomponentsDateTime 1IntervalDateTime 2List of DateTimesDurationyears, months, days,etc.

Common Patterns

import pendulum

# Create duration
duration = pendulum.duration(days=5, hours=3)

# Create interval from two datetimes
start = pendulum.datetime(2024, 1, 1, tz='UTC')
end = pendulum.datetime(2024, 3, 15, tz='UTC')
interval = pendulum.interval(start, end)

# Alternative: use diff
interval = start.diff(end)

# Iterate over interval
for dt in interval.range('days'):
    print(dt)

# Get duration components
print(duration.in_days())
print(duration.in_hours())

Examples

Working with Durations

import pendulum

# Create durations
d1 = pendulum.duration(days=5)
d2 = pendulum.duration(hours=10, minutes=30)
d3 = pendulum.duration(weeks=2, days=3)
d4 = pendulum.duration(years=1, months=2, days=15)

print(f"5 days: {d1}")
print(f"10h 30m: {d2}")

# Duration arithmetic
combined = d1 + d2
print(f"Combined: {combined}")

doubled = d1 * 2
print(f"Doubled: {doubled}")

# Add duration to datetime
dt = pendulum.datetime(2024, 3, 15, tz='UTC')
future = dt + d1
print(f"Plus 5 days: {future}")

# Subtract duration
past = dt - d2
print(f"Minus 10h 30m: {past}")

# Get total in various units
d = pendulum.duration(days=2, hours=12)
print(f"Total hours: {d.in_hours()}")       # 60
print(f"Total minutes: {d.in_minutes()}")   # 3600
print(f"Total seconds: {d.in_seconds()}")   # 216000

# Duration properties
print(f"Days: {d.days}")
print(f"Hours: {d.hours}")
print(f"Minutes: {d.minutes}")

Working with Intervals

import pendulum

# Create interval from two datetimes
start = pendulum.datetime(2024, 1, 1, tz='UTC')
end = pendulum.datetime(2024, 3, 15, tz='UTC')

interval = pendulum.interval(start, end)

# Or using diff
interval = start.diff(end)

# Interval properties
print(f"Start: {interval.start}")
print(f"End: {interval.end}")
print(f"In days: {interval.in_days()}")
print(f"In weeks: {interval.in_weeks()}")
print(f"In months: {interval.in_months()}")

# Human-readable
print(f"Human: {interval.in_words()}")  # 2 months 2 weeks

# Interval as absolute (always positive)
interval_abs = pendulum.interval(end, start, absolute=True)
print(f"Absolute: {interval_abs.in_days()}")

Iterating Over Intervals

import pendulum

start = pendulum.datetime(2024, 3, 1, tz='UTC')
end = pendulum.datetime(2024, 3, 10, tz='UTC')

interval = pendulum.interval(start, end)

# Iterate by days
print("Days in interval:")
for dt in interval.range('days'):
    print(f"  {dt.format('ddd DD MMM')}")

# Iterate by hours
interval_short = pendulum.interval(
    pendulum.datetime(2024, 3, 1, 9, 0, tz='UTC'),
    pendulum.datetime(2024, 3, 1, 17, 0, tz='UTC')
)
print("\nHours in workday:")
for dt in interval_short.range('hours'):
    print(f"  {dt.format('HH:mm')}")

# Iterate by weeks
start = pendulum.datetime(2024, 1, 1, tz='UTC')
end = pendulum.datetime(2024, 3, 31, tz='UTC')
interval = pendulum.interval(start, end)

print("\nWeeks in Q1:")
for dt in interval.range('weeks'):
    print(f"  Week of {dt.format('D MMM')}")

# Iterate by months
print("\nMonths in Q1:")
for dt in interval.range('months'):
    print(f"  {dt.format('MMMM YYYY')}")

# Custom step
print("\nEvery 2 days:")
for dt in interval.range('days', 2):
    print(f"  {dt.format('DD MMM')}")

Interval Contains and Overlap

import pendulum

# Define intervals
interval1 = pendulum.interval(
    pendulum.datetime(2024, 3, 1, tz='UTC'),
    pendulum.datetime(2024, 3, 31, tz='UTC')
)

interval2 = pendulum.interval(
    pendulum.datetime(2024, 3, 15, tz='UTC'),
    pendulum.datetime(2024, 4, 15, tz='UTC')
)

# Check if datetime is in interval (use comparison, .between() removed in v3)
check_date = pendulum.datetime(2024, 3, 10, tz='UTC')
is_in = interval1.start <= check_date <= interval1.end
print(f"March 10 in interval: {is_in}")  # True

# Count specific days in interval
interval = pendulum.interval(
    pendulum.datetime(2024, 3, 1, tz='UTC'),
    pendulum.datetime(2024, 3, 31, tz='UTC')
)

# Count weekdays (pendulum v3: Mon=0 ... Sun=6, Sat=5)
weekdays = sum(1 for dt in interval.range('days')
               if dt.day_of_week not in [5, 6])  # 5=Sat, 6=Sun
print(f"Weekdays in March: {weekdays}")

# Count specific day (e.g., Fridays — Friday=4)
fridays = sum(1 for dt in interval.range('days')
              if dt.day_of_week == 4)  # Friday
print(f"Fridays in March: {fridays}")

Business Day Calculations

import pendulum

def add_business_days(dt, days):
    """Add business days (skip weekends)."""
    result = dt
    added = 0
    while added < days:
        result = result.add(days=1)
        # Skip weekends (pendulum v3: Mon=0...Fri=4, Sat=5, Sun=6)
        if result.day_of_week not in [5, 6]:
            added += 1
    return result

def business_days_between(start, end):
    """Count business days between two dates."""
    interval = pendulum.interval(start, end)
    return sum(1 for dt in interval.range('days')
               if dt.day_of_week not in [5, 6])

# Example usage
start = pendulum.datetime(2024, 3, 1, tz='UTC')  # Friday

# Add 5 business days
result = add_business_days(start, 5)
print(f"5 business days from {start.format('ddd D MMM')}: "
      f"{result.format('ddd D MMM')}")

# Count business days in March
end = pendulum.datetime(2024, 3, 31, tz='UTC')
count = business_days_between(start, end)
print(f"Business days in March: {count}")

Quick Reference

Creating DateTimes

Operation Code
Current (local) pendulum.now()
Current (UTC) pendulum.now('UTC')
Current (specific TZ) pendulum.now('Europe/London')
Today pendulum.today()
Yesterday pendulum.yesterday()
Tomorrow pendulum.tomorrow()
From components pendulum.datetime(2024, 3, 15, 14, 30)
Parse string pendulum.parse('2024-03-15')
From format pendulum.from_format('15/03/2024', 'DD/MM/YYYY')
From timestamp pendulum.from_timestamp(1710502245)
From stdlib datetime pendulum.instance(dt)

Timezone Operations

Operation Code
Convert timezone dt.in_timezone('Asia/Tokyo')
Convert (shorthand) dt.in_tz('Asia/Tokyo')
Get timezone name dt.timezone.name
Get offset hours dt.offset_hours
Check DST dt.is_dst()
Check if UTC dt.is_utc()

Date Arithmetic

Operation Code
Add days dt.add(days=7)
Subtract months dt.subtract(months=2)
Add multiple dt.add(years=1, months=2, days=3)
Get difference dt1.diff(dt2)
Diff in days dt1.diff(dt2).in_days()
Human diff dt.diff_for_humans()
Start of day dt.start_of('day')
End of month dt.end_of('month')
Next Monday dt.next(pendulum.MONDAY)

Formatting

Format Code
ISO 8601 dt.to_iso8601_string()
Date string dt.to_date_string()
Time string dt.to_time_string()
Custom format dt.format('DD/MM/YYYY HH:mm')
Human-readable dt.diff_for_humans()

Duration and Interval

Operation Code
Create duration pendulum.duration(days=5, hours=3)
Create interval pendulum.interval(start, end)
Duration to datetime dt + duration
Iterate interval interval.range('days')
Interval in words interval.in_words()

Common Issues and Solutions

Issue: Mixing Pendulum and stdlib datetime

Problem: TypeError when comparing pendulum DateTime with stdlib datetime

Solution:

import pendulum
from datetime import datetime

# Convert stdlib to pendulum
std_dt = datetime(2024, 3, 15, 14, 30)
pdt = pendulum.instance(std_dt)

# Now comparison works
now = pendulum.now()
is_past = pdt < now

# Or convert pendulum to stdlib if needed
std_result = pdt.naive()  # Returns stdlib datetime (no timezone)

Issue: Parse Error with Non-ISO Strings

Problem: ParserError: Unable to parse string

Solution:

import pendulum

# pendulum.parse() only handles ISO 8601 and similar structured formats
# Natural language ("next monday") is NOT supported in v3
try:
    dt = pendulum.parse('2024-03-15 14:30')  # Works
    dt = pendulum.parse('2024-03-15T14:30:00+00:00')  # Works
except pendulum.parsing.exceptions.ParserError:
    # Use explicit format for non-ISO strings
    dt = pendulum.from_format('15/03/2024', 'DD/MM/YYYY')

Issue: Wrong Timezone After Parsing

Problem: Parsed datetime has wrong or no timezone

Solution:

import pendulum

# String without timezone info
date_str = '2024-03-15 14:30:00'

# Method 1: Specify timezone during parse
dt = pendulum.parse(date_str, tz='Europe/London')

# Method 2: Parse then convert
dt = pendulum.parse(date_str)  # Assumes local
dt = dt.in_tz('Europe/London')

# Method 3: Use from_format with tz
dt = pendulum.from_format(date_str, 'YYYY-MM-DD HH:mm:ss',
                          tz='Europe/London')

Issue: Month Arithmetic Edge Cases

Problem: Adding months to month-end dates produces unexpected results

Solution:

import pendulum

# Pendulum handles this intelligently
jan_31 = pendulum.datetime(2024, 1, 31)

# Adding 1 month goes to Feb 29 (leap year)
feb = jan_31.add(months=1)
print(f"Jan 31 + 1 month: {feb.format('D MMM')}")  # 29 Feb

# Non-leap year goes to Feb 28
jan_31_2023 = pendulum.datetime(2023, 1, 31)
feb_2023 = jan_31_2023.add(months=1)
print(f"Jan 31 + 1 month (2023): {feb_2023.format('D MMM')}")  # 28 Feb

# If you need "same day or last day of month" logic
# Pendulum does this automatically

Issue: Iteration Memory Usage

Problem: Large period iteration consumes too much memory

Solution:

import pendulum

start = pendulum.datetime(2000, 1, 1, tz='UTC')
end = pendulum.datetime(2024, 12, 31, tz='UTC')
interval = pendulum.interval(start, end)

# DON'T: Create list of all dates
# all_dates = list(interval.range('days'))  # 9000+ items

# DO: Iterate without storing
for dt in interval.range('days'):
    # Process each date
    if dt.day == 1:  # Example: only first of month
        print(dt.format('MMM YYYY'))

# DO: Use generator expression
first_of_months = (dt for dt in interval.range('months'))

Issue: DST Transition Handling

Problem: Unexpected times during daylight saving transitions

Solution:

import pendulum

# During spring forward (clocks skip 1 hour)
# 01:30 AM doesn't exist on that day
tz = 'Europe/London'

# Pendulum handles this gracefully
dt = pendulum.datetime(2024, 3, 31, 1, 30, tz=tz)
print(f"Spring: {dt}")  # Adjusted automatically

# During fall back (clocks repeat 1 hour)
# 01:30 AM occurs twice
dt = pendulum.datetime(2024, 10, 27, 1, 30, tz=tz)
print(f"Autumn: {dt}")
print(f"DST: {dt.is_dst()}")  # Check which occurrence (method in v3)

# Work in UTC to avoid DST issues
utc_time = pendulum.datetime(2024, 3, 31, 0, 30, tz='UTC')
london_time = utc_time.in_tz('Europe/London')
print(f"Converted: {london_time}")  # Correct handling

Issue: Format Token Confusion

Problem: Wrong output due to incorrect format tokens

Solution:

import pendulum

dt = pendulum.datetime(2024, 3, 15, 14, 30)

# Pendulum uses different tokens than strftime
# WRONG: dt.format('%Y-%m-%d')  # strftime tokens don't work
# RIGHT: dt.format('YYYY-MM-DD')

# Common token differences:
# strftime    pendulum
# %Y          YYYY
# %m          MM
# %d          DD
# %H          HH
# %M          mm
# %S          ss
# %I          hh
# %p          A

# If you need strftime compatibility
formatted = dt.strftime('%Y-%m-%d %H:%M:%S')  # This works too

Issue: Serialisation for JSON/API

Problem: DateTime not serialisable to JSON

Solution:

import pendulum
import json

dt = pendulum.now('UTC')

# Convert to ISO string for JSON
data = {
    'timestamp': dt.to_iso8601_string(),
    'date': dt.to_date_string(),
}
json_str = json.dumps(data)

# Or use custom encoder
class PendulumEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, pendulum.DateTime):
            return obj.to_iso8601_string()
        return super().default(obj)

data = {'created_at': dt}
json_str = json.dumps(data, cls=PendulumEncoder)

# Parse back
parsed_data = json.loads(json_str)
dt_back = pendulum.parse(parsed_data['timestamp'])

Related Topics

  • Python datetime - Standard library datetime module for basic operations
  • dateutil - Extended datetime parsing and timezone handling
  • arrow - Similar human-friendly datetime library
  • pytz - Legacy timezone library (use pendulum's built-in timezones instead)
  • pandas datetime - Efficient datetime operations for data analysis
  • Celery - Task scheduling with timezone-aware timestamps