Here’s a few examples of working with dates, times and timezones in Python. We are using the following packages:
- datetime (part of the standard Python distribution)
- dateutil – for date parsing, though there’s a lot more depth to this package that I’m not touching here
- pytz – for timezone handling, and specifically making available the Olson timezone database to Python
There’s a lot of complexity to working with datetimes in any language; I’m not going to get into that but would prefer instead to show a few practical examples. Keep the following in mind:
- datetimes may or may not have timezones associated with them. If they do not, they are called “naive” and their meaning is effectively defined by the program. In general, you want to work with non-naive datetimes. Generally the assumption would be that the naive datetime is in the application’s current timezone or the user’s preferred timezone
- when working with datetimes, consider the strategy of converting everything to the universal UTC timezone, then converting back to the user’s timezone only when you need to display that to the user
- if you are rolling your own code for handling dates, times and timezones and you haven’t done a lot of research, your implementation is garbage. Do yourself and everyone else a favor and use a library.
Our standard imports. Log is from the pybm library and it’s purpose is rather obvious.
from bm_log import Log
import dateutil.parser
import pytz
import datetime
Here’s an example of parsing the an e-mail or RSS type date using dateutil.
dts = "Thu, 13 Nov 2008 05:41:35 +0000"
dt = dateutil.parser.parse(dts)
Log(
"Parsing an RFC type date",
src = dts,
dt = dt,
iso = dt.isoformat(),
)
message: Parsing an RFC type date
dt: 2008-11-13 05:41:35+00:00
iso: 2008-11-13T05:41:35+00:00
src: Thu, 13 Nov 2008 05:41:35 +0000
Here’s an example of parsing an ISO Datetime
dts = '2008-11-13T05:41:35-0400'
dt = dateutil.parser.parse(dts)
Log(
"Parsing an ISO Date with Timezone",
src = dts,
dt = dt,
iso = dt.isoformat(),
)
message: Parsing an ISO Date with Timezone
dt: 2008-11-13 05:41:35-04:00
iso: 2008-11-13T05:41:35-04:00
src: 2008-11-13T05:41:35-0400
Here’s an example of parsing a naive timezone.
dts = '2008-11-13T05:41:35'
dt = dateutil.parser.parse(dts)
Log(
"Parsing an ISO Date without a Timezone",
src = dts,
dt = dt,
iso = dt.isoformat(),
)
message: Parsing an ISO Date without a Timezone
dt: 2008-11-13 05:41:35
iso: 2008-11-13T05:41:35
src: 2008-11-13T05:41:35
Here’s are two similar example, showing how to force the timezone if it’s not present. This will happen in the first part, but not the second.
tz = pytz.timezone('America/Toronto')
dts = '2008-11-13T05:41:35'
dt = dateutil.parser.parse(dts)
if dt.tzinfo == None:
dt = dt.replace(tzinfo = tz)
Log(
"Parsing an ISO Date without a Timezone BUT specifying default TZ",
src = dts,
dt = dt,
iso = dt.isoformat(),
tz = tz,
)
tz = pytz.timezone('America/Toronto')
dts = '2008-11-13T05:41:35-0400'
dt = dateutil.parser.parse(dts)
if dt.tzinfo == None:
dt = dt.replace(tzinfo = tz)
Log(
"Parsing an ISO Date with a Timezone AND specifying default TZ",
src = dts,
dt = dt,
iso = dt.isoformat(),
tz = tz,
)
message: Parsing an ISO Date without a Timezone BUT specifying default TZ
dt: 2008-11-13 05:41:35-05:00
iso: 2008-11-13T05:41:35-05:00
src: 2008-11-13T05:41:35
tz: America/Toronto
message: Parsing an ISO Date with a Timezone AND specifying default TZ
dt: 2008-11-13 05:41:35-04:00
iso: 2008-11-13T05:41:35-04:00
src: 2008-11-13T05:41:35-0400
tz: America/Toronto
Update: here’s an example of moving datetimes to UTC and then to a different Timezone. Remember: you want your backend code to work with UTC datetimes for simplicity and correctness:
dts = '2008-11-13T05:41:35-0400'
dt_orig = dateutil.parser.parse(dts)
dt_utc = dt.astimezone(pytz.UTC)
Log(
"Changing a datetime to UTC",
src = dts,
dt_orig = dt_orig,
dt_utc = dt_utc,
)
tz_vancouver = pytz.timezone('America/Vancouver')
dt_vancouver = dt_utc.astimezone(tz_vancouver)
Log(
"Changing UTC datetime to a different timezone",
dt_vancouver = dt_vancouver,
dt_utc = dt_utc,
)
message: Changing a datetime to UTC
dt_orig: 2008-11-13 05:41:35-04:00
dt_utc: 2008-11-13 09:41:35+00:00
src: 2008-11-13T05:41:35-0400
message: Changing UTC datetime to a different timezone
dt_utc: 2008-11-13 09:41:35+00:00
dt_vancouver: 2008-11-13 01:41:35-08:00
Here is an example of listing all “common” timezones using pytz. Note that “America” refers to the two continents, not the Irish word for the United States. Printing the actual timezone offset turned out to be a surprisingly complex task, which I will outline in a different blog post. For now let it suffice that with pytz try not to depend on utcoffset.
dt_now = datetime.datetime.now()
def tzname2offset(tzname):
dt_in_utc = pytz.UTC.localize(dt_now)
dt_in_tz = pytz.timezone(tzname).localize(dt_now)
offset = dt_in_utc - dt_in_tz
seconds = offset.seconds + offset.days * ( 24 * 3600 )
return "%02d:%02d" % ( seconds // 3600, seconds % 3600 // 60, )
Log(
"Olsen (pytz) common timezones and their UTC offsets",
timezones = map(
lambda tzname: ( tzname, tzname2offset(tzname), ),
pytz.common_timezones,
)
)
message: Olsen (pytz) common timezones and their UTC offsets
timezones:
[('Africa/Abidjan', '00:00'),
('Africa/Accra', '00:00'),
('Africa/Addis_Ababa', '03:00'),
('Africa/Algiers', '01:00'),
('Africa/Asmara', '03:00'),
...
('Pacific/Wake', '12:00'),
('Pacific/Wallis', '12:00'),
('US/Alaska', '-9:00'),
('US/Arizona', '-7:00'),
('US/Central', '-6:00'),
('US/Eastern', '-5:00'),
('US/Hawaii', '-10:00'),
('US/Mountain', '-7:00'),
('US/Pacific', '-8:00'),
('UTC', '00:00')]