From 59a5425597052f439a782fbbca35cd67ca55c81b Mon Sep 17 00:00:00 2001 From: Philip Wellnitz Date: Tue, 26 May 2020 20:08:44 +0900 Subject: [PATCH] Add timezone argument to date and fuzzy_time segments (#2097) * add basic timezone support --- powerline/segments/common/time.py | 117 ++++++++++++++++++----------- tests/test_python/test_segments.py | 44 +++++++++-- 2 files changed, 109 insertions(+), 52 deletions(-) diff --git a/powerline/segments/common/time.py b/powerline/segments/common/time.py index 1e2207b3..be727c9e 100644 --- a/powerline/segments/common/time.py +++ b/powerline/segments/common/time.py @@ -4,22 +4,33 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct from datetime import datetime -def date(pl, format='%Y-%m-%d', istime=False): +def date(pl, format='%Y-%m-%d', istime=False, timezone=None): '''Return the current date. :param str format: strftime-style date format string :param bool istime: If true then segment uses ``time`` highlight group. + :param string timezone: + Specify a timezone to use as ``+HHMM`` or ``-HHMM``. + (Defaults to system defaults.) Divider highlight group used: ``time:divider``. Highlight groups used: ``time`` or ``date``. ''' + try: - contents = datetime.now().strftime(format) + tz = datetime.strptime(timezone, '%z').tzinfo if timezone else None + except ValueError: + tz = None + + nw = datetime.now(tz) + + try: + contents = nw.strftime(format) except UnicodeEncodeError: - contents = datetime.now().strftime(format.encode('utf-8')).decode('utf-8') + contents = nw.strftime(format.encode('utf-8')).decode('utf-8') return [{ 'contents': contents, @@ -34,59 +45,77 @@ UNICODE_TEXT_TRANSLATION = { } -def fuzzy_time(pl, unicode_text=False): +def fuzzy_time(pl, format='{minute_str} {hour_str}', unicode_text=False, timezone=None, hour_str=['twelve', 'one', 'two', 'three', 'four', + 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven'], minute_str = { + '0': 'o\'clock', '5': 'five past', '10': 'ten past','15': 'quarter past', + '20': 'twenty past', '25': 'twenty-five past', '30': 'half past', '35': 'twenty-five to', + '40': 'twenty to', '45': 'quarter to', '50': 'ten to', '55': 'five to' + }, special_case_str = { + '(23, 58)': 'round about midnight', + '(23, 59)': 'round about midnight', + '(0, 0)': 'midnight', + '(0, 1)': 'round about midnight', + '(0, 2)': 'round about midnight', + '(12, 0)': 'noon', + }): + '''Display the current time as fuzzy time, e.g. "quarter past six". + :param string format: + Format used to display the fuzzy time. (Ignored when a special time + is displayed.) :param bool unicode_text: - If true then hyphenminuses (regular ASCII ``-``) and single quotes are + If true then hyphenminuses (regular ASCII ``-``) and single quotes are replaced with unicode dashes and apostrophes. - ''' - hour_str = ['twelve', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven'] - minute_str = { - 5: 'five past', - 10: 'ten past', - 15: 'quarter past', - 20: 'twenty past', - 25: 'twenty-five past', - 30: 'half past', - 35: 'twenty-five to', - 40: 'twenty to', - 45: 'quarter to', - 50: 'ten to', - 55: 'five to', - } - special_case_str = { - (23, 58): 'round about midnight', - (23, 59): 'round about midnight', - (0, 0): 'midnight', - (0, 1): 'round about midnight', - (0, 2): 'round about midnight', - (12, 0): 'noon', - } + :param string timezone: + Specify a timezone to use as ``+HHMM`` or ``-HHMM``. + (Defaults to system defaults.) + :param string list hour_str: + Strings to be used to display the hour, starting with midnight. + (This list may contain 12 or 24 entries.) + :param dict minute_str: + Dictionary mapping minutes to strings to be used to display them. + :param dict special_case_str: + Special strings for special times. - now = datetime.now() + Highlight groups used: ``fuzzy_time``. + ''' try: - return special_case_str[(now.hour, now.minute)] + tz = datetime.strptime(timezone, '%z').tzinfo if timezone else None + except ValueError: + tz = None + + now = datetime.now(tz) + + try: + # We don't want to enforce a special type of spaces/ alignment in the input + from ast import literal_eval + special_case_str = {literal_eval(x):special_case_str[x] for x in special_case_str} + result = special_case_str[(now.hour, now.minute)] + if unicode_text: + result = result.translate(UNICODE_TEXT_TRANSLATION) + return result except KeyError: pass hour = now.hour - if now.minute > 32: - if hour == 23: - hour = 0 - else: - hour += 1 - if hour > 11: - hour = hour - 12 - hour = hour_str[hour] + if now.minute >= 30: + hour = hour + 1 + hour = hour % len(hour_str) - minute = int(round(now.minute / 5.0) * 5) - if minute == 60 or minute == 0: - result = ' '.join([hour, 'o\'clock']) - else: - minute = minute_str[minute] - result = ' '.join([minute, hour]) + min_dis = 100 + min_pos = 0 + + for mn in minute_str: + mn = int(mn) + if now.minute >= mn and now.minute - mn < min_dis: + min_dis = now.minute - mn + min_pos = mn + elif now.minute < mn and mn - now.minute < min_dis: + min_dis = mn - now.minute + min_pos = mn + result = format.format(minute_str=minute_str[str(min_pos)], hour_str=hour_str[hour]) if unicode_text: result = result.translate(UNICODE_TEXT_TRANSLATION) diff --git a/tests/test_python/test_segments.py b/tests/test_python/test_segments.py index 8d6cbae1..d8bd2e80 100644 --- a/tests/test_python/test_segments.py +++ b/tests/test_python/test_segments.py @@ -819,9 +819,11 @@ class TestTime(TestCommon): def test_date(self): pl = Pl() - with replace_attr(self.module, 'datetime', Args(now=lambda: Args(strftime=lambda fmt: fmt))): + with replace_attr(self.module, 'datetime', Args(strptime=lambda timezone, fmt: Args(tzinfo=timezone), now=lambda tz:Args(strftime=lambda fmt: fmt + (tz if tz else '')))): self.assertEqual(self.module.date(pl=pl), [{'contents': '%Y-%m-%d', 'highlight_groups': ['date'], 'divider_highlight_group': None}]) + self.assertEqual(self.module.date(pl=pl, timezone='+0900'), [{'contents': '%Y-%m-%d+0900', 'highlight_groups': ['date'], 'divider_highlight_group': None}]) self.assertEqual(self.module.date(pl=pl, format='%H:%M', istime=True), [{'contents': '%H:%M', 'highlight_groups': ['time', 'date'], 'divider_highlight_group': 'time:divider'}]) + self.assertEqual(self.module.date(pl=pl, format='%H:%M', istime=True, timezone='-0900'), [{'contents': '%H:%M-0900', 'highlight_groups': ['time', 'date'], 'divider_highlight_group': 'time:divider'}]) unicode_date = self.module.date(pl=pl, format='\u231a', istime=True) expected_unicode_date = [{'contents': '\u231a', 'highlight_groups': ['time', 'date'], 'divider_highlight_group': 'time:divider'}] if python_implementation() == 'PyPy' and sys.version_info >= (3,): @@ -832,23 +834,49 @@ class TestTime(TestCommon): def test_fuzzy_time(self): time = Args(hour=0, minute=45) pl = Pl() - with replace_attr(self.module, 'datetime', Args(now=lambda: time)): + hour_str = ['12', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven'] + minute_str = {'0': 'o\'clock', '5': 'five past', '10': 'ten past','15': + 'quarter past','20': 'twenty past', '25': 'twenty-five past', + '30': 'half past', '35': 'twenty-five to','40': 'twenty to', '45': + 'quarter to', '50': 'ten to', '55': 'five to'} + special_case_str = { + '(23, 58)': '~ midnight', + '(23, 59)': '~ midnight', + '(0, 0)': 'midnight', + '(0, 1)': '~ midnight', + '(0, 2)': '~ midnight', + '(12, 0)': 'twelve o\'clock'} + with replace_attr(self.module, 'datetime', Args(strptime=lambda timezone, fmt: Args(tzinfo=timezone), now=lambda tz: time)): + self.assertEqual(self.module.fuzzy_time(pl=pl, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), 'quarter to one') self.assertEqual(self.module.fuzzy_time(pl=pl), 'quarter to one') time.hour = 23 time.minute = 59 + self.assertEqual(self.module.fuzzy_time(pl=pl, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), '~ midnight') self.assertEqual(self.module.fuzzy_time(pl=pl), 'round about midnight') + time.hour = 11 time.minute = 33 + self.assertEqual(self.module.fuzzy_time(pl=pl, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str),'twenty-five to 12') self.assertEqual(self.module.fuzzy_time(pl=pl), 'twenty-five to twelve') - time.minute = 60 - self.assertEqual(self.module.fuzzy_time(pl=pl), 'twelve o\'clock') + time.hour = 12 + time.minute = 0 + self.assertEqual(self.module.fuzzy_time(pl=pl, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), 'twelve o\'clock') + self.assertEqual(self.module.fuzzy_time(pl=pl), 'noon') + time.hour = 11 time.minute = 33 + self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), 'twenty-five to 12') self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False), 'twenty-five to twelve') - time.minute = 60 - self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False), 'twelve o\'clock') + time.hour = 12 + time.minute = 0 + self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), 'twelve o\'clock') + self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False), 'noon') + time.hour = 11 time.minute = 33 + self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), 'twenty‐five to 12') self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True), 'twenty‐five to twelve') - time.minute = 60 - self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True), 'twelve o’clock') + time.hour = 12 + time.minute = 0 + self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), 'twelve o’clock') + self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True),'noon') class TestSys(TestCommon):