# HG changeset patch # User Marcin Kuzminski # Date 2018-04-09 08:54:07 # Node ID 7fe5f77f3720605e315571a0a08d4741fc1283a8 # Parent d73e2e7580404acecb02f4f483199f1ab7f0048e timezone: fix issues with timezone detection of current logic. - it was reported that in certain timezones e.g `US/Central` the generated offset was +19:00 which is incorrect and should be -05:00. - we now use a special library for detection of local timezone and use logic of pytz to calculate the offset - fixes #5371 diff --git a/pkgs/python-packages.nix b/pkgs/python-packages.nix --- a/pkgs/python-packages.nix +++ b/pkgs/python-packages.nix @@ -1602,13 +1602,13 @@ }; }; pytz = super.buildPythonPackage { - name = "pytz-2017.3"; + name = "pytz-2018.3"; buildInputs = with self; []; doCheck = false; propagatedBuildInputs = with self; []; src = fetchurl { - url = "https://pypi.python.org/packages/60/88/d3152c234da4b2a1f7a989f89609ea488225eaea015bc16fbde2b3fdfefa/pytz-2017.3.zip"; - md5 = "7006b56c0d68a162d9fe57d4249c3171"; + url = "https://pypi.python.org/packages/1b/50/4cdc62fc0753595fc16c8f722a89740f487c6e5670c644eb8983946777be/pytz-2018.3.tar.gz"; + md5 = "abb07c09c79f78d7c04f222a550c99ef"; }; meta = { license = [ pkgs.lib.licenses.mit ]; @@ -1683,7 +1683,7 @@ name = "rhodecode-enterprise-ce-4.12.0"; buildInputs = with self; [pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage configobj]; doCheck = true; - propagatedBuildInputs = with self; [setuptools-scm amqp authomatic Babel Beaker celery Chameleon channelstream click colander configobj cssselect decorator deform docutils dogpile.cache dogpile.core ecdsa FormEncode future futures gnureadline infrae.cache iso8601 itsdangerous Jinja2 billiard kombu lxml Mako Markdown MarkupSafe msgpack-python MySQL-python objgraph packaging Paste PasteDeploy PasteScript pathlib2 peppercorn psutil psycopg2 py-bcrypt pycrypto pycurl pyflakes pygments-markdown-lexer Pygments pyparsing pyramid-beaker pyramid-debugtoolbar pyramid-jinja2 pyramid-mako pyramid pysqlite python-dateutil python-ldap python-memcached python-pam pytz pyzmq py-gfm recaptcha-client redis repoze.lru requests Routes setproctitle simplejson six SQLAlchemy sshpubkeys subprocess32 supervisor Tempita translationstring trollius urllib3 URLObject venusian WebError WebHelpers2 WebHelpers WebOb Whoosh wsgiref zope.cachedescriptors zope.deprecation zope.event zope.interface nbconvert bleach nbformat jupyter-client alembic invoke bumpversion transifex-client gevent greenlet gunicorn waitress uWSGI ipdb ipython CProfileV bottle rhodecode-tools appenlight-client pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage]; + propagatedBuildInputs = with self; [setuptools-scm amqp authomatic Babel Beaker celery Chameleon channelstream click colander configobj cssselect decorator deform docutils dogpile.cache dogpile.core ecdsa FormEncode future futures gnureadline infrae.cache iso8601 itsdangerous Jinja2 billiard kombu lxml Mako Markdown MarkupSafe msgpack-python MySQL-python objgraph packaging Paste PasteDeploy PasteScript pathlib2 peppercorn psutil psycopg2 py-bcrypt pycrypto pycurl pyflakes pygments-markdown-lexer Pygments pyparsing pyramid-beaker pyramid-debugtoolbar pyramid-jinja2 pyramid-mako pyramid pysqlite python-dateutil python-ldap python-memcached python-pam pytz tzlocal pyzmq py-gfm recaptcha-client redis repoze.lru requests Routes setproctitle simplejson six SQLAlchemy sshpubkeys subprocess32 supervisor Tempita translationstring trollius urllib3 URLObject venusian WebError WebHelpers2 WebHelpers WebOb Whoosh wsgiref zope.cachedescriptors zope.deprecation zope.event zope.interface nbconvert bleach nbformat jupyter-client alembic invoke bumpversion transifex-client gevent greenlet gunicorn waitress uWSGI ipdb ipython CProfileV bottle rhodecode-tools appenlight-client pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage]; src = ./.; meta = { license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ]; @@ -1910,6 +1910,19 @@ license = [ pkgs.lib.licenses.asl20 ]; }; }; + tzlocal = super.buildPythonPackage { + name = "tzlocal-1.5.1"; + buildInputs = with self; []; + doCheck = false; + propagatedBuildInputs = with self; [pytz]; + src = fetchurl { + url = "https://pypi.python.org/packages/cb/89/e3687d3ed99bc882793f82634e9824e62499fdfdc4b1ae39e211c5b05017/tzlocal-1.5.1.tar.gz"; + md5 = "4553be891efa0812c4adfb0c6e818eec"; + }; + meta = { + license = [ pkgs.lib.licenses.mit ]; + }; + }; uWSGI = super.buildPythonPackage { name = "uWSGI-2.0.15"; buildInputs = with self; []; diff --git a/requirements.txt b/requirements.txt --- a/requirements.txt +++ b/requirements.txt @@ -61,7 +61,8 @@ python-dateutil python-ldap==2.4.45 python-memcached==1.58 python-pam==1.8.2 -pytz==2017.3 +pytz==2018.3 +tzlocal==1.5.1 pyzmq==14.6.0 py-gfm==0.1.3 recaptcha-client==1.0.6 diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py +++ b/rhodecode/lib/helpers.py @@ -25,6 +25,7 @@ Consists of functions to typically be us available to Controllers. This module is available to both as 'h'. """ +import os import random import hashlib import StringIO @@ -742,18 +743,23 @@ short_id = lambda x: x[:12] hide_credentials = lambda x: ''.join(credentials_filter(x)) +import pytz +import tzlocal +local_timezone = tzlocal.get_localzone() + + def age_component(datetime_iso, value=None, time_is_local=False): title = value or format_date(datetime_iso) tzinfo = '+00:00' # detect if we have a timezone info, otherwise, add it - if isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo: - if time_is_local: - tzinfo = time.strftime("+%H:%M", - time.gmtime( - (datetime.now() - datetime.utcnow()).seconds + 1 - ) - ) + if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo: + force_timezone = os.environ.get('RC_TIMEZONE', '') + if force_timezone: + force_timezone = pytz.timezone(force_timezone) + timezone = force_timezone or local_timezone + offset = timezone.localize(datetime_iso).strftime('%z') + tzinfo = '{}:{}'.format(offset[:-2], offset[-2:]) return literal( '