Datetime in Python: Working with Dates and Times
The datetime rules that prevent real bugs: why naive and aware datetimes cannot be compared, how to attach and convert time zones with zoneinfo, parsing ISO strings and custom formats, formatting for people, and why adding one day is not always 24 hours.
GloryTecks Team
Training & Placement Faculty
Last updated: 24 September 2026
Date bugs are rarely crashes. They are an order timestamped five and a half hours early, a report that double-counts the hour when clocks go back, a "daily" job that runs twice. Nearly all of them come from one question asked too late: which time zone is this? Python's datetime module answers it well, provided you know the difference between two kinds of datetime.
Naive and aware: the distinction everything depends on
A naive datetime has no time zone attached. It is just a calendar date and a wall-clock time, and Python cannot know which instant it means. An aware datetime carries a time zone, so it identifies one precise moment (Python docs: datetime).
from datetime import date, datetime, timedelta, timezone
from zoneinfo import ZoneInfo
IST = ZoneInfo("Asia/Kolkata")
naive = datetime(2026, 3, 1, 9, 30)
aware = datetime(2026, 3, 1, 9, 30, tzinfo=IST)
print(naive.tzinfo, aware.utcoffset())
try:
naive < aware
except TypeError as exc:
print(exc)None 5:30:00
can't compare offset-naive and offset-aware datetimesThat TypeError is Python protecting you: it refuses to guess. The rule that follows is simple. Make datetimes aware as soon as they enter your program, and keep them aware.
Time zones with zoneinfo
Since Python 3.9, the standard library's zoneinfo module provides the IANA time zone database: names like Asia/Kolkata, Europe/London and America/New_York, with their full history of offset and daylight-saving changes (Python docs: zoneinfo). On Windows, which has no system copy of that database, install the tzdata package (python -m pip install tzdata); zoneinfo uses it automatically.
astimezone() converts an aware datetime to another zone. The instant stays the same; only the wall-clock reading changes:
order_placed = datetime(2026, 3, 1, 9, 30, tzinfo=IST)
print(order_placed.astimezone(timezone.utc))
print(order_placed.astimezone(ZoneInfo("America/New_York")))
print(datetime.now(timezone.utc).tzinfo)2026-03-01 04:00:00+00:00
2026-02-28 23:00:00-05:00
UTCNote the date changed in New York: the order was placed on 1 March in India and on 28 February in New York. A daily sales report grouped by naive dates would disagree between the two offices.
For the current time, use datetime.now(timezone.utc) or datetime.now(IST). The older datetime.utcnow() returns a naive value and is deprecated from Python 3.12.
The storage rule: store and compute in UTC, convert to a local zone only for display. India Standard Time has no daylight saving, so IST is always UTC+05:30, but your users, servers and data sources may not all be in India.
Parsing strings into datetimes
Input usually arrives as text. Two tools cover almost everything:
print(datetime.fromisoformat("2026-03-01T09:30:00+05:30"))
print(datetime.fromisoformat("2026-03-01T04:00:00Z"))
legacy = datetime.strptime("01/03/2026 09:30", "%d/%m/%Y %H:%M")
print(legacy, legacy.tzinfo)
print(legacy.replace(tzinfo=IST).isoformat())2026-03-01 09:30:00+05:30
2026-03-01 04:00:00+00:00
2026-03-01 09:30:00 None
2026-03-01T09:30:00+05:30fromisoformat()reads ISO 8601 strings, the format APIs and databases usually produce. Since Python 3.11 it accepts most ISO forms, including a trailingZfor UTC; earlier versions were much stricter.strptime()reads any other layout from a format string. The result is naive unless the string itself contains an offset (%z), so attach the zone you know the data was recorded in withreplace(tzinfo=...).
The trap in the middle example is 01/03/2026: is that 1 March or 3 January? strptime does exactly what the format says, so the format must come from knowing the source, not from guessing.
Formatting for people
strftime() goes the other way, and isoformat() produces the machine-readable form:
print(order_placed.strftime("%d %b %Y, %I:%M %p"))
print(order_placed.strftime("%A"), order_placed.isoformat())01 Mar 2026, 09:30 AM
Sunday 2026-03-01T09:30:00+05:30Use isoformat() whenever the value is going into a file, a JSON payload or an API; see working with JSON in Python for serialising datetimes. Month and day names from %b and %A follow the current locale.
Arithmetic: when one day is not 24 hours
A timedelta is a duration. Subtracting dates gives one, and adding one moves a date:
print(date(2026, 12, 31) - date(2026, 9, 25))
print(date(2026, 1, 31) + timedelta(days=30))97 days, 0:00:00
2026-03-02There is no "add one month" in timedelta, because months differ in length; the second line shows the effect of adding 30 days instead. For calendar months, compute the target year and month yourself, or use a library built for it.
The subtle case is daylight saving. For aware datetimes in the same zone, Python does wall-clock arithmetic (PEP 495): adding one day keeps the same clock time on the next day, even when that day is shorter. In New York, clocks jumped forward on 8 March 2026:
NY = ZoneInfo("America/New_York")
before = datetime(2026, 3, 7, 12, 0, tzinfo=NY)
after = before + timedelta(days=1)
print(after)
print(after.astimezone(timezone.utc) - before.astimezone(timezone.utc))2026-03-08 12:00:00-04:00
23:00:00Noon to noon, but only 23 hours actually passed. If you are measuring elapsed time, such as a job's runtime or a billing period in hours, convert both ends to UTC first, as in the last line. If you are scheduling something for "the same time tomorrow", wall-clock arithmetic is what you want. Clocks going back create the opposite problem: one wall-clock hour happens twice, and the fold attribute says which of the two a datetime means.
A short checklist
- Attach a zone at the edge of your program: when parsing input, reading a database value or calling
now(). - Store and compare in UTC; convert to local time only for display.
- Use
fromisoformat()for ISO strings, and an explicitstrptime()format for everything else. - Write timestamps out with
isoformat(), so the offset travels with the value. - Measure durations in UTC; schedule by the local clock.
How do I get a Unix timestamp, and turn one back into a datetime?
How do I compare only the dates and ignore the time?
Should I use pytz?
In tables of data, pandas applies the same ideas at scale with to_datetime, tz_localize and tz_convert; see the pandas guide. Timestamps inside CSV files are covered in reading and writing CSV files in Python. The standard library modules used here, datetime and zoneinfo among them, are part of the modules and packages section of the Python Programming course.
Sources
- datetime β Basic date and time types β Python documentation
- zoneinfo β IANA time zone support β Python documentation
- PEP 495 β Local Time Disambiguation β Python Enhancement Proposals
GloryTecks Team
Training & Placement Faculty
The GloryTecks faculty team mentors learners across Data Science, AI and Data Engineering, with a focus on real-time projects and placement readiness in Hyderabad.
Want to master Python?
Join GloryTecksβ expert-led Python Programming training in Hyderabad with real-time projects and 100% placement support.
Related articles
Learn Python from Scratch: A Complete 2026 Roadmap
How to learn Python from zero: install a current version (including the new Windows install manager), then work through seven stages, from values and loops to files, virtual environments, classes and tests, each ending with a small program you should be able to write on your own.
Python Interview Questions: Top 50 with Answers
Fifty conceptual Python interview questions with short, precise answers, grouped by topic: basics, types and mutability, data structures, functions and scope, OOP, iterators and decorators, errors and files, and internals, with runnable examples for the ones people most often get wrong.
Python for Data Science: The Essential Libraries
The Python data science stack explained by the job each library does: NumPy for arrays, pandas for tables, Matplotlib and seaborn for charts, SciPy and statsmodels for statistics, scikit-learn for models and Jupyter for exploration, with one runnable example that passes a real dataset through them.