Skip to content

Commit cf7f72b

Browse files
dcamronkafitzgerald
authored andcommitted
Update timezone content (ProjectPythia#620)
Manually construct a timezone of known offset, and introduce zoneinfo to replace pytz intro. Minor cleanups.
1 parent e09e5a8 commit cf7f72b

File tree

1 file changed

+58
-38
lines changed

1 file changed

+58
-38
lines changed

core/datetime/datetime.ipynb

Lines changed: 58 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"1. Introduce the [time](https://docs.python.org/3/library/time.html) and [datetime](https://docs.python.org/3/library/datetime.html) modules from the Python Standard Library\n",
3535
"1. Look at formatted input and output of dates and times\n",
3636
"1. See how we can do simple arithmetic on date and time data, by making use of the `timedelta` object\n",
37-
"1. Briefly make use of the [pytz](https://pypi.org/project/pytz/) module to handle some thorny time zone issues in Python."
37+
"1. Briefly make use of the [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) module to handle time zone awareness and conversions in Python."
3838
]
3939
},
4040
{
@@ -64,7 +64,7 @@
6464
"source": [
6565
"## Imports\n",
6666
"\n",
67-
"For the examples on this page, we import three modules from the Python Standard Library, as well as one third-party module. The import syntax used here, as well as a discussion on this syntax and an overview of these modules, can be found in the next section."
67+
"For the examples on this page, we import three modules from the Python Standard Library. The import syntax used here, as well as a discussion on this syntax and an overview of these modules, can be found in the next section."
6868
]
6969
},
7070
{
@@ -76,11 +76,8 @@
7676
"# Python Standard Library packages\n",
7777
"# We'll discuss below WHY we alias the packages this way\n",
7878
"import datetime as dt\n",
79-
"import math\n",
8079
"import time as tm\n",
81-
"\n",
82-
"# Third-party package for time zone handling, we'll discuss below!\n",
83-
"import pytz"
80+
"from zoneinfo import ZoneInfo"
8481
]
8582
},
8683
{
@@ -203,7 +200,7 @@
203200
"\n",
204201
"- performing date and time arithmetic and calculating time duration\n",
205202
"- reading and writing date and time strings with various formats\n",
206-
"- handling time zones (with the help of third-party libraries)\n",
203+
"- handling time zones (with the help of the Standard Library's [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) module)\n",
207204
"\n",
208205
"The `time` and `datetime` modules overlap in functionality, but in your geoscientific work, you will probably be using the `datetime` module more than the `time` module."
209206
]
@@ -429,20 +426,29 @@
429426
"source": [
430427
"Notice that `aware` has `+00:00` appended at the end, indicating zero hours offset from UTC.\n",
431428
"\n",
432-
"Our `naive` object shows the local time on whatever computer was used to run this code. If you're reading this online, chances are the code was executed on a cloud server that already uses UTC. If this is the case, `naive` and `aware` will differ only at the microsecond level, due to round-off error.\n",
433-
"\n",
434-
"In the code above, we used `dt.timezone.utc` to initialize the UTC timezone for our `aware` object. Unfortunately, at this time, the Python Standard Library does not fully support initializing datetime objects with arbitrary time zones; it also does not fully support conversions between time zones for datetime objects. However, there exist third-party libraries that provide some of this functionality; one such library is covered below."
429+
"Our `naive` object shows the local time on whatever computer was used to run this code. If you're reading this online, chances are the code was executed on a cloud server that already uses UTC. If this is the case, `naive` and `aware` will differ only at the microsecond level, due to round-off error."
435430
]
436431
},
437432
{
438433
"cell_type": "markdown",
439434
"metadata": {},
440435
"source": [
441-
"### Full time zone support with the `pytz` module\n",
442-
"\n",
443-
"For improved handling of time zones in Python, you will need the third-party [pytz](https://pypi.org/project/pytz/) module, whose classes build upon, or, in object-oriented programming terms, inherit from, classes from the `datetime` module.\n",
436+
"````{note}\n",
437+
"`datetime` provides an alias to access `datetime.timezone.UTC` that may simplify the above code for you:\n",
438+
"```python\n",
439+
"from datetime import datetime, UTC\n",
440+
"aware = datetime.now(UTC)\n",
441+
"```\n",
442+
"````"
443+
]
444+
},
445+
{
446+
"cell_type": "markdown",
447+
"metadata": {},
448+
"source": [
449+
"### Fixed UTC Offsets with `datetime.timezone`\n",
444450
"\n",
445-
"In this next example, we repeat the above exercise, but this time, we use a method from the `pytz` module to initialize the `aware` object in a different time zone:"
451+
"When you know the exact UTC offset for a time zone and do not need Daylight Saving Time handling, you can construct a time zone directly from the `datetime` module using `datetime.timezone` and a `datetime.timedelta`. This approach requires no external data — the offset is purely arithmetic. For example, let's construct US Mountain Daylight Time (MDT), which is defined as 6 hours behind UTC (UTC-6):"
446452
]
447453
},
448454
{
@@ -452,22 +458,27 @@
452458
"outputs": [],
453459
"source": [
454460
"naive = dt.datetime.now()\n",
455-
"aware = dt.datetime.now(pytz.timezone('US/Mountain'))\n",
456-
"print(f\"I am time zone naive: {naive}.\")\n",
457-
"print(f\"I am time zone aware: {aware}.\")"
461+
"TZ_MDT = dt.timezone(offset=dt.timedelta(hours=-6), name='MDT')\n",
462+
"aware = dt.datetime.now(tz=TZ_MDT)\n",
463+
"print(f\"I am time zone naive: {naive}. My time zone is {naive.tzname()}.\")\n",
464+
"print(f\"I am time zone aware: {aware}. My time zone is {aware.tzname()}.\")"
458465
]
459466
},
460467
{
461468
"cell_type": "markdown",
462469
"metadata": {},
463470
"source": [
464-
"The `pytz.timezone()` method takes a time zone string; if this string is formatted correctly, the method returns a `tzinfo` object, which can be used when making a datetime object time zone aware. This initializes the time zone for the newly aware object to a specific time zone matching the time zone string. The `-06:00` indicates that we are now operating in a time zone six hours behind UTC.\n",
471+
"### Handling Time Zones with `zoneinfo`\n",
465472
"\n",
466-
"### Print Time with a Different Time Zone\n",
473+
"Python 3.9 introduced the [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) module to the Standard Library, providing access to the [IANA time zone database](https://www.iana.org/time-zones). This allows you to create time zone aware datetime objects using named time zones such as `'US/Mountain'` or `'America/New_York'`, without any third-party packages.\n",
467474
"\n",
468-
"If you have data that are in UTC, and wish to convert them to another time zone (in this example, US Mountain Time Zone), you will again need to make use of the `pytz` module.\n",
475+
"A key advantage of IANA time zones over a fixed UTC offset is that they automatically account for Daylight Saving Time (DST). For example, `ZoneInfo('US/Mountain')` will report `MST` (UTC-7) in winter and `MDT` (UTC-6) in summer.\n",
469476
"\n",
470-
"First, we will create a new datetime object with the [utcnow()](https://docs.python.org/3/library/datetime.html#datetime.datetime.utcnow) method. Despite the name of this method, the newly created object is time zone naive. Therefore, we must invoke the object's [replace()](https://docs.python.org/3/library/datetime.html#datetime.datetime.replace) method and specify UTC with a `tzinfo` object in order to make the object time zone aware. As described above, we can use the `pytz` module's timezone() method to create a new `tzinfo` object, again using the time zone string 'US/Mountain' (US Mountain Time Zone). To convert the datetime object `utc` from UTC to Mountain Time, we can then run the [astimezone()](https://docs.python.org/3/library/datetime.html#datetime.datetime.astimezone) method."
477+
":::{warning}\n",
478+
"`zoneinfo` reads time zone data from the operating system's timezone database. On most Linux and macOS systems this is available automatically. On Windows, or in minimal environments such as some Docker containers, this database may be absent. In those cases, install the [`tzdata`](https://pypi.org/project/tzdata/) package (`pip install tzdata`), which provides the IANA database as a Python package and is used by `zoneinfo` as a fallback.\n",
479+
":::\n",
480+
"\n",
481+
"In the next example, we create a time zone aware datetime using `ZoneInfo` and inspect the time zone name with `tzname()`:"
471482
]
472483
},
473484
{
@@ -476,25 +487,41 @@
476487
"metadata": {},
477488
"outputs": [],
478489
"source": [
479-
"utc = dt.datetime.utcnow().replace(tzinfo=pytz.utc)\n",
480-
"print(\"The UTC time is {}.\".format(utc.strftime('%B %d, %Y, %-I:%M%p')))\n",
481-
"mountaintz = pytz.timezone(\"US/Mountain\")\n",
482-
"ny = utc.astimezone(mountaintz)\n",
483-
"print(\"The 'US/Mountain' time is {}.\".format(ny.strftime('%B %d, %Y, %-I:%M%p')))"
490+
"naive = dt.datetime.now()\n",
491+
"aware = dt.datetime.now(ZoneInfo('US/Mountain'))\n",
492+
"print(f\"I am time zone naive: {naive}. My time zone is {naive.tzname()}.\")\n",
493+
"print(f\"I am time zone aware: {aware}. My time zone is {aware.tzname()}.\")"
484494
]
485495
},
486496
{
487497
"cell_type": "markdown",
488498
"metadata": {},
489499
"source": [
490-
"In the above example, we also use the `strftime()` method to format the date and time string in a human-friendly format."
500+
"The `ZoneInfo` object passed to `datetime.now()` tells Python which IANA time zone to use. The time zone name returned by `tzname()` reflects whether DST is in effect — `MST` in winter, `MDT` in summer.\n",
501+
"\n",
502+
"### Convert Between Time Zones\n",
503+
"\n",
504+
"If you have data in UTC and wish to convert them to another time zone (in this example, US Mountain Time), you can call the [astimezone()](https://docs.python.org/3/library/datetime.html#datetime.datetime.astimezone) method with a `ZoneInfo` object. Start from a UTC-aware datetime (using `dt.timezone.utc`), then let `astimezone()` handle the conversion — including any DST offset automatically:"
505+
]
506+
},
507+
{
508+
"cell_type": "code",
509+
"execution_count": null,
510+
"metadata": {},
511+
"outputs": [],
512+
"source": [
513+
"utc = dt.datetime.now(dt.timezone.utc)\n",
514+
"print(f'The UTC time is {utc.strftime('%B %d, %Y, %-I:%M%p')}.')\n",
515+
"TZ_USMT = ZoneInfo('US/Mountain')\n",
516+
"us_mt = utc.astimezone(TZ_USMT)\n",
517+
"print(f'The {TZ_USMT} time is {us_mt.strftime('%B %d, %Y, %-I:%M%p')}.')"
491518
]
492519
},
493520
{
494521
"cell_type": "markdown",
495522
"metadata": {},
496523
"source": [
497-
"---"
524+
"In the above example, we also use the `strftime()` method to format the date and time string in a human-friendly format."
498525
]
499526
},
500527
{
@@ -507,7 +534,7 @@
507534
"\n",
508535
"The `datetime` module contains various classes for storing, converting, comparing, and formatting date and time data on the Gregorian calendar. We saw how we can parse data files with date and time strings into `dt.datetime` objects using the `dt.datetime.strptime()` method. We also saw how to perform arithmetic using date and time data; this uses the `dt.timedelta` class to represent intervals of time.\n",
509536
"\n",
510-
"Finally, we looked at using the third-party [pytz](https://pypi.org/project/pytz/) module to handle time zone awareness and conversions.\n",
537+
"Finally, we looked at using the [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) module (Python Standard Library, version 3.9+) to handle time zone awareness and conversions using IANA time zone names, with automatic Daylight Saving Time support.\n",
511538
"\n",
512539
"### What's Next?\n",
513540
"\n",
@@ -520,24 +547,17 @@
520547
"source": [
521548
"## Resources and References\n",
522549
"\n",
523-
"This page was based on and adapted from material in [Unidata's Python Training](https://unidata.github.io/python-training/python/times_and_dates/).\n",
550+
"This page was based on and adapted from material designed at NSF Unidata.\n",
524551
"\n",
525552
"For further reading on these modules, take a look at the official documentation for:\n",
526553
"- [time](https://docs.python.org/3/library/time.html)\n",
527554
"- [datetime](https://docs.python.org/3/library/datetime.html)\n",
528-
"- [pytz](https://pypi.org/project/pytz/)\n",
555+
"- [zoneinfo](https://docs.python.org/3/library/zoneinfo.html)\n",
529556
"\n",
530557
"For more information on Python string formatting, try:\n",
531558
"- [Python string documentation](https://docs.python.org/3/library/string.html)\n",
532559
"- RealPython's [string formatting tutorial](https://realpython.com/python-string-formatting/) (nicely written)"
533560
]
534-
},
535-
{
536-
"cell_type": "code",
537-
"execution_count": null,
538-
"metadata": {},
539-
"outputs": [],
540-
"source": []
541561
}
542562
],
543563
"metadata": {

0 commit comments

Comments
 (0)