diff --git a/src/zope/__init__.py b/src/zope/__init__.py index de40ea7..2cdb0e4 100644 --- a/src/zope/__init__.py +++ b/src/zope/__init__.py @@ -1 +1 @@ -__import__('pkg_resources').declare_namespace(__name__) +__import__('pkg_resources').declare_namespace(__name__) # pragma: no cover diff --git a/src/zope/datetime/__init__.py b/src/zope/datetime/__init__.py index 5ff616c..8e6f47b 100644 --- a/src/zope/datetime/__init__.py +++ b/src/zope/datetime/__init__.py @@ -19,16 +19,9 @@ import re import time as _time # there is a method definition that makes just "time" # problematic while executing a class definition +from time import tzname -try: - from types import StringTypes -except ImportError: - StringTypes = (str, ) # python3 and up - -try: - from time import tzname -except ImportError: - tzname = ('UNKNOWN', 'UNKNOWN') +StringTypes = (str,) if str is not bytes else (basestring,) # These are needed because the various date formats below must @@ -45,15 +38,13 @@ def iso8601_date(ts=None): # Return an ISO 8601 formatted date string, required # for certain DAV properties. # '2000-11-10T16:21:09-08:00 - if ts is None: - ts = _time.time() + ts = _time.time() if ts is None else ts return _time.strftime('%Y-%m-%dT%H:%M:%SZ', _time.gmtime(ts)) def rfc850_date(ts=None): # Return an HTTP-date formatted date string. # 'Friday, 10-Nov-00 16:21:09 GMT' - if ts is None: - ts = _time.time() + ts = _time.time() if ts is None else ts year, month, day, hh, mm, ss, wd, y, z = _time.gmtime(ts) return "%s, %02d-%3s-%2s %02d:%02d:%02d GMT" % ( weekday_full[wd], @@ -65,8 +56,7 @@ def rfc1123_date(ts=None): # Return an RFC 1123 format date string, required for # use in HTTP Date headers per the HTTP 1.1 spec. # 'Fri, 10 Nov 2000 16:21:09 GMT' - if ts is None: - ts = _time.time() + ts = _time.time() if ts is None else ts year, month, day, hh, mm, ss, wd, y, z = _time.gmtime(ts) return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (weekday_abbr[wd], day, monthname[month], @@ -95,7 +85,7 @@ class SyntaxError(DateTimeError): 'Invalid Date-Time String' numericTimeZoneMatch=re.compile(r'[+-][0-9][0-9][0-9][0-9]').match #TS -class _timezone: +class _timezone(object): def __init__(self,data): self.name,self.timect,self.typect, \ ttrans,self.tindex,self.tinfo,self.az=data @@ -108,7 +98,7 @@ def default_index(self): return 0 def index(self, t=None): - t = t or _time.time() + t = t if t is not None else _time.time() if self.timect == 0: idx = (0, 0, 0) elif t < self.ttrans[0]: @@ -139,7 +129,7 @@ def info(self,t=None): -class _cache: +class _cache(object): _zlst=['Brazil/Acre','Brazil/DeNoronha','Brazil/East', 'Brazil/West','Canada/Atlantic','Canada/Central', @@ -291,7 +281,7 @@ def __init__(self): def __getitem__(self,k): try: n=self._zmap[k.lower()] except KeyError: - if numericTimeZoneMatch(k) == None: + if numericTimeZoneMatch(k) is None: raise DateTimeError('Unrecognized timezone: %s' % k) return k try: @@ -301,7 +291,7 @@ def __getitem__(self,k): return z def _findLocalTimeZoneName(isDST): - if not _time.daylight: + if not _time.daylight: # pragma: no cover # Daylight savings does not occur in this time zone. isDST = 0 try: @@ -311,19 +301,18 @@ def _findLocalTimeZoneName(isDST): except KeyError: try: # Generate a GMT-offset zone name. - if isDST: - localzone = _time.altzone - else: - localzone = _time.timezone + localzone = _time.altzone if isDST else _time.timezone + offset=(-localzone/(60*60)) majorOffset=int(offset) if majorOffset != 0 : minorOffset=abs(int((offset % majorOffset) * 60.0)) - else: minorOffset = 0 + else: # pragma: no cover + minorOffset = 0 m=majorOffset >= 0 and '+' or '' lz='%s%0.02d%0.02d' % (m, majorOffset, minorOffset) _localzone = _cache._zmap[('GMT%s' % lz).lower()] - except: + except Exception: # pragma: no cover _localzone = '' return _localzone @@ -455,7 +444,7 @@ def safelocaltime(t): 'of this Python implementation.' % float(t)) return _time.localtime(t_int) -class DateTimeParser: +class DateTimeParser(object): def parse(self, arg, local=True): """Parse a string containing some sort of date-time data. @@ -609,8 +598,8 @@ def localZone(self, ltm=None): can change according to daylight savings.''' if not self._multipleZones: return self._localzone0 - if ltm == None: - ltm = _time.localtime() + + ltm = _time.localtime() if ltm is None else ltm isDST = ltm[8] lz = isDST and self._localzone1 or self._localzone0 return lz @@ -723,12 +712,12 @@ def _parse(self, string, local=True): v=MonthNumbers[s] if month is None: month=v else: raise SyntaxError(string) - continue + continue # pragma: no cover # Check for time modifier: if s in TimeModifiers: if tm is None: tm=s else: raise SyntaxError(string) - continue + continue # pragma: no cover # Check for and skip day of week: if s in DayOfWeekNames: continue @@ -893,7 +882,7 @@ def __parse_iso8601(self,s): ###################################################################### # Time-zone info based soley on offsets # -# Share tzinfos for the same offset +# Share tzinfos for the same offset from datetime import tzinfo as _tzinfo, timedelta as _timedelta @@ -917,7 +906,7 @@ def dst(self, dt): def tzname(self, dt): return None - def __repr__(self): + def __repr__(self): # pragma: no cover return 'tzinfo(%d)' % self.__minutes @@ -951,4 +940,4 @@ def parseDatetimetz(string, local=True): _tzinfo = None return _datetime(y, mo, d, int(h), int(m), int(s), int(micro), _tzinfo) -_iso_tz_re = re.compile("[-+]\d\d:\d\d$") +_iso_tz_re = re.compile(r"[-+]\d\d:\d\d$") diff --git a/src/zope/datetime/tests/test_datetime.py b/src/zope/datetime/tests/test_datetime.py new file mode 100644 index 0000000..a38789d --- /dev/null +++ b/src/zope/datetime/tests/test_datetime.py @@ -0,0 +1,243 @@ +############################################################################## +# +# Copyright (c) 2017 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +# pylint:disable=protected-access +import unittest + +from zope import datetime + +class TestCache(unittest.TestCase): + + def test_error(self): + with self.assertRaisesRegexp(datetime.DateTimeError, + "Unrecognized timezone"): + datetime._cache().__getitem__('foo') + + +class TestFuncs(unittest.TestCase): + + def test_correctYear(self): + + self.assertEqual(2069, datetime._correctYear(69)) + self.assertEqual(1998, datetime._correctYear(98)) + + + def test_safegmtime_safelocaltime_overflow(self): + def i(*args): + raise OverflowError() + try: + datetime.int = i + with self.assertRaises(datetime.TimeError): + datetime.safegmtime(1) + + with self.assertRaises(datetime.TimeError): + datetime.safelocaltime(1) + + finally: + del datetime.int + + def test_safegmtime(self): + self.assertIsNotNone(datetime.safegmtime(6000)) + + def test_calcSD(self): + s, d = datetime._calcSD(9) + self.assertAlmostEqual(s, 0, places=1) + self.assertGreater(d, 0) + + def test_calcDependentSecond(self): + s = datetime._calcDependentSecond('000', 0) + self.assertGreater(s, 0) + + def test_julianday(self): + self.assertEqual(datetime._julianday(2000, 13, 1), 2451910) + self.assertEqual(datetime._julianday(2000, -1, 1), 2451483) + self.assertEqual(datetime._julianday(0, 1, 1), 1721057) + + def test_calendarday(self): + # XXX: Why do we get different things on Py2 vs Py3? + # Are the calculations wrapping around somewhere? + answer = (-4712, 1, 3) if str is bytes else (-4711, 2, 0) + self.assertEqual(datetime._calendarday(1), answer) + + +class TestTimezone(unittest.TestCase): + + def _makeOne(self, name='name', timect=1, typect=0, + ttrans=(), tindex=1, tinfo=(), az=0): + return datetime._timezone((name, timect, typect, ttrans, tindex, tinfo, az)) + + def test_default_index(self): + tz = self._makeOne(timect=0) + self.assertEqual(0, tz.default_index()) + + tz = self._makeOne(timect=1, typect=2, tinfo=((0, 1), (0, 0))) + self.assertEqual(1, tz.default_index()) + + tz = self._makeOne(timect=1, typect=2, tinfo=((0, 1), (0, 1))) + self.assertEqual(0, tz.default_index()) + + def test_index(self): + tz = self._makeOne(ttrans=(1,), tindex='a') + self.assertEqual((0, 97, 0), + tz.index(0)) + + self.assertEqual((97, 97, 0), + tz.index(1)) + + tz.timect = 2 + tz.tindex = 'ab' + self.assertEqual((98, 98, 97), + tz.index(1)) + + + +class TestDateTimeParser(unittest.TestCase): + + def _makeOne(self): + return datetime.DateTimeParser() + + def _callParse(self, input_data): + return self._makeOne().parse(input_data) + + def _call_parse(self, input_data): + return self._makeOne()._parse(input_data) + + def test_parse_bad_input(self): + + with self.assertRaises(TypeError): + self._callParse(None) + + with self.assertRaises(datetime.SyntaxError): + self._callParse('') + + def test_parse_bad_year(self): + with self.assertRaises(datetime.DateError): + self._callParse("2000-01-32") + + def test_parse_bad_time(self): + with self.assertRaises(datetime.TimeError): + self._callParse("2000-01-01T25:63") + + def test_time_bad_tz(self): + # Have to mock the time() method here to get + # the desired return value for TZ, I couldn't find + # input that would get is there naturally + def t(*args): + return (2000, 1, 1, 0, 0, 0, '+FOO') + dtp = self._makeOne() + dtp.parse = t + with self.assertRaisesRegexp(datetime.DateTimeError, + "Unknown time zone"): + dtp.time("foo") + + def test_time_no_tz(self): + # See test_time_bad_tz + def t(*args): + return (2000, 1, 1, 0, 0, 0, '') + dtp = self._makeOne() + dtp.parse = t + x = dtp.time("foo") + self.assertEqual(x, 946706400.0) + + def test_localZone_non_multiple(self): + dtp = self._makeOne() + dtp._localzone0 = 0 + dtp._localzone1 = 1 + + dtp._multipleZones = False + self.assertEqual(0, dtp.localZone()) + + def test_calcTimezoneName_non_multiple(self): + dtp = self._makeOne() + dtp._localzone0 = 0 + dtp._localzone1 = 1 + + dtp._multipleZones = False + self.assertEqual(0, dtp._calcTimezoneName(None, None)) + + def test_calcTimezoneName_safelocaltime_fail(self): + + dtp = self._makeOne() + dtp._multipleZones = True + dtp._localzone0 = '0' + dtp._localzone1 = '1' + + called = [] + def i(_): + if not called: + called.append(1) + raise OverflowError() + return (0, 0, 0, 0, 0, 0, 0, 0, 1) + orig_safelocaltime = datetime.safelocaltime + try: + datetime.safelocaltime = i + self.assertEqual('1', dtp._calcTimezoneName(9467061400, 0)) + finally: + datetime.safelocaltime = orig_safelocaltime + + def test_parse_noniso_bad_month(self): + with self.assertRaises(datetime.SyntaxError): + self._callParse("2000--31 +1") + + def test_parse_noniso_tz(self): + x = self._call_parse("2000-01-01 22:13+05:00") + self.assertEqual((2000, 1, 1, 22, 13, 0, '+0500'), x) + + def test_parse_bad_month(self): + with self.assertRaises(datetime.SyntaxError): + self._call_parse("January January") + + def test_parse_bad_tm(self): + with self.assertRaises(datetime.SyntaxError): + self._call_parse("pm pm") + + def test_parse_multiple_ints(self): + result = self._call_parse("1 24 2000") + self.assertEqual(result[:-1], + (2000, 1, 24, 0, 0, 0)) + + with self.assertRaises(datetime.DateTimeError): + result = self._call_parse("January 24 2000 61") + + result = self._call_parse("January 2000 10") + self.assertEqual(result[:-1], + (2000, 1, 10, 0, 0, 0)) + + result = self._call_parse("10 32 12") + self.assertEqual(result[:-1], + (2017, 8, 3, 0, 0, 0)) + + result = self._call_parse("13 32 12") + self.assertEqual(result[:-1], + (2032, 12, 13, 0, 0, 0)) + + result = self._call_parse("12 32 13") + self.assertEqual(result[:-1], + (2032, 12, 13, 0, 0, 0)) + + with self.assertRaises(datetime.DateError): + result = self._call_parse("13 31 32") + + result = self._call_parse("10 31 32") + self.assertEqual(result[:-1], + (2032, 10, 31, 0, 0, 0)) + + result = self._call_parse("10 30 30") + self.assertEqual(result[:-1], + (2030, 10, 30, 0, 0, 0)) + + def test_parse_iso_index_error(self): + dtp = self._makeOne() + with self.assertRaisesRegexp(datetime.DateError, + "Not an ISO 8601 compliant"): + dtp._parse_iso8601('') diff --git a/src/zope/datetime/tests/test_datetimeparse.py b/src/zope/datetime/tests/test_datetimeparse.py index 1d48ce4..b81221a 100644 --- a/src/zope/datetime/tests/test_datetimeparse.py +++ b/src/zope/datetime/tests/test_datetimeparse.py @@ -97,8 +97,4 @@ def testParseDatetimetz(self): datetime(2003, 6, 4)) def test_suite(): - loader=unittest.TestLoader() - return loader.loadTestsFromTestCase(Test) - -if __name__=='__main__': - unittest.TextTestRunner().run(test_suite()) + return unittest.defaultTestLoader.loadTestsFromName(__name__) diff --git a/src/zope/datetime/tests/test_standard_dates.py b/src/zope/datetime/tests/test_standard_dates.py index 1135810..40dca45 100644 --- a/src/zope/datetime/tests/test_standard_dates.py +++ b/src/zope/datetime/tests/test_standard_dates.py @@ -35,8 +35,7 @@ def testrfc1123_date(self): "Sat, 12 Jan 2002 01:01:01 GMT") def test_suite(): - loader=unittest.TestLoader() - return loader.loadTestsFromTestCase(Test) + return unittest.defaultTestLoader.loadTestsFromName(__name__) -if __name__=='__main__': - unittest.TextTestRunner().run(test_suite()) +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/src/zope/datetime/tests/test_timezones.py b/src/zope/datetime/tests/test_timezones.py new file mode 100644 index 0000000..0156dc0 --- /dev/null +++ b/src/zope/datetime/tests/test_timezones.py @@ -0,0 +1,31 @@ +############################################################################## +# +# Copyright (c) 2017 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## + +import unittest + +class TestTimezones(unittest.TestCase): + + def test_dumpTimezoneInfo(self): + from zope.datetime.timezones import dumpTimezoneInfo + from zope.datetime.timezones import historical_zone_info + + import io + + output = io.StringIO() if bytes is not str else io.BytesIO() + + dumpTimezoneInfo(historical_zone_info, out=output) + + value = output.getvalue() + + self.assertTrue(value.endswith('\n}\n')) diff --git a/src/zope/datetime/tests/test_tzinfo.py b/src/zope/datetime/tests/test_tzinfo.py index 7113685..58613f0 100644 --- a/src/zope/datetime/tests/test_tzinfo.py +++ b/src/zope/datetime/tests/test_tzinfo.py @@ -11,15 +11,17 @@ # FOR A PARTICULAR PURPOSE. # ############################################################################## -"""Test for the 'tzinfo() function +"""Test for the 'tzinfo() function """ -from unittest import TestCase, TestSuite, main, makeSuite +import unittest import pickle import datetime from zope.datetime import tzinfo -class Test(TestCase): + + +class Test(unittest.TestCase): def test(self): @@ -28,8 +30,8 @@ def test(self): info2 = tzinfo(minutes) self.assertEqual(info1, info2) - self.assert_(info1 is info2) - self.assert_(pickle.loads(pickle.dumps(info1)) is info1) + self.assertIs(info1, info2) + self.assertIs(pickle.loads(pickle.dumps(info1)), info1) self.assertEqual(info1.utcoffset(None), @@ -44,9 +46,7 @@ def test(self): def test_suite(): - return TestSuite(( - makeSuite(Test), - )) + return unittest.defaultTestLoader.loadTestsFromName(__name__) -if __name__=='__main__': - main(defaultTest='test_suite') +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/src/zope/datetime/timezones.py b/src/zope/datetime/timezones.py index 2f24812..38f1a97 100644 --- a/src/zope/datetime/timezones.py +++ b/src/zope/datetime/timezones.py @@ -1177,25 +1177,28 @@ [(0, 0, 0)], 'GMT\x00'), } -def dumpTimezoneInfo(_data): +def dumpTimezoneInfo(_data, out=None): - print("historical_zone_info = {") + if out is None: # pragma: no cover + import sys + out = sys.stdout - items = _data.items() - items.sort() + print("historical_zone_info = {", file=out) + + items = sorted(_data.items()) for key, value in items: v1, v2, v3, ilist, bitmap, two_by_three, two_nullterm = value - print("'%s': ('%s', %s, %s," % (key, v1, v2, v3)) - print("[", end='') + print("'%s': ('%s', %s, %s," % (key, v1, v2, v3), file=out) + print("[", end='', file=out) while ilist: next_5, ilist = ilist[:5], ilist[5:] line = ", ".join(["'%s'" % x for x in next_5]) - print("%s," % line) - print("], ") - print("%s," % repr(bitmap)) - print("%s, %s)," % (repr(two_by_three), repr(two_nullterm))) + print("%s," % line, file=out) + print("], ", file=out) + print("%s," % repr(bitmap), file=out) + print("%s, %s)," % (repr(two_by_three), repr(two_nullterm)), file=out) - print("}") + print("}", file=out) if __name__ == '__main__': dumpTimezoneInfo(historical_zone_info) diff --git a/tox.ini b/tox.ini index c0847a1..90869b2 100644 --- a/tox.ini +++ b/tox.ini @@ -13,8 +13,8 @@ usedevelop = true basepython = python2.7 commands = - coverage run -m zope.testrunner --test-path=src - coverage report #--fail-under=100 + coverage run -m zope.testrunner --test-path=src [] + coverage report --fail-under=100 deps = {[testenv]deps} coverage