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.
flowchart LR
subgraph pendulum_objects["Pendulum Objects"]
A[DateTime]
B[Date]
C[Time]
D[Duration]
E[Interval]
end
subgraph operations["Common Operations"]
F[Create/Parse]
G[Timezone Convert]
H[Add/Subtract]
I[Format]
J[Diff/Compare]
end
A --> F
A --> G
A --> H
A --> I
A --> J
D --> H
E --> J
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
flowchart TD
A[String] -->|parse| B[DateTime]
C[Components] -->|create| B
D[Timestamp] -->|from_timestamp| B
E[datetime] -->|instance| B
F[Now] -->|now/today| B
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
flowchart TD
A[DateTime UTC] -->|in_timezone| B[DateTime London]
A -->|in_tz| C[DateTime Tokyo]
A -->|in_tz| D[DateTime New York]
B --> E[Same instant, different representation]
C --> E
D --> E
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
flowchart LR
A[DateTime] -->|add| B[Future DateTime]
A -->|subtract| C[Past DateTime]
A -->|diff| D[Duration/Interval]
subgraph units["Time Units"]
E[years]
F[months]
G[weeks]
H[days]
I[hours]
J[minutes]
K[seconds]
end
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
Periodin v2) - Iteration - Intervals can be iterated over by various intervals
- Arithmetic - Durations can be added/subtracted from DateTimes
flowchart TD
A[DateTime 1] -->|diff| B[Interval]
A --> C[DateTime 2]
C --> B
B -->|iterate| D[List of DateTimes]
E[Duration] -->|add to| A
E -->|components| F[years, 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