Parse and Format Timestamps with datetime
Validate a fixed UTC timestamp format before producing an ISO-style output string.
Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons
Video lesson: Parse and Format Timestamps with datetime
The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube
Jump to a chapter
Read the video transcript
Select a timestamp to watch that moment on YouTube.
- 00:00
Incoming timestamps are strings, but arithmetic and validation belong on datetime objects. datetime.strptime parses text using a format pattern; strftime formats a datetime back into text. The two directions are easy to confuse, so a visible round trip helps. The first example turns 2026-10-26 09:05 into a datetime and prints it with a T separator and seconds. strptime reads text according to a pattern; strftime writes a text representation. The similar names point in opposite directions, so trace the value type between the two calls.
- 00:41
Reading a timestamp and displaying a timestamp are opposite operations with similar names. We will make the direction visible and define the source time zone so the output Z is honest. Predict whether strptime with percent-m and percent-d necessarily rejects the nonpadded value 2026-1-2 9:5. It can parse such fields, so our stricter external shape rule needs its own check. The final Z must also reflect the stated UTC source. The sample input is a contractually UTC string shaped exactly YYYY-MM-DD HH:MM. strptime accepts some non-zero-padded fields even with %m and %d, so a regular-
- 01:24
expression shape check is needed if exact width matters. It also rejects impossible calendar dates such as February 30. A pattern can specify semantic fields without enforcing every cosmetic rule. If the external format insists on two- digit month, day, hour, and minute, check its exact shape before parsing. The checked task uses a regular expression for width and punctuation, then lets strptime validate the calendar values. The starter produces the happy-path string, but accepts some non-padded inputs even though our external contract requires exact widths.
- 02:05
A value can parse semantically while still breaking the promised format. The starter prints a well-formed result for one padded value, yet that happy path does not test strict width. Try the nonpadded counterexample mentally before the next run. A parser accepting a date does not prove it followed the exact wire format. After parsing, replace(tzinfo=timezone.utc) is safe only because this input contract already says the clock reading is UTC. It does not convert a local time into UTC. If the source time were in another zone, attach that zone and call astimezone(timezone.utc) instead.
- 02:47
The second example passes a date with no calendar meaning. strptime raises ValueError, which is the right result for this input contract. A regular expression alone would accept the string because the digits and punctuation look correct; syntax shape and actual date validity are two different checks. Keep both layers narrow. The regex should only enforce the exact YYYY-MM-DD HH:MM shape, not attempt to encode every leap-year rule. The datetime parser can reject impossible dates and out-of-range clock fields. The caller can then show a useful invalid- input message.
- 03:27
The diagnostic run separates shape from calendar validity. A regex can enforce punctuation and width; strptime rejects impossible dates. Neither layer should pretend to do the other's job. The diagnostic invalid date shows the other half of validation: strptime rejects February 30. A regex can enforce four-two-two and two- two digit widths, but it cannot by itself prove the calendar date exists. Both checks have a distinct job. Implement format_utc(text) for exact YYYY- MM-DD HH:MM UTC input. Validate the exact shape, parse with datetime.strptime, attach timezone.utc,
- 04:09
and return YYYY-MM-DDTHH:MM:SSZ. Raise ValueError for wrong width or impossible dates. Use re.fullmatch(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}', text), then datetime.strptime and replace(tzinfo=timezone.utc). The final example constructs an aware UTC datetime and prints a zero offset and Z-suffixed result. Z is a promise about the time zone, not just a decorative character. The task may attach timezone.utc because its input contract already says the clock reading is UTC.
- 04:51
The fixed function may attach timezone.utc only because its input contract explicitly says the clock reading is UTC. If the source were local time, replace would mislabel it; conversion would require the true source zone. The fixed function first checks exact width and punctuation, then parses the actual date and time. It attaches timezone.utc only because the input contract says the source clock is already UTC. Attaching UTC to a local clock would relabel rather than convert the instant. The checked function returns a fixed ISO- style string with seconds and Z.
- 05:32
It rejects wrong shapes and impossible dates with ValueError. In production, prefer actual ISO 8601 inputs when available, document offsets explicitly, and avoid inventing a time zone for naive data. If a caller supplies a local clock reading instead, replace(tzinfo=timezone.utc) would mislabel its instant. Attach the actual source zone and use astimezone for conversion. After validating and attaching UTC, format a stable string with seconds. Check a leap-day input and reject a non- padded input to cover both semantics and shape.
- 06:12
Keep a strict input contract separate from semantic date validation. This lesson's replace is valid only because the input was defined as UTC. The final run accepts a leap-day time and rejects a non-padded input. That verifies both semantic validity and strict shape. Keep these tests close to the parser whenever an external feed changes its format. If you later accept offsets such as plus two hours, parse the offset into an aware datetime and convert to UTC before emitting Z. Relabeling the wall clock would silently change the instant. The final run accepts a padded leap-day time and rejects a nonpadded one.
- 06:55
The Z suffix is a semantic promise of UTC; for a source with an offset, parse that offset and convert with astimezone before formatting. Check both the input shape and the resulting timezone-aware value when adding a new feed, since correct punctuation alone cannot establish the source zone.
Understand the concept
Incoming timestamps are strings, but arithmetic and validation belong on datetime objects. datetime.strptime parses text using a format pattern; strftime formats a datetime back into text. The two directions are easy to confuse, so a visible round trip helps.
The sample input is a contractually UTC string shaped exactly YYYY-MM-DD HH:MM. strptime accepts some non-zero-padded fields even with %m and %d, so a regular-expression shape check is needed if exact width matters. It also rejects impossible calendar dates such as February 30.
After parsing, replace(tzinfo=timezone.utc) is safe only because this input contract already says the clock reading is UTC. It does not convert a local time into UTC. If the source time were in another zone, attach that zone and call astimezone(timezone.utc) instead.
The checked function returns a fixed ISO-style string with seconds and Z. It rejects wrong shapes and impossible dates with ValueError. In production, prefer actual ISO 8601 inputs when available, document offsets explicitly, and avoid inventing a time zone for naive data.
- strptime parses; strftime formats
- Pattern parsing may accept flexible widths
- UTC attachment is not conversion
- Reject impossible dates and malformed shapes
See it step by step
Read the code, predict the output, then compare it with the result.
01. Parse then format
from datetime import datetime
dt=datetime.strptime('2026-10-26 09:05','%Y-%m-%d %H:%M')
print(dt.strftime('%Y-%m-%dT%H:%M:%S'))2026-10-26T09:05:00Parsing makes a datetime; formatting chooses its display.
02. Reject an impossible date
from datetime import datetime
try:
datetime.strptime('2026-02-30','%Y-%m-%d')
except ValueError:
print('invalid date')invalid dateThe parser checks calendar validity.
03. Show explicit UTC awareness
from datetime import datetime, timezone
dt=datetime(2026,10,26,9,5,tzinfo=timezone.utc)
print(dt.utcoffset())
print(dt.strftime('%Y-%m-%dT%H:%M:%SZ'))0:00:00
2026-10-26T09:05:00ZThe Z suffix matches the explicit UTC offset.
A closer look
Follow the reasoning, inspect each result, then try the suggested changes in the console below.
Parse one string and format it differently
The first example turns 2026-10-26 09:05 into a datetime and prints it with a T separator and seconds. strptime reads text according to a pattern; strftime writes a text representation. The similar names point in opposite directions, so trace the value type between the two calls.
A pattern can specify semantic fields without enforcing every cosmetic rule. If the external format insists on two-digit month, day, hour, and minute, check its exact shape before parsing. The checked task uses a regular expression for width and punctuation, then lets strptime validate the calendar values.
from datetime import datetime
dt=datetime.strptime('2026-10-26 09:05','%Y-%m-%d %H:%M')
print(dt.strftime('%Y-%m-%dT%H:%M:%S'))2026-10-26T09:05:00- Read with strptime.
- Inspect the resulting datetime.
- Write with strftime using an explicit output pattern.
Let calendar validation reject February 30
The second example passes a date with no calendar meaning. strptime raises ValueError, which is the right result for this input contract. A regular expression alone would accept the string because the digits and punctuation look correct; syntax shape and actual date validity are two different checks.
Keep both layers narrow. The regex should only enforce the exact YYYY-MM-DD HH:MM shape, not attempt to encode every leap-year rule. The datetime parser can reject impossible dates and out-of-range clock fields. The caller can then show a useful invalid-input message.
from datetime import datetime
try:
datetime.strptime('2026-02-30','%Y-%m-%d')
except ValueError:
print('invalid date')invalid date- Match the exact textual shape.
- Parse into a datetime.
- Reject a well-shaped but impossible date.
State the UTC assumption explicitly
The final example constructs an aware UTC datetime and prints a zero offset and Z-suffixed result. Z is a promise about the time zone, not just a decorative character. The task may attach timezone.utc because its input contract already says the clock reading is UTC.
If a caller supplies a local clock reading instead, replace(tzinfo=timezone.utc) would mislabel its instant. Attach the actual source zone and use astimezone for conversion. After validating and attaching UTC, format a stable string with seconds. Check a leap-day input and reject a non-padded input to cover both semantics and shape.
from datetime import datetime, timezone
dt=datetime(2026,10,26,9,5,tzinfo=timezone.utc)
print(dt.utcoffset())
print(dt.strftime('%Y-%m-%dT%H:%M:%SZ'))0:00:00
2026-10-26T09:05:00Z- Attach UTC only to an already-UTC reading.
- Format the aware value with seconds and Z.
- Keep conversion from another zone as a separate operation.
Try it in Python
Edit the example and run it. Python starts in your browser the first time you click Run.
Python console
Ready to runNeed input()? Add one value per line
Your output appears here.
Need a hint?
Use re.fullmatch(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}', text), then datetime.strptime and replace(tzinfo=timezone.utc).
Complete the task and select Check task to verify your code.
Quick quiz
Three questions. You can change your answers and try again.
Typical mistakes
Everyone meets these errors. See what causes them and how to fix them.
Assuming parsing enforces exact width
datetime.strptime('2026-1-02 09:05', pattern)Validate shape with re.fullmatch firstWhat happens: A non-padded month may be accepted.
Keep a strict input contract separate from semantic date validation.
Labelling local time as UTC
local.replace(tzinfo=timezone.utc)Attach the true source zone, then astimezone(timezone.utc)What happens: The instant becomes misrepresented.
This lesson's replace is valid only because the input was defined as UTC.