From 2cfad1d9e01f0557bc69e6ca0180359e361c1a04 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Aug 11 2020 13:57:07 +0000 Subject: [PATCH 1/6] Fix time formatting for timezone values Fixes: https://pagure.io/koji/issue/2423 --- diff --git a/koji/__init__.py b/koji/__init__.py index 5a48b1f..a02458b 100644 --- a/koji/__init__.py +++ b/koji/__init__.py @@ -50,6 +50,7 @@ import xml.sax import xml.sax.handler from fnmatch import fnmatch +import dateutil.parser import requests import six import six.moves.configparser @@ -3277,10 +3278,18 @@ def formatTimeLong(value): """ if not value: return '' + if isinstance(value, six.string_types): + t = dateutil.parser.parse(value) + elif isinstance(value, xmlrpc_client.DateTime): + t = dateutil.parser.parse(value.value) else: - # Assume the string value passed in is the local time - localtime = time.mktime(time.strptime(formatTime(value), '%Y-%m-%d %H:%M:%S')) - return time.strftime('%a, %d %b %Y %H:%M:%S %Z', time.localtime(localtime)) + t = value + # return date in local timezone, py 2.6 has tzone as astimezone required parameter + # would work simply as t.astimezone() for py 2.7+ + if t.tzinfo is None: + t = t.replace(tzinfo=dateutil.tz.gettz()) + t = t.astimezone(dateutil.tz.gettz()) + return datetime.datetime.strftime(t, '%a, %d %b %Y %H:%M:%S %Z') def buildLabel(buildInfo, showEpoch=False): diff --git a/tests/test_lib/test_format_time.py b/tests/test_lib/test_format_time.py index b780dfa..c919495 100644 --- a/tests/test_lib/test_format_time.py +++ b/tests/test_lib/test_format_time.py @@ -1,5 +1,7 @@ from __future__ import absolute_import import datetime +import os +import time import locale try: import unittest2 as unittest @@ -11,6 +13,16 @@ import six.moves.xmlrpc_client as xmlrpc_client from koji import formatTime, formatTimeLong class TestFormatTime(unittest.TestCase): + def setUp(self): + self._orig_tz = os.environ.get('TZ') + + def tearDown(self): + if self._orig_tz: + os.environ['TZ'] = self._orig_tz + elif 'TZ' in os.environ: + del os.environ['TZ'] + time.tzset() + def test_format_time(self): self.assertEqual(formatTime(None), '') self.assertEqual(formatTime(''), '') @@ -33,6 +45,8 @@ class TestFormatTime(unittest.TestCase): def test_format_time_long(self): # force locale to compare 'desired' value locale.setlocale(locale.LC_ALL, ('en_US', 'UTF-8')) + os.environ['TZ'] = 'GMT' + time.tzset() self.assertEqual(formatTimeLong(None), '') self.assertEqual(formatTimeLong(''), '') @@ -62,4 +76,28 @@ class TestFormatTime(unittest.TestCase): r = r[:r.rfind(' ')] self.assertEqual(r, desired) + # str + timezone + d3 = '2017-10-05 09:52:31+02:00' + desired = 'Thu, 05 Oct 2017 07:52:31 GMT' + os.environ['TZ'] = 'GMT' + time.tzset() + r = formatTimeLong(d3) + self.assertEqual(r, desired) + + # non-GMT without DST + d3 = '2017-06-05 09:52:31+02:00' + desired = 'Mon, 05 Jun 2017 09:52:31 CEST' + os.environ['TZ'] = 'Europe/Prague' + time.tzset() + r = formatTimeLong(d3) + self.assertEqual(r, desired) + + # non-GMT with DST + d3 = '2017-12-05 09:52:31+02:00' + desired = 'Tue, 05 Dec 2017 08:52:31 CET' + os.environ['TZ'] = 'Europe/Prague' + time.tzset() + r = formatTimeLong(d3) + self.assertEqual(r, desired) + locale.resetlocale() From 590d7846d2fe9a800d1f9a856c68294a9afcbf0b Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Aug 17 2020 11:13:05 +0000 Subject: [PATCH 2/6] formatTime/Long can handle timestamp Also replaced *_time with *_ts wherever possible. --- diff --git a/builder/kojid b/builder/kojid index 9f6d644..96999c1 100755 --- a/builder/kojid +++ b/builder/kojid @@ -5275,8 +5275,8 @@ Build Info: %(weburl)s/buildinfo?buildID=%(build_id)i\r if target is not None: dest_tag = target['dest_tag_name'] status = koji.BUILD_STATES[build['state']].lower() - creation_time = koji.formatTimeLong(build['creation_time']) - completion_time = koji.formatTimeLong(build['completion_time']) + creation_time = koji.formatTimeLong(build['creation_ts']) + completion_time = koji.formatTimeLong(build['completion_ts']) task_id = build['task_id'] task_data = self._getTaskData(task_id) diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index bffd85c..8c44631 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -3225,7 +3225,7 @@ def anon_handle_buildinfo(goptions, session, args): print("Task: %s %s" % (task['id'], koji.taskLabel(task))) else: print("Task: none") - print("Finished: %s" % koji.formatTimeLong(info['completion_time'])) + print("Finished: %s" % koji.formatTimeLong(info['completion_ts'])) maven_info = session.getMavenBuild(info['id']) if maven_info: print("Maven groupId: %s" % maven_info['group_id']) @@ -4729,7 +4729,7 @@ def _do_parseTaskParams(session, method, task_id, topdir): oldrepo = params[2] if oldrepo: lines.append("Old Repo ID: %i" % oldrepo['id']) - lines.append("Old Repo Creation: %s" % koji.formatTimeLong(oldrepo['creation_time'])) + lines.append("Old Repo Creation: %s" % koji.formatTimeLong(oldrepo['creation_ts'])) if len(params) > 3: lines.append("External Repos: %s" % ', '.join([ext['external_repo_name'] for ext in params[3]])) diff --git a/koji/__init__.py b/koji/__init__.py index a02458b..fdb1d75 100644 --- a/koji/__init__.py +++ b/koji/__init__.py @@ -3257,10 +3257,12 @@ class DBHandler(logging.Handler): def formatTime(value): """Format a timestamp so it looks nicer""" - if not value: + if not value and not isinstance(value, (int, float)): return '' if isinstance(value, xmlrpc_client.DateTime): value = datetime.datetime.strptime(value.value, "%Y%m%dT%H:%M:%S") + elif isinstance(value, (int, float)): + value = datetime.datetime.fromtimestamp(value) if isinstance(value, datetime.datetime): return value.strftime('%Y-%m-%d %H:%M:%S') else: @@ -3276,12 +3278,14 @@ def formatTimeLong(value): """Format a timestamp to a more human-reable format, i.e.: Sat, 07 Sep 2002 00:00:01 GMT """ - if not value: + if not value and not isinstance(value, (int, float)): return '' if isinstance(value, six.string_types): t = dateutil.parser.parse(value) elif isinstance(value, xmlrpc_client.DateTime): t = dateutil.parser.parse(value.value) + elif isinstance(value, (int, float)): + t = datetime.datetime.fromtimestamp(value) else: t = value # return date in local timezone, py 2.6 has tzone as astimezone required parameter diff --git a/tests/test_lib/test_format_time.py b/tests/test_lib/test_format_time.py index c919495..7fb588e 100644 --- a/tests/test_lib/test_format_time.py +++ b/tests/test_lib/test_format_time.py @@ -100,4 +100,17 @@ class TestFormatTime(unittest.TestCase): r = formatTimeLong(d3) self.assertEqual(r, desired) + # timestamps, local timezone + d4 = 0 + desired = 'Thu, 01 Jan 1970 01:00:00 CET' + r = formatTimeLong(d4) + self.assertEqual(r, desired) + + # timestamps, GMT + desired = 'Thu, 01 Jan 1970 00:00:00 GMT' + os.environ['TZ'] = 'GMT' + time.tzset() + r = formatTimeLong(d4) + self.assertEqual(r, desired) + locale.resetlocale() diff --git a/www/kojiweb/buildinfo.chtml b/www/kojiweb/buildinfo.chtml index 6ad940b..2a70487 100644 --- a/www/kojiweb/buildinfo.chtml +++ b/www/kojiweb/buildinfo.chtml @@ -69,7 +69,7 @@ $build.volume_name - Started$util.formatTimeLong($start_time) + Started$util.formatTimeLong($start_ts) #if $build.state == $koji.BUILD_STATES.BUILDING #if $estCompletion @@ -79,7 +79,7 @@ #end if #else - Completed$util.formatTimeLong($build.completion_time) + Completed$util.formatTimeLong($build.completion_ts) #end if #if $build.cg_id diff --git a/www/kojiweb/builds.chtml b/www/kojiweb/builds.chtml index 843955d..fd14d1d 100644 --- a/www/kojiweb/builds.chtml +++ b/www/kojiweb/builds.chtml @@ -127,7 +127,7 @@ $build.tag_name #end if $build.owner_name - $util.formatTime($build.completion_time) + $util.formatTime($build.completion_ts) #set $stateName = $util.stateName($build.state) $util.stateImage($build.state) diff --git a/www/kojiweb/fileinfo.chtml b/www/kojiweb/fileinfo.chtml index 1a1d04d..07dbe32 100644 --- a/www/kojiweb/fileinfo.chtml +++ b/www/kojiweb/fileinfo.chtml @@ -23,7 +23,7 @@ #if 'mtime' in $file and $file.mtime - Modification time$util.formatTimeLong($datetime.datetime.fromtimestamp($file.mtime)) + Modification time$util.formatTimeLong($file.mtime) #end if #if 'user' in $file and $file.user diff --git a/www/kojiweb/index.chtml b/www/kojiweb/index.chtml index 2961bd4..ab14d0c 100644 --- a/www/kojiweb/index.chtml +++ b/www/kojiweb/index.chtml @@ -24,7 +24,7 @@ #if not $user $build.owner_name #end if - $util.formatTime($build.completion_time) + $util.formatTime($build.completion_ts) $util.stateImage($build.state) #end for @@ -65,7 +65,7 @@ #end if $task.arch - $util.formatTime($task.completion_time) + $util.formatTime($task.completion_ts) $util.imageTag($state) #end for diff --git a/www/kojiweb/packageinfo.chtml b/www/kojiweb/packageinfo.chtml index 639584c..0e3838e 100644 --- a/www/kojiweb/packageinfo.chtml +++ b/www/kojiweb/packageinfo.chtml @@ -48,7 +48,7 @@ $build.nvr $build.owner_name - $util.formatTime($build.completion_time) + $util.formatTime($build.completion_ts) #set $stateName = $util.stateName($build.state) $util.stateImage($build.state) diff --git a/www/kojiweb/recentbuilds.chtml b/www/kojiweb/recentbuilds.chtml index f1688e7..28e8eb5 100644 --- a/www/kojiweb/recentbuilds.chtml +++ b/www/kojiweb/recentbuilds.chtml @@ -43,7 +43,7 @@ $koji.BUILD_STATES[$build.state].lower(): $koji.buildLabel($build)#if $build.task then ', target: ' + $build.task.request[1] else ''# $weburl/buildinfo?buildID=$build.build_id #if $build.completion_time - $util.formatTimeRSS($build.completion_time) + $util.formatTimeRSS($build.completion_ts) #end if #if $build.state == $koji.BUILD_STATES['COMPLETE'] and $build.changelog <pre>$util.escapeHTML($koji.util.formatChangelog($build.changelog))</pre> diff --git a/www/kojiweb/repoinfo.chtml b/www/kojiweb/repoinfo.chtml index afbcfe0..b735875 100644 --- a/www/kojiweb/repoinfo.chtml +++ b/www/kojiweb/repoinfo.chtml @@ -11,7 +11,7 @@ Tag$repo.tag_name #set $state = $util.repoState($repo.state) State$state - Event$repo.create_event ($util.formatTimeLong($repo.creation_time)) + Event$repo.create_event ($util.formatTimeLong($repo.creation_ts)) #if $repo.state != koji.REPO_STATES['DELETED'] URLrepodata Repo jsonrepo.json diff --git a/www/kojiweb/taginfo.chtml b/www/kojiweb/taginfo.chtml index 63b1fb9..6c1f9b7 100644 --- a/www/kojiweb/taginfo.chtml +++ b/www/kojiweb/taginfo.chtml @@ -115,7 +115,7 @@ Repo created #if $repo - $util.formatTimeRSS($repo.creation_time) + $util.formatTimeRSS($repo.creation_ts) #end if diff --git a/www/kojiweb/taskinfo.chtml b/www/kojiweb/taskinfo.chtml index 55f9d6e..75f6010 100644 --- a/www/kojiweb/taskinfo.chtml +++ b/www/kojiweb/taskinfo.chtml @@ -77,11 +77,11 @@ #end for #end if - Created$util.formatTimeLong($task.create_time) + Created$util.formatTimeLong($task.create_ts) #if $task.start_time - Started$util.formatTimeLong($task.start_time) + Started$util.formatTimeLong($task.start_ts) #end if #set $end_ts = None #if $task.state == $koji.TASK_STATES.OPEN @@ -93,7 +93,7 @@ #end if #elif $task.completion_time - Completed$util.formatTimeLong($task.completion_time) + Completed$util.formatTimeLong($task.completion_ts) #set $end_ts = $task.completion_ts #end if diff --git a/www/kojiweb/taskinfo_params.chtml b/www/kojiweb/taskinfo_params.chtml index b83a4d2..f59a96c 100644 --- a/www/kojiweb/taskinfo_params.chtml +++ b/www/kojiweb/taskinfo_params.chtml @@ -195,7 +195,7 @@ $printOpts($params[3]) #set $oldrepo = $params[2] #if $oldrepo Old Repo ID: $oldrepo.id
- Old Repo Creation: $koji.formatTimeLong($oldrepo.creation_time)
+ Old Repo Creation: $koji.formatTimeLong($oldrepo.creation_ts)
#end if #if $len($params) > 4 and $params[4] External Repos: $printValue(None, [ext['external_repo_name'] for ext in $params[3]])
diff --git a/www/kojiweb/tasks.chtml b/www/kojiweb/tasks.chtml index 4c1a32f..842d617 100644 --- a/www/kojiweb/tasks.chtml +++ b/www/kojiweb/tasks.chtml @@ -153,7 +153,7 @@ All #end if $task.arch - $util.formatTime($task.completion_time) + $util.formatTime($task.completion_ts) $util.imageTag($taskState) #end for diff --git a/www/kojiweb/userinfo.chtml b/www/kojiweb/userinfo.chtml index f7b97fb..88db1a3 100644 --- a/www/kojiweb/userinfo.chtml +++ b/www/kojiweb/userinfo.chtml @@ -93,7 +93,7 @@ #set $stateName = $util.stateName($build.state) $build.nvr - $util.formatTime($build.completion_time) + $util.formatTime($build.completion_ts) $util.stateImage($build.state) #end for From 237a4138cb0189bd87c553fa5cc2872f07adda94 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Aug 18 2020 14:00:23 +0000 Subject: [PATCH 3/6] fix start_ts --- diff --git a/www/kojiweb/index.py b/www/kojiweb/index.py index 9c3fd02..b2ec2a4 100644 --- a/www/kojiweb/index.py +++ b/www/kojiweb/index.py @@ -1276,12 +1276,12 @@ def buildinfo(environ, buildID): if field not in values: values[field] = None - values['start_time'] = build.get('start_time') or build['creation_time'] + values['start_ts'] = build.get('start_ts') or build['creation_ts'] # the build start time is not accurate for maven and win builds, get it from the # task start time instead if 'maven' in typeinfo or 'win' in typeinfo: if task: - values['start_time'] = task['start_time'] + values['start_ts'] = task['start_ts'] if build['state'] == koji.BUILD_STATES['BUILDING']: avgDuration = server.getAverageBuildDuration(build['package_id']) if avgDuration is not None: From 9a9bf9f0e334817f6e913ee0205f047d2306b531 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Aug 25 2020 11:01:03 +0000 Subject: [PATCH 4/6] fix tests --- diff --git a/tests/test_builder/data/calls/build_notif_1/message.txt b/tests/test_builder/data/calls/build_notif_1/message.txt index 34d3d1f..1693f6a 100644 --- a/tests/test_builder/data/calls/build_notif_1/message.txt +++ b/tests/test_builder/data/calls/build_notif_1/message.txt @@ -11,8 +11,8 @@ Tag: f23 Status: complete Built by: user ID: 612609 -Started: Wed, 18 Feb 2015 14:50:37 EST -Finished: Wed, 18 Feb 2015 14:57:37 EST +Started: Wed, 18 Feb 2015 09:50:37 EST +Finished: Wed, 18 Feb 2015 09:57:37 EST Changelog: * Wed Feb 18 2015 Happy Koji User - 1:0.3.0-0.2.M1 - Unbundle ASM diff --git a/tests/test_builder/test_build_notification.py b/tests/test_builder/test_build_notification.py index a9056e4..da92ba5 100644 --- a/tests/test_builder/test_build_notification.py +++ b/tests/test_builder/test_build_notification.py @@ -59,6 +59,7 @@ class MyClientSession(koji.ClientSession): class TestBuildNotification(unittest.TestCase): def setUp(self): + self.maxDiff = None self.original_timezone = os.environ.get('TZ') os.environ['TZ'] = 'US/Eastern' time.tzset() @@ -111,5 +112,5 @@ class TestBuildNotification(unittest.TestCase): msg_expect = fp.read() if six.PY2: msg_expect = msg_expect.decode() - self.assertEqual(message, msg_expect) + self.assertMultiLineEqual(message.decode(), msg_expect.decode()) locale.resetlocale() diff --git a/tests/test_cli/test_taskinfo.py b/tests/test_cli/test_taskinfo.py index f7afeb7..1c7ca6b 100644 --- a/tests/test_cli/test_taskinfo.py +++ b/tests/test_cli/test_taskinfo.py @@ -218,7 +218,7 @@ class TestParseTaskParams(utils.CliTestCase): self.__run_parseTask_test('prepRepo', params, expect) def test_createRepo(self): - params = [1, 'x86_64', {'id': 1, 'creation_time': '1970-1-1 0:0:0'}, + params = [1, 'x86_64', {'id': 1, 'creation_ts': 0}, [{'external_repo_name': 'fedoraproject.net'}, {'external_repo_name': 'centos.org'}]] expect = ["Repo ID: %i" % params[0]] From dc27981da6b77c3fbaad9f4d93333510cee52d4d Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Aug 25 2020 12:36:11 +0000 Subject: [PATCH 5/6] fix create_ts for repos --- diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index 8c44631..09eaae9 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -4729,7 +4729,7 @@ def _do_parseTaskParams(session, method, task_id, topdir): oldrepo = params[2] if oldrepo: lines.append("Old Repo ID: %i" % oldrepo['id']) - lines.append("Old Repo Creation: %s" % koji.formatTimeLong(oldrepo['creation_ts'])) + lines.append("Old Repo Creation: %s" % koji.formatTimeLong(oldrepo['create_ts'])) if len(params) > 3: lines.append("External Repos: %s" % ', '.join([ext['external_repo_name'] for ext in params[3]])) diff --git a/www/kojiweb/repoinfo.chtml b/www/kojiweb/repoinfo.chtml index b735875..9d8a4e0 100644 --- a/www/kojiweb/repoinfo.chtml +++ b/www/kojiweb/repoinfo.chtml @@ -11,7 +11,7 @@ Tag$repo.tag_name #set $state = $util.repoState($repo.state) State$state - Event$repo.create_event ($util.formatTimeLong($repo.creation_ts)) + Event$repo.create_event ($util.formatTimeLong($repo.create_ts)) #if $repo.state != koji.REPO_STATES['DELETED'] URLrepodata Repo jsonrepo.json diff --git a/www/kojiweb/taginfo.chtml b/www/kojiweb/taginfo.chtml index 6c1f9b7..3acb8ea 100644 --- a/www/kojiweb/taginfo.chtml +++ b/www/kojiweb/taginfo.chtml @@ -115,7 +115,7 @@ Repo created #if $repo - $util.formatTimeRSS($repo.creation_ts) + $util.formatTimeRSS($repo.create_ts) #end if diff --git a/www/kojiweb/taskinfo_params.chtml b/www/kojiweb/taskinfo_params.chtml index f59a96c..e98563b 100644 --- a/www/kojiweb/taskinfo_params.chtml +++ b/www/kojiweb/taskinfo_params.chtml @@ -195,7 +195,7 @@ $printOpts($params[3]) #set $oldrepo = $params[2] #if $oldrepo Old Repo ID: $oldrepo.id
- Old Repo Creation: $koji.formatTimeLong($oldrepo.creation_ts)
+ Old Repo Creation: $koji.formatTimeLong($oldrepo.create_ts)
#end if #if $len($params) > 4 and $params[4] External Repos: $printValue(None, [ext['external_repo_name'] for ext in $params[3]])
From 31e70dcb33ccc610cc161de453111f54e7fddc0b Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Aug 25 2020 12:59:18 +0000 Subject: [PATCH 6/6] fix test --- diff --git a/tests/test_cli/test_taskinfo.py b/tests/test_cli/test_taskinfo.py index 1c7ca6b..08bb3ca 100644 --- a/tests/test_cli/test_taskinfo.py +++ b/tests/test_cli/test_taskinfo.py @@ -218,7 +218,7 @@ class TestParseTaskParams(utils.CliTestCase): self.__run_parseTask_test('prepRepo', params, expect) def test_createRepo(self): - params = [1, 'x86_64', {'id': 1, 'creation_ts': 0}, + params = [1, 'x86_64', {'id': 1, 'create_ts': 0}, [{'external_repo_name': 'fedoraproject.net'}, {'external_repo_name': 'centos.org'}]] expect = ["Repo ID: %i" % params[0]]