Skip to content

Instantly share code, notes, and snippets.

@gene9
Forked from zed/test_arrow_timezone.py
Last active August 29, 2015 14:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gene9/c3439e832fa711014497 to your computer and use it in GitHub Desktop.
Save gene9/c3439e832fa711014497 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""Notes on `arrow` library.
* inconsitent (fixed)
>>> a = arrow.get(datetime(2012, 4, 7, 0, 30, 00), 'America/New_York')
>>> a.datetime.tzname() == a.tzinfo.tzname(a.datetime) # fixed
True
* generic exception is thrown; should be more specific (fixed)
>>> a.to('utC') # fixed
Traceback (most recent call last):
...
arrow.parser.ParserError: ('Could not parse timezone expression "{0}"', 'utC')
* invalid type raises RuntimeError instead of TypeError (fixed)
>>> arrow.get(object) # fixed
Traceback (most recent call last):
...
TypeError: Can't parse single argument type of ...
* invalid value raises RuntimeError instead of ValueError or more
specific (fixed)
>>> arrow.get("aa") # fixed
Traceback (most recent call last):
...
arrow.parser.ParserError: Could not match input to any of ['YYYY-MM-DD', 'YYYY-MM', 'YYYY'] on 'aa'
* timestamp truncates fractions of a second (round-trip doesn't work)
>>> a = arrow.get(datetime(2012, 11, 21, 14, 50, 8, 816010))
>>> a.timestamp
1353509408
>>> def timestamp(utc_dt, epoch=datetime(1970, 1, 1)):
... return (utc_dt - epoch).total_seconds()
>>> timestamp(a.datetime.replace(tzinfo=None))
1353509408.81601
Note: if float doesn't provide enough precision (around the datetime.max
scale you might loose a dozen microsecond or two) you could get microseconds:
>>> def timestamp_microseconds(utc_dt, epoch=datetime(1970, 1, 1)):
... td = utc_dt - epoch
... return td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6
>>> timestamp_microseconds(a.datetime.replace(tzinfo=None))
1353509408816010
See http://stackoverflow.com/a/8778548
* `__repr__` is ambigous (depends on locale)
`eval(repr(a))` doesn't work but it is not surrounded by `<>`
>>> dt = datetime(2012, 11, 21, 14, 50, 8, 816010)
>>> a = arrow.get(dt)
>>> a
<Arrow [2012-11-21T14:50:08.816010+00:00]>
* enhancement: make it easy to convert from/to rfc 3339
See http://tools.ietf.org/html/rfc3339#section-5.8
"""
import os
import time
from datetime import datetime, timedelta
import pytz # $ pip install pytz
import arrow # $ pip install arrow
fmt = '%Y-%m-%d %H:%M:%S.%f %Z%z'
def assert_equal(dt, a):
assert dt.strftime(fmt) == a.datetime.strftime(fmt), (dt, a.datetime)
assert dt.tzname() == a.datetime.tzname()
assert dt.utcoffset() == a.datetime.utcoffset()
def fail(dt, a):
print("\texpected %s" % (dt.strftime(fmt),))
print("\tgot %s" % (a.datetime.strftime(fmt),))
try:
assert_equal(dt, a)
except AssertionError:
pass # expected failure
else:
assert 0, "unexpected success"
# Converting between time zones
# Summary: It works only with the latest timezone for the region.
# DST transitions, other timezone changes are not handled correctly
# Some issues due to inconsistency in tzinfo handling in `arrow`
# (e.g., tz.name is based on `.now(tz)`),
# some - due to the bug in dateutils see https://gist.github.com/3838828
print("* round-trip convert timestamp -> named timezone -> utc")
def totz(dt_aware, tz):
# if dt is not in UTC; tz.normalize might be required
return tz.normalize(dt_aware.astimezone(tz))
def utc_astz(utc_dt, tz):
result = tz.fromutc(utc_dt)
assert result == utc_dt.replace(tzinfo=pytz.utc).astimezone(tz)
# show general formula
from_tz, from_tz_naive_dt = pytz.utc, utc_dt
assert result == totz(from_tz.localize(from_tz_naive_dt, is_dst=None), tz)
return result
# expected 5:30 UTC+0000, got 6:30 UTC+0000
tz = pytz.timezone('US/Eastern')
ts = 1035696600
dt = datetime.utcfromtimestamp(ts)
fail(utc_astz(dt, tz).astimezone(pytz.utc), arrow.get(ts).to(tz.zone).to('UTC'))
print("** convert utc datetime -> named timezone")
# expected EDT-0400, got EST-0500
fail(utc_astz(dt, tz), arrow.get(dt).to(tz.zone))
# works
a = arrow.get(dt + timedelta(hours=1)).to('America/Toronto')
assert a.tzname() == 'EST' and a.utcoffset() == timedelta(hours=-5)
print("* convert utc to local timezone")
# see for other solutions at http://stackoverflow.com/a/13287083
# set your local timezone name to Europe/Moscow
not_set = object()
oldtz = os.environ.get('TZ', not_set)
os.environ['TZ'] = 'Europe/Moscow' # works on Ubuntu
time.tzset()
local_tz = pytz.timezone('Europe/Moscow')
def utc_aslocal(utc_dt):
return utc_astz(utc_dt, local_tz)
# expected MSD+0400 (daylight)
fail(utc_aslocal(datetime(2010, 6, 6, 17, 29, 7, 730000)),
arrow.get(datetime(2010, 6, 6, 17, 29, 7, 730000)).to('local'))
# FIXED expected MSK+0300 (standard), got MSK+0400
assert_equal(utc_aslocal(datetime(2010, 12, 6, 17, 29, 7, 730000)),
arrow.get(datetime(2010, 12, 6, 17, 29, 7, 730000)).to('local'))
# expected MSK+0400
fail(utc_aslocal(datetime(2010, 6, 6, 17, 29, 7, 730000)),
arrow.get(datetime(2010, 6, 6, 17, 29, 7, 730000)).to('local'))
# restore TZ
if oldtz is not_set:
if 'TZ' in os.environ:
del os.environ['TZ']
else:
os.environ['TZ'] = oldtz
time.tzset()
print("* date & time in named time zone (FIXED)")
# (naive datetime in named time zone to timezone-aware object)
# FIXED expected: EDT-0400, got EST-0500
assert_equal(tz.localize(datetime(2002, 10, 27, 0, 30), is_dst=None),
arrow.get(datetime(2002, 10, 27, 0, 30), tz.zone))
a = arrow.get(datetime(2002, 10, 27, 12, 0, 0), 'Europe/Amsterdam')
assert a.datetime.tzname() == 'CET' and a.utcoffset() == timedelta(hours=1)
print("* ambiguous: naive datetime to named timezone (FIXED)")
# it could be EDT-0400 or EST-0500
ambiguous_dt = datetime(2002, 10, 27, 1, 30)
a = arrow.get(ambiguous_dt, tz.zone)
assert (a.utcoffset() == timedelta(hours=-4) or
a.utcoffset() == timedelta(hours=-5))
assert a.utcoffset() == a.datetime.utcoffset()
# compare:
dt1 = tz.localize(ambiguous_dt, is_dst=False)
assert dt1 == tz.localize(ambiguous_dt)
assert dt1.tzinfo.utcoffset(dt1) == timedelta(hours=-5)
assert_equal(dt1, arrow.get(dt1))
dt2 = tz.localize(ambiguous_dt, is_dst=True)
assert dt2.tzinfo.utcoffset(dt2) == timedelta(hours=-4)
assert_equal(dt2, arrow.get(dt2))
try:
tz.localize(ambiguous_dt, is_dst=None) # forbid ambiguous local times
except pytz.AmbiguousTimeError:
pass
else:
assert 0, "should not happen"
print("* non-existing times")
# expected non-existent time, got 2:30 EDT-0400
non_existent_eastern_dt = datetime(2002, 4, 7, 2, 30, 00)
try:
tz.localize(non_existent_eastern_dt, is_dst=None)
except pytz.NonExistentTimeError:
tz.localize(non_existent_eastern_dt) # can construct invalid
# time if desired
else:
assert 0, "should not get here"
print("\texpected non-existent time error\n\tgot %s" % (
arrow.get(non_existent_eastern_dt, tz.zone).datetime.strftime(fmt),))
print("* time zones in a distant past (FIXED)")
# FIXED expected WMT+0124
assert_equal(pytz.timezone('Europe/Warsaw').localize(
datetime(1915, 8, 4, 22, 59, 59), is_dst=None),
arrow.get(datetime(1915, 8, 4, 22, 59, 59), 'Europe/Warsaw'))
# CET+0100
a = arrow.get(datetime(1915, 8, 5, 0, 0, 0), 'Europe/Warsaw')
assert a.datetime.tzname() == 'CET' and a.utcoffset() == timedelta(hours=1)
assert_equal(pytz.timezone('Europe/Warsaw').localize(
datetime(1915, 8, 5, 0, 0, 0), is_dst=None), a)
# See http://stackoverflow.com/q/7770730
# http://www.timeanddate.com/worldclock/clockchange.html?n=16&year=1933
print("** seconds in utcoffset (pytz also fails here, datetime limitation)")
# FIXED (partially, pytz also fails here) expected +0:19:32 AMT, got CET+0100
assert_equal(pytz.timezone('Europe/Amsterdam').localize(
datetime(1933, 5, 15, 1, 30, 0), is_dst=None),
arrow.get(datetime(1933, 5, 15, 1, 30, 0), 'Europe/Amsterdam'))
# FIXED (partially, pytz also fails here) expected +1:19:32 NST, got CET+0100
assert_equal(pytz.timezone('Europe/Amsterdam').localize(
datetime(1933, 5, 15, 3, 30, 0), is_dst=None),
arrow.get(datetime(1933, 5, 15, 3, 30, 0), 'Europe/Amsterdam'))
print("Examples are from http://pytz.sourceforge.net/")
if __name__ == "__main__":
import doctest
import sys
sys.exit(doctest.testmod(optionflags=doctest.ELLIPSIS).failed)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment