From c21303065a653e58df8d38183100f9588ab91b58 Mon Sep 17 00:00:00 2001 From: Ben Fitzpatrick Date: Thu, 1 May 2014 17:41:40 +0100 Subject: [PATCH 1/5] #27: parsing: assume local time zone by default --- isodatetime/parsers.py | 22 +++++++++++++++++----- isodatetime/tests.py | 3 ++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/isodatetime/parsers.py b/isodatetime/parsers.py index 1d279fc..716217e 100644 --- a/isodatetime/parsers.py +++ b/isodatetime/parsers.py @@ -24,6 +24,7 @@ from . import data from . import dumpers from . import parser_spec +from . import timezone class ISO8601SyntaxError(ValueError): @@ -127,8 +128,15 @@ class TimePointParser(object): is not allowed, and must be written as "20000102T011402". assume_utc (default False) specifies that dates and times without - timezone information should be assumed UTC (Z). Otherwise, these - will be converted to the local timezone. + time zone information should be assumed UTC (Z). If assume_utc and + assume_unknown_time_zone are both False, the local time zone will + be used. If they are both True, assume_utc will take precedence. + + assume_unknown_time_zone (default False) specifies that dates and + times without time zone information should be left with an unknown + time zone setting, unless assume_utc is True. If assume_utc and + assume_unknown_time_zone are both False, the local time zone will + be used. If they are both True, assume_utc will take precedence. dump_format (default None) specifies a default custom dump format string for TimePoint instances. See data.TimePoint documentation @@ -140,11 +148,13 @@ def __init__(self, num_expanded_year_digits=2, allow_truncated=False, allow_only_basic=False, assume_utc=False, + assume_unknown_time_zone=False, dump_format=None): self.expanded_year_digits = num_expanded_year_digits self.allow_truncated = allow_truncated self.allow_only_basic = allow_only_basic self.assume_utc = assume_utc + self.assume_unknown_time_zone = assume_unknown_time_zone self.dump_format = dump_format self._generate_regexes() @@ -474,9 +484,6 @@ def get_info(self, timepoint_string): timezone_expr = "" timezone_info = ( self.process_timezone_info(timezone_info)) - if self.assume_utc: - timezone_info["time_zone_hour"] = 0 - timezone_info["time_zone_minute"] = 0 else: timezone_expr, timezone_info = self.get_timezone_info( timezone, @@ -496,6 +503,11 @@ def process_timezone_info(self, timezone_info): if self.assume_utc: timezone_info["time_zone_hour"] = 0 timezone_info["time_zone_minute"] = 0 + elif not self.assume_unknown_time_zone: + utc_hour_offset, utc_minute_offset = ( + timezone.get_timezone_for_locale()) + timezone_info["time_zone_hour"] = utc_hour_offset + timezone_info["time_zone_minute"] = utc_minute_offset return timezone_info if timezone_info.pop("time_zone_sign", "+") == "-": timezone_info["time_zone_hour"] = ( diff --git a/isodatetime/tests.py b/isodatetime/tests.py index 172184d..1a315c8 100644 --- a/isodatetime/tests.py +++ b/isodatetime/tests.py @@ -804,7 +804,8 @@ def test_timepoint_dumper(self): def test_timepoint_parser(self): """Test the parsing of date/time expressions.""" - parser = parsers.TimePointParser(allow_truncated=True) + parser = parsers.TimePointParser(allow_truncated=True, + assume_unknown_time_zone=True) for expression, timepoint_kwargs in get_timepointparser_tests( allow_truncated=True): timepoint_kwargs = copy.deepcopy(timepoint_kwargs) From fc221bd35ad165af8391e23a71bd9fdb2f9a23a2 Mon Sep 17 00:00:00 2001 From: Ben Fitzpatrick Date: Tue, 6 May 2014 17:59:57 +0100 Subject: [PATCH 2/5] #27: add and fix tests --- isodatetime/data.py | 3 ++ isodatetime/parsers.py | 3 ++ isodatetime/tests.py | 64 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/isodatetime/data.py b/isodatetime/data.py index cde3c01..5646000 100644 --- a/isodatetime/data.py +++ b/isodatetime/data.py @@ -501,6 +501,9 @@ def __str__(self): time_string = "-%02d:%02d" return time_string % (abs(self.hours), abs(self.minutes)) + def __repr__(self): + return "" + class TimePoint(object): diff --git a/isodatetime/parsers.py b/isodatetime/parsers.py index 716217e..44a7a44 100644 --- a/isodatetime/parsers.py +++ b/isodatetime/parsers.py @@ -433,6 +433,9 @@ def get_info(self, timepoint_string): format_key, type_key, date_expr = keys parsed_expr += date_expr time_info = {} + timezone_info = ( + self.process_timezone_info({})) + time_info.update(timezone_info) else: date, time_timezone = date_time_timezone if not date and self.allow_truncated: diff --git a/isodatetime/tests.py b/isodatetime/tests.py index 1a315c8..76d1efc 100644 --- a/isodatetime/tests.py +++ b/isodatetime/tests.py @@ -159,7 +159,8 @@ def get_timepoint_dumper_tests(): def get_timepointparser_tests(allow_only_basic=False, - allow_truncated=False): + allow_truncated=False, + skip_time_zones=False): """Yield tests for the time point parser.""" # Note: test dates assume 2 expanded year digits. test_date_map = { @@ -458,6 +459,8 @@ def get_timepointparser_tests(allow_only_basic=False, for key, value in info.items() + time_info.items(): combo_info[key] = value yield combo_expr, combo_info + if skip_time_zones: + continue timezone_items = timezone_format_tests.items() for timezone_expr, timezone_info in timezone_items: tz_expr = combo_expr + timezone_expr @@ -480,6 +483,8 @@ def get_timepointparser_tests(allow_only_basic=False, for key, value in time_info.items(): combo_info[key] = value yield combo_expr, combo_info + if skip_time_zones: + continue timezone_items = timezone_format_tests.items() for timezone_expr, timezone_info in timezone_items: tz_expr = combo_expr + timezone_expr @@ -608,6 +613,17 @@ def get_timerecurrenceparser_tests(): "end_point": end_point} +def get_locale_time_zone_hours_minutes(): + """Provide an independent method of getting the local timezone.""" + import datetime + utc_offset = datetime.datetime.now() - datetime.datetime.utcnow() + utc_offset_hrs = (utc_offset.seconds + 1800) // 3600 + utc_offset_minutes = ( + ((utc_offset.seconds - 3600 * utc_offset_hrs) + 30) // 60 + ) + return utc_offset_hrs, utc_offset_minutes + + class TestSuite(unittest.TestCase): """Test the functionality of parsers and data model manipulation.""" @@ -711,14 +727,11 @@ def test_timepoint(self): def test_timepoint_timezone(self): """Test the timezone handling of timepoint instances.""" - import datetime year = 2000 month_of_year = 1 day_of_month = 1 - utc_offset = datetime.datetime.now() - datetime.datetime.utcnow() - utc_offset_hrs = (utc_offset.seconds + 1800) // 3600 - utc_offset_minutes = ( - ((utc_offset.seconds - 3600 * utc_offset_hrs) + 30) // 60 + utc_offset_hrs, utc_offset_minutes = ( + get_locale_time_zone_hours_minutes() ) for hour_of_day in range(24): for minute_of_hour in [0, 30]: @@ -782,7 +795,8 @@ def test_timepoint_timezone(self): def test_timepoint_dumper(self): """Test the dumping of TimePoint instances.""" - parser = parsers.TimePointParser(allow_truncated=True) + parser = parsers.TimePointParser(allow_truncated=True, + assume_unknown_time_zone=True) dumper = dumpers.TimePointDumper() for expression, timepoint_kwargs in get_timepointparser_tests( allow_truncated=True): @@ -820,6 +834,42 @@ def test_timepoint_parser(self): test_data = str(parser.parse(expression, dump_as_parsed=True)) self.assertEqual(test_data, ctrl_data, expression) + # Test local time zone assumptions. + utc_offset_hrs, utc_offset_minutes = ( + get_locale_time_zone_hours_minutes() + ) + parser = parsers.TimePointParser(allow_truncated=True) + for expression, timepoint_kwargs in get_timepointparser_tests( + allow_truncated=True, skip_time_zones=True): + timepoint_kwargs = copy.deepcopy(timepoint_kwargs) + try: + test_timepoint = parser.parse(expression) + except parsers.ISO8601SyntaxError as syn_exc: + raise ValueError("Parsing failed for {0}: {1}".format( + expression, syn_exc)) + test_data = (test_timepoint.time_zone.hours, + test_timepoint.time_zone.minutes) + ctrl_data = (utc_offset_hrs, utc_offset_minutes) + self.assertEqual(test_data, ctrl_data, + "Local timezone for " + expression) + + # Test UTC time zone assumptions. + parser = parsers.TimePointParser(allow_truncated=True, + assume_utc=True) + for expression, timepoint_kwargs in get_timepointparser_tests( + allow_truncated=True, skip_time_zones=True): + timepoint_kwargs = copy.deepcopy(timepoint_kwargs) + try: + test_timepoint = parser.parse(expression) + except parsers.ISO8601SyntaxError as syn_exc: + raise ValueError("Parsing failed for {0}: {1}".format( + expression, syn_exc)) + test_data = (test_timepoint.time_zone.hours, + test_timepoint.time_zone.minutes) + ctrl_data = (0, 0) + self.assertEqual(test_data, ctrl_data, + "UTC for " + expression) + def test_timepoint_strftime_strptime(self): """Test the strftime/strptime for date/time expressions.""" import datetime From a6cba7cd60b98ebfdd2183629a578d4ad22f0c9b Mon Sep 17 00:00:00 2001 From: Ben Fitzpatrick Date: Wed, 7 May 2014 13:06:58 +0100 Subject: [PATCH 3/5] #27: refactor time zone assumption API --- isodatetime/dumpers.py | 6 ++--- isodatetime/parsers.py | 50 +++++++++++++++++++++--------------- isodatetime/tests.py | 57 ++++++++++++++++++++++++++++++++--------- isodatetime/timezone.py | 5 +++- 4 files changed, 82 insertions(+), 36 deletions(-) diff --git a/isodatetime/dumpers.py b/isodatetime/dumpers.py index 9db2be1..480e564 100644 --- a/isodatetime/dumpers.py +++ b/isodatetime/dumpers.py @@ -164,11 +164,11 @@ def _get_expression_and_properties(self, formatting_string): elif "+" in time_string: time_string, time_zone_string = time_string.split("+") time_zone_string = "+" + time_zone_string - custom_time_zone = self._get_time_zone(time_zone_string) + custom_time_zone = self.get_time_zone(time_zone_string) elif "-" in time_string.lstrip("-"): time_string, time_zone_string = time_string.split("-") time_zone_string = "-" + time_zone_string - custom_time_zone = self._get_time_zone(time_zone_string) + custom_time_zone = self.get_time_zone(time_zone_string) point_prop_list = [] string_map = {"date": "", "time": "", "time_zone": ""} for string, key in [(date_string, "date"), @@ -187,7 +187,7 @@ def _get_expression_and_properties(self, formatting_string): return expression, tuple(point_prop_list), custom_time_zone @util.cache_results - def _get_time_zone(self, time_zone_string): + def get_time_zone(self, time_zone_string): from . import parsers if not hasattr(self, "_timepoint_parser"): self._timepoint_parser = parsers.TimePointParser() diff --git a/isodatetime/parsers.py b/isodatetime/parsers.py index 44a7a44..77949bb 100644 --- a/isodatetime/parsers.py +++ b/isodatetime/parsers.py @@ -127,16 +127,17 @@ class TimePointParser(object): extraneous punctuation). This means that "2000-01-02T01:14:02" is not allowed, and must be written as "20000102T011402". - assume_utc (default False) specifies that dates and times without - time zone information should be assumed UTC (Z). If assume_utc and - assume_unknown_time_zone are both False, the local time zone will - be used. If they are both True, assume_utc will take precedence. - - assume_unknown_time_zone (default False) specifies that dates and - times without time zone information should be left with an unknown - time zone setting, unless assume_utc is True. If assume_utc and - assume_unknown_time_zone are both False, the local time zone will - be used. If they are both True, assume_utc will take precedence. + assumed_time_zone (default None) is a tuple of hours (integer) + and minutes (integer) that specifies that dates and times + without time zone information should be set to have a time zone + whose offset from UTC is the (hours, minutes) information in this + variable. To assume UTC, set this to (0, 0). + + no_assume_local_time_zone (default False) specifies that dates and + times without time zone information (in the absence of + assumed_time_zone_hours_minutes), should not be assumed to be + in the local time zone. They would then be left with an unknown + time zone setting. dump_format (default None) specifies a default custom dump format string for TimePoint instances. See data.TimePoint documentation @@ -147,14 +148,14 @@ class TimePointParser(object): def __init__(self, num_expanded_year_digits=2, allow_truncated=False, allow_only_basic=False, - assume_utc=False, - assume_unknown_time_zone=False, + assumed_time_zone=None, + no_assume_local_time_zone=False, dump_format=None): self.expanded_year_digits = num_expanded_year_digits self.allow_truncated = allow_truncated self.allow_only_basic = allow_only_basic - self.assume_utc = assume_utc - self.assume_unknown_time_zone = assume_unknown_time_zone + self.assumed_time_zone = assumed_time_zone + self.no_assume_local_time_zone = no_assume_local_time_zone self.dump_format = dump_format self._generate_regexes() @@ -503,15 +504,24 @@ def get_info(self, timepoint_string): def process_timezone_info(self, timezone_info): if not timezone_info: - if self.assume_utc: - timezone_info["time_zone_hour"] = 0 - timezone_info["time_zone_minute"] = 0 - elif not self.assume_unknown_time_zone: + # There is no time zone information specified. + if self.assumed_time_zone is None: + # No given value to assume. + if self.no_assume_local_time_zone: + # Return no time zone information. + return timezone_info + # Set the time zone to the current local time zone. utc_hour_offset, utc_minute_offset = ( timezone.get_timezone_for_locale()) timezone_info["time_zone_hour"] = utc_hour_offset timezone_info["time_zone_minute"] = utc_minute_offset - return timezone_info + return timezone_info + else: + # Set the time zone to a given value. + utc_hour_offset, utc_minute_offset = self.assumed_time_zone + timezone_info["time_zone_hour"] = utc_hour_offset + timezone_info["time_zone_minute"] = utc_minute_offset + return timezone_info if timezone_info.pop("time_zone_sign", "+") == "-": timezone_info["time_zone_hour"] = ( -int(timezone_info["time_zone_hour"])) @@ -561,7 +571,7 @@ def parse(self, expression): try: timepoint = parse_timepoint_expression( expression[1:], allow_truncated=False, - assume_utc=True + assumed_time_zone=(0, 0) ) except ISO8601SyntaxError: raise diff --git a/isodatetime/tests.py b/isodatetime/tests.py index 76d1efc..0b9e441 100644 --- a/isodatetime/tests.py +++ b/isodatetime/tests.py @@ -617,11 +617,11 @@ def get_locale_time_zone_hours_minutes(): """Provide an independent method of getting the local timezone.""" import datetime utc_offset = datetime.datetime.now() - datetime.datetime.utcnow() - utc_offset_hrs = (utc_offset.seconds + 1800) // 3600 + utc_offset_hours = (utc_offset.seconds + 1800) // 3600 utc_offset_minutes = ( - ((utc_offset.seconds - 3600 * utc_offset_hrs) + 30) // 60 + ((utc_offset.seconds - 3600 * utc_offset_hours) + 30) // 60 ) - return utc_offset_hrs, utc_offset_minutes + return utc_offset_hours, utc_offset_minutes class TestSuite(unittest.TestCase): @@ -730,7 +730,7 @@ def test_timepoint_timezone(self): year = 2000 month_of_year = 1 day_of_month = 1 - utc_offset_hrs, utc_offset_minutes = ( + utc_offset_hours, utc_offset_minutes = ( get_locale_time_zone_hours_minutes() ) for hour_of_day in range(24): @@ -754,7 +754,7 @@ def test_timepoint_timezone(self): test_dates[0]) test_dates[1].set_time_zone_to_local() self.assertEqual(test_dates[1].time_zone.hours, - utc_offset_hrs, test_dates[1]) + utc_offset_hours, test_dates[1]) self.assertEqual(test_dates[1].time_zone.minutes, utc_offset_minutes, test_dates[1]) @@ -796,7 +796,7 @@ def test_timepoint_timezone(self): def test_timepoint_dumper(self): """Test the dumping of TimePoint instances.""" parser = parsers.TimePointParser(allow_truncated=True, - assume_unknown_time_zone=True) + no_assume_local_time_zone=True) dumper = dumpers.TimePointDumper() for expression, timepoint_kwargs in get_timepointparser_tests( allow_truncated=True): @@ -818,8 +818,10 @@ def test_timepoint_dumper(self): def test_timepoint_parser(self): """Test the parsing of date/time expressions.""" + + # Test unknown time zone assumptions. parser = parsers.TimePointParser(allow_truncated=True, - assume_unknown_time_zone=True) + no_assume_local_time_zone=True) for expression, timepoint_kwargs in get_timepointparser_tests( allow_truncated=True): timepoint_kwargs = copy.deepcopy(timepoint_kwargs) @@ -834,8 +836,8 @@ def test_timepoint_parser(self): test_data = str(parser.parse(expression, dump_as_parsed=True)) self.assertEqual(test_data, ctrl_data, expression) - # Test local time zone assumptions. - utc_offset_hrs, utc_offset_minutes = ( + # Test local time zone assumptions (the default). + utc_offset_hours, utc_offset_minutes = ( get_locale_time_zone_hours_minutes() ) parser = parsers.TimePointParser(allow_truncated=True) @@ -849,13 +851,44 @@ def test_timepoint_parser(self): expression, syn_exc)) test_data = (test_timepoint.time_zone.hours, test_timepoint.time_zone.minutes) - ctrl_data = (utc_offset_hrs, utc_offset_minutes) + ctrl_data = (utc_offset_hours, utc_offset_minutes) self.assertEqual(test_data, ctrl_data, "Local timezone for " + expression) + # Test given time zone assumptions. + utc_offset_hours, utc_offset_minutes = ( + get_locale_time_zone_hours_minutes() + ) + given_utc_offset_hours = -2 # This is an arbitrary number! + if given_utc_offset_hours == utc_offset_hours: + # No point testing this twice, change it. + given_utc_offset_hours = -3 + given_utc_offset_minutes = -15 + given_time_zone_hours_minutes = ( + given_utc_offset_hours, given_utc_offset_minutes) + parser = parsers.TimePointParser( + allow_truncated=True, + assumed_time_zone=given_time_zone_hours_minutes + ) + for expression, timepoint_kwargs in get_timepointparser_tests( + allow_truncated=True, skip_time_zones=True): + timepoint_kwargs = copy.deepcopy(timepoint_kwargs) + try: + test_timepoint = parser.parse(expression) + except parsers.ISO8601SyntaxError as syn_exc: + raise ValueError("Parsing failed for {0}: {1}".format( + expression, syn_exc)) + test_data = (test_timepoint.time_zone.hours, + test_timepoint.time_zone.minutes) + ctrl_data = given_time_zone_hours_minutes + self.assertEqual(test_data, ctrl_data, + "A given timezone for " + expression) + # Test UTC time zone assumptions. - parser = parsers.TimePointParser(allow_truncated=True, - assume_utc=True) + parser = parsers.TimePointParser( + allow_truncated=True, + assumed_time_zone=(0, 0) + ) for expression, timepoint_kwargs in get_timepointparser_tests( allow_truncated=True, skip_time_zones=True): timepoint_kwargs = copy.deepcopy(timepoint_kwargs) diff --git a/isodatetime/timezone.py b/isodatetime/timezone.py index 5419654..08ad06c 100644 --- a/isodatetime/timezone.py +++ b/isodatetime/timezone.py @@ -31,14 +31,17 @@ def get_timezone_for_locale(): return utc_offset_hours, utc_offset_minutes -def get_timezone_format_for_locale(extended_mode=False): +def get_timezone_format_for_locale(extended_mode=False, reduced_mode=False): """Return the timezone format string for this locale (e.g. '+0300').""" utc_offset_hours, utc_offset_minutes = get_timezone_for_locale() if utc_offset_hours == 0 and utc_offset_minutes == 0: return "Z" + reduced_timezone_template = "%s%02d" timezone_template = "%s%02d%02d" if extended_mode: timezone_template = "%s%02d:%02d" sign = "-" if (utc_offset_hours < 0 or utc_offset_minutes < 0) else "+" + if reduced_mode and utc_offset_minutes == 0: + return reduced_timezone_template % (sign, abs(utc_offset_hours)) return timezone_template % ( sign, abs(utc_offset_hours), abs(utc_offset_minutes)) From f26c063c6cd7a5053611e474ab572d9cf52f326b Mon Sep 17 00:00:00 2001 From: Ben Fitzpatrick Date: Wed, 7 May 2014 14:49:51 +0100 Subject: [PATCH 4/5] #27: fix get_time_zone dumper for 'Z' --- isodatetime/dumpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/isodatetime/dumpers.py b/isodatetime/dumpers.py index 480e564..e2e345c 100644 --- a/isodatetime/dumpers.py +++ b/isodatetime/dumpers.py @@ -197,6 +197,8 @@ def get_time_zone(self, time_zone_string): except parsers.ISO8601SyntaxError as e: return None info = self._timepoint_parser.process_timezone_info(info) + if info.get('time_zone_utc'): + return (0, 0) if "time_zone_hour" not in info and "time_zone_minute" not in info: return None return info.get("time_zone_hour", 0), info.get("time_zone_minute", 0) From 23efa018b2a1e43d45796049e1c007ee67156db0 Mon Sep 17 00:00:00 2001 From: benfitzpatrick Date: Mon, 12 May 2014 10:33:07 +0100 Subject: [PATCH 5/5] #27: implement feedback and tidy up --- isodatetime/data.py | 8 +- isodatetime/dumpers.py | 10 +-- isodatetime/parser_spec.py | 26 +++---- isodatetime/parsers.py | 156 +++++++++++++++++++------------------ isodatetime/tests.py | 45 +++++------ isodatetime/timezone.py | 12 +-- 6 files changed, 130 insertions(+), 127 deletions(-) diff --git a/isodatetime/data.py b/isodatetime/data.py index 5646000..d82a840 100644 --- a/isodatetime/data.py +++ b/isodatetime/data.py @@ -563,11 +563,11 @@ class TimePoint(object): for leap seconds at 60 yet) second_of_minute_decimal - a float between 0 and 1, if using decimal accuracy for seconds. - time_zone_hour - (default 0) an integer denoting the hour timezone + time_zone_hour - (default 0) an integer denoting the hour time zone offset from UTC. Note that unless this is a truncated representation, 0 will be assumed if this is not provided. time_zone_minute - (default 0) an integer between 0 and 59 denoting - the minute component of the timezone offset from UTC. + the minute component of the time zone offset from UTC. dump_format - a custom format string to control the stringification of the timepoint. See isodatetime.parser_spec for more details. truncated - (default False) a boolean denoting whether the @@ -919,8 +919,8 @@ def set_time_zone(self, dest_time_zone): self.time_zone = dest_time_zone def set_time_zone_to_local(self): - """Set the time zone to the local timezone, if it's not already.""" - local_hours, local_minutes = timezone.get_timezone_for_locale() + """Set the time zone to the local time zone, if it's not already.""" + local_hours, local_minutes = timezone.get_local_time_zone() self.set_time_zone(TimeZone(hours=local_hours, minutes=local_minutes)) def set_time_zone_to_utc(self): diff --git a/isodatetime/dumpers.py b/isodatetime/dumpers.py index e2e345c..0041791 100644 --- a/isodatetime/dumpers.py +++ b/isodatetime/dumpers.py @@ -35,7 +35,7 @@ class TimePointDumper(object): of valid patterns is found in the parser_spec module, with lots of examples (coincidentally, used to generate the parsing). Anything not matched will get left as it is in the string. - Specifying a particular timezone will result in a timezone + Specifying a particular time zone will result in a time zone conversion of the date/time information before it is output. For example, the following formatting_string @@ -47,7 +47,7 @@ class TimePointDumper(object): T - left alone, date/time separator hh - hour of day information, e.g. 06 mm - minute of hour information, e.g. 58 - Z - Zulu or UTC zero-offset timezone, left in, forces timezone + Z - Zulu or UTC zero-offset time zone, left in, forces time zone conversion and might dump a TimePoint instance like this: '19850531T0658Z'. @@ -66,7 +66,7 @@ def __init__(self, num_expanded_year_digits=2): num_expanded_year_digits), "date"), (parser_spec.get_time_translate_info(), "time"), - (parser_spec.get_timezone_translate_info(), "time_zone")]: + (parser_spec.get_time_zone_translate_info(), "time_zone")]: for regex, regex_sub, format_sub, prop_name in info: rec = re.compile(regex) self._rec_formats[key].append((rec, format_sub, prop_name)) @@ -193,10 +193,10 @@ def get_time_zone(self, time_zone_string): self._timepoint_parser = parsers.TimePointParser() try: (expr, info) = ( - self._timepoint_parser.get_timezone_info(time_zone_string)) + self._timepoint_parser.get_time_zone_info(time_zone_string)) except parsers.ISO8601SyntaxError as e: return None - info = self._timepoint_parser.process_timezone_info(info) + info = self._timepoint_parser.process_time_zone_info(info) if info.get('time_zone_utc'): return (0, 0) if "time_zone_hour" not in info and "time_zone_minute" not in info: diff --git a/isodatetime/parser_spec.py b/isodatetime/parser_spec.py index 41c4b90..4fc0c85 100644 --- a/isodatetime/parser_spec.py +++ b/isodatetime/parser_spec.py @@ -151,7 +151,7 @@ --ss.tt # Deviation? Not allowed in standard ? """ } } -TIMEZONE_EXPRESSIONS = { +TIME_ZONE_EXPRESSIONS = { "basic": u""" Z ±hh @@ -220,7 +220,7 @@ (u"^-", "(?P-)", "-", None) ] -_TIMEZONE_TRANSLATE_INFO = [ +_TIME_ZONE_TRANSLATE_INFO = [ (u"(?<=±hh)mm", "(?P\d\d)", "%(time_zone_minute_abs)02d", "time_zone_minute_abs"), (u"(?<=±hh:)mm", "(?P\d\d)", @@ -233,15 +233,15 @@ "Z", None) ] -LOCALE_TIMEZONE_BASIC = timezone.get_timezone_format_for_locale() -LOCALE_TIMEZONE_BASIC_NO_Z = LOCALE_TIMEZONE_BASIC -if LOCALE_TIMEZONE_BASIC_NO_Z == "Z": - LOCALE_TIMEZONE_BASIC_NO_Z = "+0000" -LOCALE_TIMEZONE_EXTENDED = timezone.get_timezone_format_for_locale( +LOCAL_TIME_ZONE_BASIC = timezone.get_local_time_zone_format() +LOCAL_TIME_ZONE_BASIC_NO_Z = LOCAL_TIME_ZONE_BASIC +if LOCAL_TIME_ZONE_BASIC_NO_Z == "Z": + LOCAL_TIME_ZONE_BASIC_NO_Z = "+0000" +LOCAL_TIME_ZONE_EXTENDED = timezone.get_local_time_zone_format( extended_mode=True) -LOCALE_TIMEZONE_EXTENDED_NO_Z = LOCALE_TIMEZONE_EXTENDED -if LOCALE_TIMEZONE_EXTENDED_NO_Z == "Z": - LOCALE_TIMEZONE_EXTENDED_NO_Z = "+0000" +LOCAL_TIME_ZONE_EXTENDED_NO_Z = LOCAL_TIME_ZONE_EXTENDED +if LOCAL_TIME_ZONE_EXTENDED_NO_Z == "Z": + LOCAL_TIME_ZONE_EXTENDED_NO_Z = "+0000" # Note: we only accept the following subset of strftime syntax. # This is due to inconsistencies with the ISO 8601 representations. @@ -294,8 +294,8 @@ def get_time_translate_info(): return _TIME_TRANSLATE_INFO -def get_timezone_translate_info(): - return _TIMEZONE_TRANSLATE_INFO +def get_time_zone_translate_info(): + return _TIME_ZONE_TRANSLATE_INFO def translate_strftime_token(strftime_token, num_expanded_year_digits=2): @@ -323,7 +323,7 @@ def _translate_strftime_token(strftime_token, dump_mode=False, get_date_translate_info( num_expanded_year_digits=num_expanded_year_digits) + get_time_translate_info() + - get_timezone_translate_info() + get_time_zone_translate_info() ) attr_names = STRFTIME_TRANSLATE_INFO[strftime_token] if isinstance(attr_names, basestring): diff --git a/isodatetime/parsers.py b/isodatetime/parsers.py index 77949bb..0fd8ee0 100644 --- a/isodatetime/parsers.py +++ b/isodatetime/parsers.py @@ -133,11 +133,10 @@ class TimePointParser(object): whose offset from UTC is the (hours, minutes) information in this variable. To assume UTC, set this to (0, 0). - no_assume_local_time_zone (default False) specifies that dates and - times without time zone information (in the absence of - assumed_time_zone_hours_minutes), should not be assumed to be - in the local time zone. They would then be left with an unknown - time zone setting. + default_to_unknown_time_zone (default False) specifies that + dates and times without time zone information (in the absence of + assumed_time_zone) should be left with an unknown time zone + setting. Otherwise, the current local time zone will be used. dump_format (default None) specifies a default custom dump format string for TimePoint instances. See data.TimePoint documentation @@ -149,13 +148,13 @@ def __init__(self, num_expanded_year_digits=2, allow_truncated=False, allow_only_basic=False, assumed_time_zone=None, - no_assume_local_time_zone=False, + default_to_unknown_time_zone=False, dump_format=None): self.expanded_year_digits = num_expanded_year_digits self.allow_truncated = allow_truncated self.allow_only_basic = allow_only_basic self.assumed_time_zone = assumed_time_zone - self.no_assume_local_time_zone = no_assume_local_time_zone + self.default_to_unknown_time_zone = default_to_unknown_time_zone self.dump_format = dump_format self._generate_regexes() @@ -163,17 +162,17 @@ def _generate_regexes(self): """Generate combined date time strings.""" date_map = parser_spec.DATE_EXPRESSIONS time_map = parser_spec.TIME_EXPRESSIONS - timezone_map = parser_spec.TIMEZONE_EXPRESSIONS + time_zone_map = parser_spec.TIME_ZONE_EXPRESSIONS self._date_regex_map = {} self._time_regex_map = {} - self._timezone_regex_map = {} + self._time_zone_regex_map = {} format_ok_keys = ["basic", "extended"] if self.allow_only_basic: format_ok_keys = ["basic"] for format_type in format_ok_keys: self._date_regex_map.setdefault(format_type, {}) self._time_regex_map.setdefault(format_type, {}) - self._timezone_regex_map.setdefault(format_type, []) + self._time_zone_regex_map.setdefault(format_type, []) for date_key in date_map[format_type].keys(): self._date_regex_map[format_type].setdefault(date_key, []) regex_list = self._date_regex_map[format_type][date_key] @@ -190,12 +189,12 @@ def _generate_regexes(self): time_regex = self.parse_time_expression_to_regex( time_expr) regex_list.append([re.compile(time_regex), time_expr]) - for timezone_expr in self.get_expressions( - timezone_map[format_type]): - timezone_regex = self.parse_timezone_expression_to_regex( - timezone_expr) - self._timezone_regex_map[format_type].append( - [re.compile(timezone_regex), timezone_expr]) + for time_zone_expr in self.get_expressions( + time_zone_map[format_type]): + time_zone_regex = self.parse_time_zone_expression_to_regex( + time_zone_expr) + self._time_zone_regex_map[format_type].append( + [re.compile(time_zone_regex), time_zone_expr]) def get_expressions(self, text): """Yield valid expressions from text.""" @@ -223,10 +222,10 @@ def parse_time_expression_to_regex(self, expression): expression = "^" + expression + "$" return expression - def parse_timezone_expression_to_regex(self, expression): - """Construct regular expressions for the timezone.""" + def parse_time_zone_expression_to_regex(self, expression): + """Construct regular expressions for the time zone.""" for expr_regex, substitute, format_, name in ( - parser_spec.get_timezone_translate_info( + parser_spec.get_time_zone_translate_info( )): expression = re.sub(expr_regex, substitute, expression) expression = "^" + expression + "$" @@ -361,16 +360,16 @@ def _parse_from_custom_regex(self, regex, data_string, dump_format=None, time_info_keys.append(name) date_info = {} time_info = {} - timezone_info = {} + time_zone_info = {} for key, value in info.items(): if key in date_info_keys: date_info[key] = value elif key in time_info_keys: time_info[key] = value else: - timezone_info[key] = value - timezone_info = self.process_timezone_info(timezone_info) - time_info.update(timezone_info) + time_zone_info[key] = value + time_zone_info = self.process_time_zone_info(time_zone_info) + time_info.update(time_zone_info) return self._create_timepoint_from_info( date_info, time_info, dump_format=dump_format) @@ -410,35 +409,35 @@ def get_time_info(self, time_string, bad_formats=None, bad_types=None): return expr, result.groupdict() raise ISO8601SyntaxError("time", time_string) - def get_timezone_info(self, timezone_string, bad_formats=None): - """Return the properties from a timezone string.""" + def get_time_zone_info(self, time_zone_string, bad_formats=None): + """Return the properties from a time zone string.""" if bad_formats is None: bad_formats = [] - for format_key, regex_list in self._timezone_regex_map.items(): + for format_key, regex_list in self._time_zone_regex_map.items(): if format_key in bad_formats: continue for regex, expr in regex_list: - result = regex.match(timezone_string) + result = regex.match(time_zone_string) if result: return expr, result.groupdict() - raise ISO8601SyntaxError("timezone", timezone_string) + raise ISO8601SyntaxError("time zone", time_zone_string) def get_info(self, timepoint_string): """Return the date and time properties from a timepoint string.""" - date_time_timezone = timepoint_string.split( + date_time_time_zone = timepoint_string.split( parser_spec.TIME_DESIGNATOR) parsed_expr = "" - if len(date_time_timezone) == 1: - date = date_time_timezone[0] + if len(date_time_time_zone) == 1: + date = date_time_time_zone[0] keys, date_info = self.get_date_info(date) format_key, type_key, date_expr = keys parsed_expr += date_expr time_info = {} - timezone_info = ( - self.process_timezone_info({})) - time_info.update(timezone_info) + time_zone_info = ( + self.process_time_zone_info({})) + time_info.update(time_zone_info) else: - date, time_timezone = date_time_timezone + date, time_time_zone = date_time_time_zone if not date and self.allow_truncated: keys = (None, "truncated", "") date_info = {"truncated": True} @@ -458,14 +457,14 @@ def get_info(self, timepoint_string): bad_types = ["truncated"] if date_info.get("truncated"): bad_types = [] - if time_timezone.endswith("Z"): - time, timezone = time_timezone[:-1], "Z" - elif "+" in time_timezone: - time, timezone = time_timezone.split("+") - timezone = "+" + timezone - elif "-" in time_timezone: - time, timezone = time_timezone.rsplit("-", 1) - timezone = "-" + timezone + if time_time_zone.endswith("Z"): + time, time_zone = time_time_zone[:-1], "Z" + elif "+" in time_time_zone: + time, time_zone = time_time_zone.split("+") + time_zone = "+" + time_zone + elif "-" in time_time_zone: + time, time_zone = time_time_zone.rsplit("-", 1) + time_zone = "-" + time_zone # Make sure this isn't just a truncated time. try: time_expr, time_info = self.get_time_info( @@ -473,62 +472,65 @@ def get_info(self, timepoint_string): bad_formats=bad_formats, bad_types=bad_types ) - timezone_expr, timezone_info = self.get_timezone_info( - timezone, + time_zone_expr, time_zone_info = self.get_time_zone_info( + time_zone, bad_formats=bad_formats ) except ISO8601SyntaxError: - time = time_timezone - timezone = None + time = time_time_zone + time_zone = None else: - time = time_timezone - timezone = None - if timezone is None: - timezone_info = {} - timezone_expr = "" - timezone_info = ( - self.process_timezone_info(timezone_info)) + time = time_time_zone + time_zone = None + if time_zone is None: + time_zone_info = {} + time_zone_expr = "" + time_zone_info = ( + self.process_time_zone_info(time_zone_info)) else: - timezone_expr, timezone_info = self.get_timezone_info( - timezone, + time_zone_expr, time_zone_info = self.get_time_zone_info( + time_zone, bad_formats=bad_formats ) - timezone_info = self.process_timezone_info(timezone_info) + time_zone_info = self.process_time_zone_info(time_zone_info) time_expr, time_info = self.get_time_info( time, bad_formats=bad_formats, bad_types=bad_types) parsed_expr += parser_spec.TIME_DESIGNATOR + ( - time_expr + timezone_expr) - time_info.update(timezone_info) + time_expr + time_zone_expr) + time_info.update(time_zone_info) return date_info, time_info, parsed_expr - def process_timezone_info(self, timezone_info): - if not timezone_info: + def process_time_zone_info(self, time_zone_info=None): + """Rationalise time zone data and set defaults if appropriate.""" + if time_zone_info is None: + time_zone_info = {} + if not time_zone_info: # There is no time zone information specified. if self.assumed_time_zone is None: # No given value to assume. - if self.no_assume_local_time_zone: + if self.default_to_unknown_time_zone: # Return no time zone information. - return timezone_info + return {} # Set the time zone to the current local time zone. utc_hour_offset, utc_minute_offset = ( - timezone.get_timezone_for_locale()) - timezone_info["time_zone_hour"] = utc_hour_offset - timezone_info["time_zone_minute"] = utc_minute_offset - return timezone_info + timezone.get_local_time_zone()) + time_zone_info["time_zone_hour"] = utc_hour_offset + time_zone_info["time_zone_minute"] = utc_minute_offset + return time_zone_info else: # Set the time zone to a given value. utc_hour_offset, utc_minute_offset = self.assumed_time_zone - timezone_info["time_zone_hour"] = utc_hour_offset - timezone_info["time_zone_minute"] = utc_minute_offset - return timezone_info - if timezone_info.pop("time_zone_sign", "+") == "-": - timezone_info["time_zone_hour"] = ( - -int(timezone_info["time_zone_hour"])) - if "time_zone_minute" in timezone_info: - timezone_info["time_zone_minute"] = ( - -int(timezone_info["time_zone_minute"])) - return timezone_info + time_zone_info["time_zone_hour"] = utc_hour_offset + time_zone_info["time_zone_minute"] = utc_minute_offset + return time_zone_info + if time_zone_info.pop("time_zone_sign", "+") == "-": + time_zone_info["time_zone_hour"] = ( + -int(time_zone_info["time_zone_hour"])) + if "time_zone_minute" in time_zone_info: + time_zone_info["time_zone_minute"] = ( + -int(time_zone_info["time_zone_minute"])) + return time_zone_info class TimeIntervalParser(object): diff --git a/isodatetime/tests.py b/isodatetime/tests.py index 688b4f4..b01a4e9 100644 --- a/isodatetime/tests.py +++ b/isodatetime/tests.py @@ -410,7 +410,7 @@ def get_timepointparser_tests(allow_only_basic=False, } } } - test_timezone_map = { + test_time_zone_map = { "basic": { "Z": {"time_zone_hour": 0, "time_zone_minute": 0}, "+01": {"time_zone_hour": 1}, @@ -437,7 +437,7 @@ def get_timepointparser_tests(allow_only_basic=False, for format_type in format_ok_keys: date_format_tests = test_date_map[format_type] time_format_tests = test_time_map[format_type] - timezone_format_tests = test_timezone_map[format_type] + time_zone_format_tests = test_time_zone_map[format_type] for date_key in date_format_tests: if not allow_truncated and date_key == "truncated": continue @@ -461,12 +461,12 @@ def get_timepointparser_tests(allow_only_basic=False, yield combo_expr, combo_info if skip_time_zones: continue - timezone_items = timezone_format_tests.items() - for timezone_expr, timezone_info in timezone_items: - tz_expr = combo_expr + timezone_expr + time_zone_items = time_zone_format_tests.items() + for time_zone_expr, time_zone_info in time_zone_items: + tz_expr = combo_expr + time_zone_expr tz_info = {} for key, value in (combo_info.items() + - timezone_info.items()): + time_zone_info.items()): tz_info[key] = value yield tz_expr, tz_info if not allow_truncated: @@ -485,12 +485,12 @@ def get_timepointparser_tests(allow_only_basic=False, yield combo_expr, combo_info if skip_time_zones: continue - timezone_items = timezone_format_tests.items() - for timezone_expr, timezone_info in timezone_items: - tz_expr = combo_expr + timezone_expr + time_zone_items = time_zone_format_tests.items() + for time_zone_expr, time_zone_info in time_zone_items: + tz_expr = combo_expr + time_zone_expr tz_info = {} for key, value in (combo_info.items() + - timezone_info.items()): + time_zone_info.items()): tz_info[key] = value yield tz_expr, tz_info @@ -613,8 +613,8 @@ def get_timerecurrenceparser_tests(): "end_point": end_point} -def get_locale_time_zone_hours_minutes(): - """Provide an independent method of getting the local timezone.""" +def get_local_time_zone_hours_minutes(): + """Provide an independent method of getting the local time zone.""" import datetime utc_offset = datetime.datetime.now() - datetime.datetime.utcnow() utc_offset_hours = (utc_offset.seconds + 1800) // 3600 @@ -725,13 +725,13 @@ def test_timepoint(self): timedelta = datetime.timedelta(days=1) my_date += timedelta - def test_timepoint_timezone(self): - """Test the timezone handling of timepoint instances.""" + def test_timepoint_time_zone(self): + """Test the time zone handling of timepoint instances.""" year = 2000 month_of_year = 1 day_of_month = 1 utc_offset_hours, utc_offset_minutes = ( - get_locale_time_zone_hours_minutes() + get_local_time_zone_hours_minutes() ) for hour_of_day in range(24): for minute_of_hour in [0, 30]: @@ -796,7 +796,7 @@ def test_timepoint_timezone(self): def test_timepoint_dumper(self): """Test the dumping of TimePoint instances.""" parser = parsers.TimePointParser(allow_truncated=True, - no_assume_local_time_zone=True) + default_to_unknown_time_zone=True) dumper = dumpers.TimePointDumper() for expression, timepoint_kwargs in get_timepointparser_tests( allow_truncated=True): @@ -820,8 +820,9 @@ def test_timepoint_parser(self): """Test the parsing of date/time expressions.""" # Test unknown time zone assumptions. - parser = parsers.TimePointParser(allow_truncated=True, - no_assume_local_time_zone=True) + parser = parsers.TimePointParser( + allow_truncated=True, + default_to_unknown_time_zone=True) for expression, timepoint_kwargs in get_timepointparser_tests( allow_truncated=True): timepoint_kwargs = copy.deepcopy(timepoint_kwargs) @@ -838,7 +839,7 @@ def test_timepoint_parser(self): # Test local time zone assumptions (the default). utc_offset_hours, utc_offset_minutes = ( - get_locale_time_zone_hours_minutes() + get_local_time_zone_hours_minutes() ) parser = parsers.TimePointParser(allow_truncated=True) for expression, timepoint_kwargs in get_timepointparser_tests( @@ -853,11 +854,11 @@ def test_timepoint_parser(self): test_timepoint.time_zone.minutes) ctrl_data = (utc_offset_hours, utc_offset_minutes) self.assertEqual(test_data, ctrl_data, - "Local timezone for " + expression) + "Local time zone for " + expression) # Test given time zone assumptions. utc_offset_hours, utc_offset_minutes = ( - get_locale_time_zone_hours_minutes() + get_local_time_zone_hours_minutes() ) given_utc_offset_hours = -2 # This is an arbitrary number! if given_utc_offset_hours == utc_offset_hours: @@ -882,7 +883,7 @@ def test_timepoint_parser(self): test_timepoint.time_zone.minutes) ctrl_data = given_time_zone_hours_minutes self.assertEqual(test_data, ctrl_data, - "A given timezone for " + expression) + "A given time zone for " + expression) # Test UTC time zone assumptions. parser = parsers.TimePointParser( diff --git a/isodatetime/timezone.py b/isodatetime/timezone.py index 08ad06c..9c2cd94 100644 --- a/isodatetime/timezone.py +++ b/isodatetime/timezone.py @@ -16,13 +16,13 @@ # along with this program. If not, see . #----------------------------------------------------------------------------- -"""This provides utilites for extracting the local timezone.""" +"""This provides utilites for extracting the local time zone.""" import time -def get_timezone_for_locale(): - """Return the UTC offset for this locale in hours and minutes.""" +def get_local_time_zone(): + """Return the current local UTC offset in hours and minutes.""" utc_offset_seconds = -time.timezone if time.localtime().tm_isdst == 1 and time.daylight: utc_offset_seconds = -time.altzone @@ -31,9 +31,9 @@ def get_timezone_for_locale(): return utc_offset_hours, utc_offset_minutes -def get_timezone_format_for_locale(extended_mode=False, reduced_mode=False): - """Return the timezone format string for this locale (e.g. '+0300').""" - utc_offset_hours, utc_offset_minutes = get_timezone_for_locale() +def get_local_time_zone_format(extended_mode=False, reduced_mode=False): + """Return a string denoting the current local UTC offset.""" + utc_offset_hours, utc_offset_minutes = get_local_time_zone() if utc_offset_hours == 0 and utc_offset_minutes == 0: return "Z" reduced_timezone_template = "%s%02d"