datetime.strftime / strptime

One directive table, two directions: strftime ("format") turns a date into text, strptime ("parse") turns text into a datetime. The C89 directives are portable; flags like %-d depend on your platform.

datetime methodPython 2.3+Live demo
Common call
dt.strftime('%Y-%m-%d %H:%M') · datetime.strptime(s, '%d/%m/%Y')
Returns
str · datetime
Replaces
Hand-built f-strings with zero-padding; regex date parsing
Watch out
%m is month, %M is minute; strptime must match the WHOLE string
dt.strftime(formatformat — Text with %-directives (table below). Everything else is copied literally; %% is a literal percent sign.type: str · required)
→ str

Demo

Live evaluation
Pick a moment (ISO text) and a format string. Directives are replaced, everything else is copied.
Try:
Inputs
whenstran ISO 8601 timestamp
fmtstra strftime format
Code
from datetime import datetime
datetime.fromisoformat('2026-09-05T14:05:09').strftime('%Y-%m-%d %H:%M:%S')
Result
'2026-09-05 14:05:09'

strftime directives this page lists as portable give the same text on every platform. For the rest the demo follows Linux (glibc): there %-d gives "5", while Windows raises ValueError: Invalid format string (Windows spells it %#d); %s is refused because it depends on the local timezone. In the round trip, fields missing from the format come back as defaults — 1900-01-01 for the date, 00:00 for the time.

Parameters

NameTypeRequiredDescription
formatstryesText with %-directives (table below). Everything else is copied literally; %% is a literal percent sign.

Return value

str — strftime: the formatted text. (strptime returns a datetime — naive, or aware when the format has %z.)

Attributes

AttributeTypeMeaning
%a / %A'Sat' / 'Saturday'Weekday name, abbreviated / full (locale names; English unless locale.setlocale changed LC_TIME).
%w'6'Weekday number, 0 = Sunday … 6 = Saturday.
%d'05'Day of the month, zero-padded.
%b / %B'Sep' / 'September'Month name, abbreviated / full (locale).
%m'09'Month number, zero-padded.
%y / %Y'26' / '2026'Year without / with century. strptime maps %y 00–68 to 2000–2068 and 69–99 to 1969–1999.
%H / %I'14' / '02'Hour, 24-hour / 12-hour clock, zero-padded.
%p'PM'AM/PM (locale). With strptime it only changes the hour when %I is used, not %H.
%M'05'Minute, zero-padded.
%S'09'Second, zero-padded.
%f'250000'Microsecond, 6 digits. strptime accepts 1 to 6 digits. Handled by Python itself, so it works everywhere.
%z'+0200'UTC offset; empty for naive objects. strptime also accepts Z, +02:00 and seconds, and returns an aware datetime.
%:z'+02:00'UTC offset with colons (Python 3.12+, strftime only).
%Z'UTC+02:00'tzinfo.tzname(); empty for naive. strptime only accepts UTC, GMT and the local zone names in time.tzname — and the result stays naive.
%j'248'Day of the year, 001–366.
%U / %W'35' / '35'Week of the year, weeks starting Sunday / Monday; days before the first one are week 00.
%G / %V / %u'2026' / '36' / '6'ISO 8601 year, week (01–53) and weekday (1 = Monday). In strptime %G and %V need each other plus a weekday directive.
%c / %x / %X'Sat Sep 5 14:05:09 2026' / '09/05/26' / '14:05:09'Locale date+time / date / time. Shown for the C locale; the layout changes with the locale — avoid in data formats.
%%'%'A literal percent sign.
%e %F %T %D %R %C %g' 5' '2026-09-05' '14:05:09' …Accepted by strftime on Linux (glibc) and on Windows (UCRT); not in the C89 set Python documents. strptime rejects them.
%-d %-m %-I'5' '9' '2'No zero padding — glibc only. Windows raises ValueError: Invalid format string; its own flag is %#d. strptime rejects both.
%sseconds since epochglibc only, computed as LOCAL time, ignoring tzinfo. Use dt.timestamp() instead.

Common patterns

f-string formatting
datetime objects implement __format__, so a format spec is a strftime format.
from datetime import datetime
label = f"Saved {datetime(2026, 9, 29, 14, 5):%d %b %Y at %H:%M}"
Try several input formats
strptime raises ValueError on a mismatch — loop until one fits.
from datetime import datetime

def parse_any(text, formats=('%Y-%m-%d', '%d/%m/%Y', '%d %b %Y')):
    for fmt in formats:
        try:
            return datetime.strptime(text, fmt)
        except ValueError:
            pass
    raise ValueError(f'unrecognised date: {text!r}')
Parse to a date, not a datetime
date has no strptime; parse a datetime and take .date().
from datetime import datetime
day = datetime.strptime('29.09.2026', '%d.%m.%Y').date()
Day without a leading zero, portably
%-d is glibc-only and %#d Windows-only; build that field yourself.
label = f"{dt.day} {dt:%B %Y}"  # '5 September 2026' on every platform

Examples

1. Sortable timestamp
from datetime import datetime datetime(2026, 9, 29, 14, 5, 9).strftime('%Y-%m-%d %H:%M:%S')
Returns
'2026-09-29 14:05:09'
2. Same thing in an f-string
from datetime import datetime dt = datetime(2026, 9, 29, 14, 5) f'{dt:%Y-%m-%d %H:%M}'
Returns
'2026-09-29 14:05'
3. Microseconds with %f
from datetime import datetime datetime(2026, 9, 29, 14, 5, 9, 250000).strftime('%H:%M:%S.%f')
Returns
'14:05:09.250000'
4. Offsets need an aware value
from datetime import datetime, timedelta, timezone datetime(2026, 9, 29, tzinfo=timezone(timedelta(hours=-4))).strftime('%z %:z %Z')
Returns
'-0400 -04:00 UTC-04:00'
5. A date has midnight and no zone
from datetime import date date(2026, 9, 29).strftime('%Y-%m-%d %H:%M:%S %z')
Returns
'2026-09-29 00:00:00 '
6. Parse with %z → aware
from datetime import datetime datetime.strptime('2026-09-29 14:30:00+05:30', '%Y-%m-%d %H:%M:%S%z')
Returns
datetime.datetime(2026, 9, 29, 14, 30, tzinfo=datetime.timezone(datetime.timedelta(seconds=19800)))
7. Two-digit years pivot at 69
from datetime import datetime (datetime.strptime('68', '%y').year, datetime.strptime('69', '%y').year)
Returns
(2068, 1969)
8. ISO week date
from datetime import datetime datetime.strptime('2026-W40-2', '%G-W%V-%u')
Returns
datetime.datetime(2026, 9, 29, 0, 0)

Pitfalls

1. %M is minutes, %m is months
The case matters and both are valid, so the mistake produces a plausible-looking wrong date instead of an error.
%M
from datetime import datetime
datetime(2026, 9, 29, 14, 5).strftime('%Y-%M-%d')
'2026-05-29'
%m
from datetime import datetime
datetime(2026, 9, 29, 14, 5).strftime('%Y-%m-%d')
'2026-09-29'
2. Day-first text with a month-first format
strptime does not guess. 29 cannot be a month, so this fails loudly — but 05/09 would silently parse as 9 May.
%m/%d/%Y
from datetime import datetime
datetime.strptime('29/09/2026', '%m/%d/%Y')
ValueError: time data '29/09/2026' does not match format '%m/%d/%Y'
%d/%m/%Y
from datetime import datetime
datetime.strptime('29/09/2026', '%d/%m/%Y')
datetime.datetime(2026, 9, 29, 0, 0)
3. Parsing a day and month without a year
The missing year defaults to 1900, which is not a leap year, so 29 February cannot be built. Python 3.13 also emits a DeprecationWarning for any day-of-month format without a year.
no year
from datetime import datetime
datetime.strptime('29 Feb', '%d %b')
ValueError: day is out of range for month
add the year
from datetime import datetime
datetime.strptime('29 Feb 2028', '%d %b %Y')
datetime.datetime(2028, 2, 29, 0, 0)
4. Expecting %Z to make the result aware
strptime matches a zone NAME only against UTC, GMT and the local names, and still returns a naive datetime. Offsets (%z) are what produce tzinfo.
%Z
from datetime import datetime
datetime.strptime('2026-09-29 14:30 UTC', '%Y-%m-%d %H:%M %Z').tzinfo is None
True
%z
from datetime import datetime
datetime.strptime('2026-09-29 14:30 +0000', '%Y-%m-%d %H:%M %z').tzinfo
datetime.timezone.utc

When to use

Use it
  • Human-readable output: reports, UI labels, file names
  • Parsing a known, fixed input layout (CSV exports, log lines)
Reach for something else
  • ISO 8601 text → isoformat() / fromisoformat(): faster, exact, offset-aware
  • Locale-sensitive display for many languages → babel or the platform formatter
  • Guessing unknown formats → dateutil.parser (third-party)

Notes

CPython impl
strftime: Modules/_datetimemodule.c expands %z, %:z, %Z and %f itself, then calls time.strftime (the C library). strptime: pure Python in Lib/_strptime.py, a regex built from the format
Portability
The C89 directives in the table behave the same everywhere; anything else is whatever the platform C library does (glibc, BSD/macOS, Windows UCRT differ)
Case
strptime matches names case-insensitively: SEP, sep and Sep all parse with %b
Errors
strptime raises ValueError: "time data ... does not match format ...", "unconverted data remains: ...", "'Q' is a bad directive in format ...", or the date validation message

FAQ

strftime = "string format time": an object method that turns a date, time or datetime into text. strptime = "string parse time": a datetime class method that turns text into a datetime. Both use the same %-directives, so the format that wrote a string can read it back.