Last active
April 5, 2018 20:56
-
-
Save zed/4127162 to your computer and use it in GitHub Desktop.
Notes on `arrow` library.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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
Thank you, I will definitely take a look at this!