"""
Date, time and timezone utilities
---------------------------------
"""
from datetime import datetime
from aniso8601 import parse_datetime, parse_duration
from aniso8601.exceptions import ISOFormatError as ParseError
import isoweek
import pytz
__all__ = [
'utcnow',
'parse_isoformat',
'parse_duration',
'isoweek_datetime',
'midnight_to_utc',
'sorted_timezones',
'ParseError',
]
# --- Thread safety fix -------------------------------------------------------
# Force import of strptime. This was previously used in :func:`parse_isoformat`,
# but we have left this in because it could break elsewhere.
# https://stackoverflow.com/q/16309650/78903
datetime.strptime('20160816', '%Y%m%d')
[docs]def utcnow():
"""
Returns the current time at UTC with `tzinfo` set
"""
return datetime.now(pytz.UTC)
[docs]def isoweek_datetime(year, week, timezone='UTC', naive=False):
"""
Returns a datetime matching the starting point of a specified ISO week
in the specified timezone (default UTC). Returns a naive datetime in
UTC if requested (default False).
>>> isoweek_datetime(2017, 1)
datetime.datetime(2017, 1, 2, 0, 0, tzinfo=<UTC>)
>>> isoweek_datetime(2017, 1, 'Asia/Kolkata')
datetime.datetime(2017, 1, 1, 18, 30, tzinfo=<UTC>)
>>> isoweek_datetime(2017, 1, 'Asia/Kolkata', naive=True)
datetime.datetime(2017, 1, 1, 18, 30)
>>> isoweek_datetime(2008, 1, 'Asia/Kolkata')
datetime.datetime(2007, 12, 30, 18, 30, tzinfo=<UTC>)
"""
naivedt = datetime.combine(isoweek.Week(year, week).day(0), datetime.min.time())
if isinstance(timezone, str):
tz = pytz.timezone(timezone)
else:
tz = timezone
dt = tz.localize(naivedt).astimezone(pytz.UTC)
if naive:
return dt.replace(tzinfo=None)
else:
return dt
[docs]def midnight_to_utc(dt, timezone=None, naive=False):
"""
Returns a UTC datetime matching the midnight for the given date or datetime.
>>> from datetime import date
>>> midnight_to_utc(datetime(2017, 1, 1))
datetime.datetime(2017, 1, 1, 0, 0, tzinfo=<UTC>)
>>> midnight_to_utc(pytz.timezone('Asia/Kolkata').localize(datetime(2017, 1, 1)))
datetime.datetime(2016, 12, 31, 18, 30, tzinfo=<UTC>)
>>> midnight_to_utc(datetime(2017, 1, 1), naive=True)
datetime.datetime(2017, 1, 1, 0, 0)
>>> midnight_to_utc(pytz.timezone('Asia/Kolkata').localize(datetime(2017, 1, 1)),
... naive=True)
datetime.datetime(2016, 12, 31, 18, 30)
>>> midnight_to_utc(date(2017, 1, 1))
datetime.datetime(2017, 1, 1, 0, 0, tzinfo=<UTC>)
>>> midnight_to_utc(date(2017, 1, 1), naive=True)
datetime.datetime(2017, 1, 1, 0, 0)
>>> midnight_to_utc(date(2017, 1, 1), timezone='Asia/Kolkata')
datetime.datetime(2016, 12, 31, 18, 30, tzinfo=<UTC>)
>>> midnight_to_utc(datetime(2017, 1, 1), timezone='Asia/Kolkata')
datetime.datetime(2016, 12, 31, 18, 30, tzinfo=<UTC>)
>>> midnight_to_utc(pytz.timezone('Asia/Kolkata').localize(datetime(2017, 1, 1)),
... timezone='UTC')
datetime.datetime(2017, 1, 1, 0, 0, tzinfo=<UTC>)
"""
if timezone:
if isinstance(timezone, str):
tz = pytz.timezone(timezone)
else:
tz = timezone
elif isinstance(dt, datetime) and dt.tzinfo:
tz = dt.tzinfo
else:
tz = pytz.UTC
utc_dt = tz.localize(datetime.combine(dt, datetime.min.time())).astimezone(pytz.UTC)
if naive:
return utc_dt.replace(tzinfo=None)
return utc_dt
[docs]def sorted_timezones():
"""
Return a list of timezones sorted by offset from UTC.
"""
def hourmin(delta):
if delta.days < 0:
hours, remaining = divmod(86400 - delta.seconds, 3600)
else:
hours, remaining = divmod(delta.seconds, 3600)
minutes, remaining = divmod(remaining, 60)
return hours, minutes
now = datetime.utcnow()
# Make a list of country code mappings
timezone_country = {}
for countrycode in pytz.country_timezones:
for timezone in pytz.country_timezones[countrycode]:
timezone_country[timezone] = countrycode
# Make a list of timezones, discarding the US/* and Canada/* zones since they aren't
# reliable for DST, and discarding UTC and GMT since timezones in that zone have
# their own names
timezones = [
(pytz.timezone(tzname).utcoffset(now, is_dst=False), tzname)
for tzname in pytz.common_timezones
if not tzname.startswith('US/')
and not tzname.startswith('Canada/')
and tzname not in ('GMT', 'UTC')
]
# Sort timezones by offset from UTC and their human-readable name
presorted = [
(
delta,
'%s%s - %s%s (%s)'
% (
(delta.days < 0 and '-')
or (delta.days == 0 and delta.seconds == 0 and ' ')
or '+',
'%02d:%02d' % hourmin(delta),
(pytz.country_names[timezone_country[name]] + ': ')
if name in timezone_country
else '',
name.replace('_', ' '),
pytz.timezone(name).tzname(now, is_dst=False),
),
name,
)
for delta, name in timezones
]
presorted.sort()
# Return a list of (timezone, label) with the timezone offset included in the label.
return [(name, label) for (delta, label, name) in presorted]