From ee066cd14806ca7a3013d32030f91aa18e111a75 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Fri, 26 Feb 2016 11:44:16 +0100 Subject: [PATCH] Correctly handle utc timestamp for python 2.x. Backport again several things. This may be the last backport for 2.6 as the next time I'm thinking of discontinuing support instead of implementing 2.7 in 2.6... This should fix #306 and maybe #302. --- demo/moulinrouge/tests.py | 3 ++- pygal/_compat.py | 27 ++++++++++++++++++++++----- pygal/test/test_date.py | 15 +++++++++++++++ tox.ini | 2 +- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/demo/moulinrouge/tests.py b/demo/moulinrouge/tests.py index 2c944cc..1a0a997 100644 --- a/demo/moulinrouge/tests.py +++ b/demo/moulinrouge/tests.py @@ -463,6 +463,7 @@ def get_test_routes(app): chart.add(10 * '1b', [(4, 12), (5, 8), (6, 4)], secondary=True) chart.add(10 * '2b', [(3, 24), (0, 17), (12, 9)], secondary=True) chart.add(10 * '2', [(8, 23), (21, 1), (5, 0)]) + chart.value_formatter = lambda x: str(int(x)) + '+' return chart.render_response() @app.route('/test/box') @@ -930,7 +931,7 @@ def get_test_routes(app): (datetime(2013, 1, 12, 8), 412), (datetime(2013, 1, 12, 8, tzinfo=tzn4), 823) ]) - line.x_value_formatter = lambda x: x.isoformat() # strftime("%Y-%m-%d") + # line.x_value_formatter = lambda x: x.isoformat() # strftime("%Y-%m-%d") line.x_label_rotation = 45 return line.render_response() diff --git a/pygal/_compat.py b/pygal/_compat.py index 7d53ab0..997335d 100644 --- a/pygal/_compat.py +++ b/pygal/_compat.py @@ -16,11 +16,12 @@ # # You should have received a copy of the GNU Lesser General Public License # along with pygal. If not, see . +from __future__ import division """Various hacks for transparent python 2 / python 3 support""" import sys from collections import Iterable -import time +from datetime import datetime, timedelta, tzinfo if sys.version_info[0] == 3: @@ -70,16 +71,32 @@ def total_seconds(td): ) / 10 ** 6 return td.total_seconds() +try: + from datetime import timezone + utc = timezone.utc +except ImportError: + class UTC(tzinfo): + def tzname(self, dt): + return 'UTC' + + def utcoffset(self, dt): + return timedelta(0) + + def dst(self, dt): + return None + utc = UTC() + def timestamp(x): """Get a timestamp from a date in python 3 and python 2""" + if x.tzinfo is None: + # Naive dates to utc + x = x.replace(tzinfo=utc) + if hasattr(x, 'timestamp'): - from datetime import timezone - if x.tzinfo is None: - return x.replace(tzinfo=timezone.utc).timestamp() return x.timestamp() else: - return time.mktime(x.utctimetuple()) + return total_seconds(x - datetime(1970, 1, 1, tzinfo=utc)) try: from urllib import quote_plus diff --git a/pygal/test/test_date.py b/pygal/test/test_date.py index d1a4858..5950700 100644 --- a/pygal/test/test_date.py +++ b/pygal/test/test_date.py @@ -20,6 +20,7 @@ """Date related charts tests""" from pygal import DateLine, TimeLine, DateTimeLine, TimeDeltaLine +from pygal._compat import timestamp, utc from pygal.test.utils import texts from datetime import datetime, date, time, timedelta @@ -158,3 +159,17 @@ def test_date_labels(): '2013-01-01', '2013-02-01', '2013-03-01'] + + +def test_utc_timestamping(): + assert timestamp( + datetime(2017, 7, 14, 2, 40).replace(tzinfo=utc) + ) == 1500000000 + + for d in ( + datetime.now(), + datetime.utcnow(), + datetime(1999, 12, 31, 23, 59, 59), + datetime(2000, 1, 1, 0, 0, 0) + ): + assert datetime.utcfromtimestamp(timestamp(d)) == d diff --git a/tox.ini b/tox.ini index 4e7dfb5..969c033 100644 --- a/tox.ini +++ b/tox.ini @@ -14,5 +14,5 @@ setenv = COVERAGE_FILE=.cov-{envname} commands = - coverage run --source=pygal {envbindir}/py.test pygal/test --junitxml=junit-{envname}.xml --flake8 + coverage run --source=pygal {envbindir}/py.test {posargs:pygal/test} --junitxml=junit-{envname}.xml --flake8 coverage xml -o coverage-{envname}.xml