Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(6)

Delta Between Two Patch Sets: Lib/test/datetimetester.py

Issue 15873: "datetime" cannot parse ISO 8601 dates and times
Left Patch Set: Created 4 years ago
Right Patch Set: Created 3 years, 6 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « Lib/datetime.py ('k') | no next file » | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 """Test date/time type. 1 """Test date/time type.
2 2
3 See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases 3 See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4 """ 4 """
5 from test.support import is_resource_enabled
6
7 import itertools
8 import bisect
5 9
6 import copy 10 import copy
7 import decimal 11 import decimal
8 import sys 12 import sys
13 import os
9 import pickle 14 import pickle
10 import random 15 import random
16 import struct
11 import unittest 17 import unittest
18 import sysconfig
19
20 from array import array
12 21
13 from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod 22 from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod
14 23
15 from test import support 24 from test import support
16 25
17 import datetime as datetime_module 26 import datetime as datetime_module
18 from datetime import MINYEAR, MAXYEAR 27 from datetime import MINYEAR, MAXYEAR
19 from datetime import timedelta 28 from datetime import timedelta
20 from datetime import tzinfo 29 from datetime import tzinfo
21 from datetime import time 30 from datetime import time
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after
109 return self.__offset 118 return self.__offset
110 def tzname(self, dt): 119 def tzname(self, dt):
111 return self.__name 120 return self.__name
112 def dst(self, dt): 121 def dst(self, dt):
113 return self.__dstoffset 122 return self.__dstoffset
114 123
115 class PicklableFixedOffset(FixedOffset): 124 class PicklableFixedOffset(FixedOffset):
116 125
117 def __init__(self, offset=None, name=None, dstoffset=None): 126 def __init__(self, offset=None, name=None, dstoffset=None):
118 FixedOffset.__init__(self, offset, name, dstoffset) 127 FixedOffset.__init__(self, offset, name, dstoffset)
128
129 def __getstate__(self):
130 return self.__dict__
119 131
120 class _TZInfo(tzinfo): 132 class _TZInfo(tzinfo):
121 def utcoffset(self, datetime_module): 133 def utcoffset(self, datetime_module):
122 return random.random() 134 return random.random()
123 135
124 class TestTZInfo(unittest.TestCase): 136 class TestTZInfo(unittest.TestCase):
125 137
126 def test_refcnt_crash_bug_22044(self): 138 def test_refcnt_crash_bug_22044(self):
127 tz1 = _TZInfo() 139 tz1 = _TZInfo()
128 dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1) 140 dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1)
(...skipping 957 matching lines...) Expand 10 before | Expand all | Expand 10 after
1086 import time 1098 import time
1087 1099
1088 # Try an arbitrary fixed value. 1100 # Try an arbitrary fixed value.
1089 year, month, day = 1999, 9, 19 1101 year, month, day = 1999, 9, 19
1090 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1)) 1102 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1091 d = self.theclass.fromtimestamp(ts) 1103 d = self.theclass.fromtimestamp(ts)
1092 self.assertEqual(d.year, year) 1104 self.assertEqual(d.year, year)
1093 self.assertEqual(d.month, month) 1105 self.assertEqual(d.month, month)
1094 self.assertEqual(d.day, day) 1106 self.assertEqual(d.day, day)
1095 1107
1108 def test_fromisoformat(self):
1109 self.assertEqual(self.theclass.fromisoformat('2014-12-31'),
1110 self.theclass(2014, 12, 31))
1111 self.assertEqual(self.theclass.fromisoformat('4095-07-31'),
1112 self.theclass(4095, 7, 31))
1113
1114 with self.assertRaises(ValueError):
1115 self.theclass.fromisoformat('2014-12-011')
1116 with self.assertRaises(ValueError):
1117 self.theclass.fromisoformat('20141211')
1118 with self.assertRaises(ValueError):
1119 self.theclass.fromisoformat('043-12-01')
1120
1096 def test_insane_fromtimestamp(self): 1121 def test_insane_fromtimestamp(self):
1097 # It's possible that some platform maps time_t to double, 1122 # It's possible that some platform maps time_t to double,
1098 # and that this test will fail there. This test should 1123 # and that this test will fail there. This test should
1099 # exempt such platforms (provided they return reasonable 1124 # exempt such platforms (provided they return reasonable
1100 # results!). 1125 # results!).
1101 for insane in -1e200, 1e200: 1126 for insane in -1e200, 1e200:
1102 self.assertRaises(OverflowError, self.theclass.fromtimestamp, 1127 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
1103 insane) 1128 insane)
1104 1129
1105 def test_today(self): 1130 def test_today(self):
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
1218 1243
1219 # A naive object replaces %z and %Z w/ empty strings. 1244 # A naive object replaces %z and %Z w/ empty strings.
1220 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") 1245 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1221 1246
1222 #make sure that invalid format specifiers are handled correctly 1247 #make sure that invalid format specifiers are handled correctly
1223 #self.assertRaises(ValueError, t.strftime, "%e") 1248 #self.assertRaises(ValueError, t.strftime, "%e")
1224 #self.assertRaises(ValueError, t.strftime, "%") 1249 #self.assertRaises(ValueError, t.strftime, "%")
1225 #self.assertRaises(ValueError, t.strftime, "%#") 1250 #self.assertRaises(ValueError, t.strftime, "%#")
1226 1251
1227 #oh well, some systems just ignore those invalid ones. 1252 #oh well, some systems just ignore those invalid ones.
1228 #at least, excercise them to make sure that no crashes 1253 #at least, exercise them to make sure that no crashes
1229 #are generated 1254 #are generated
1230 for f in ["%e", "%", "%#"]: 1255 for f in ["%e", "%", "%#"]:
1231 try: 1256 try:
1232 t.strftime(f) 1257 t.strftime(f)
1233 except ValueError: 1258 except ValueError:
1234 pass 1259 pass
1235 1260
1236 #check that this standard extension works 1261 #check that this standard extension works
1237 t.strftime("%f") 1262 t.strftime("%f")
1238 1263
(...skipping 310 matching lines...) Expand 10 before | Expand all | Expand 10 after
1549 dt2 = eval(s) 1574 dt2 = eval(s)
1550 self.assertEqual(dt, dt2) 1575 self.assertEqual(dt, dt2)
1551 1576
1552 # Verify identity via reconstructing from pieces. 1577 # Verify identity via reconstructing from pieces.
1553 dt2 = self.theclass(dt.year, dt.month, dt.day, 1578 dt2 = self.theclass(dt.year, dt.month, dt.day,
1554 dt.hour, dt.minute, dt.second, 1579 dt.hour, dt.minute, dt.second,
1555 dt.microsecond) 1580 dt.microsecond)
1556 self.assertEqual(dt, dt2) 1581 self.assertEqual(dt, dt2)
1557 1582
1558 def test_isoformat(self): 1583 def test_isoformat(self):
1559 t = self.theclass(2, 3, 2, 4, 5, 1, 123) 1584 t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1560 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123") 1585 self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
1561 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123") 1586 self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1562 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123") 1587 self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1563 self.assertEqual(t.isoformat('\x00'), "0002-03-02\x0004:05:01.000123") 1588 self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
1589 self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1590 self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1591 self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1592 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05 :01.000")
1593 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05 :01.000123")
1594 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.0001 23")
1595 self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 0 4:05")
1596 self.assertRaises(ValueError, t.isoformat, timespec='foo')
1564 # str is ISO format with the separator forced to a blank. 1597 # str is ISO format with the separator forced to a blank.
1565 self.assertEqual(str(t), "0002-03-02 04:05:01.000123") 1598 self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1599
1600 t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1601 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05 :01.999+00:00")
1602
1603 t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1604 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05 :01.999")
1605
1606 t = self.theclass(1, 2, 3, 4, 5, 1)
1607 self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1608 self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05 :01.000")
1609 self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05 :01.000000")
1566 1610
1567 t = self.theclass(2, 3, 2) 1611 t = self.theclass(2, 3, 2)
1568 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00") 1612 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1569 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00") 1613 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1570 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00") 1614 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1571 # str is ISO format with the separator forced to a blank. 1615 # str is ISO format with the separator forced to a blank.
1572 self.assertEqual(str(t), "0002-03-02 00:00:00") 1616 self.assertEqual(str(t), "0002-03-02 00:00:00")
1617 # ISO format with timezone
1618 tz = FixedOffset(timedelta(seconds=16), 'XXX')
1619 t = self.theclass(2, 3, 2, tzinfo=tz)
1620 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
1573 1621
1574 def test_format(self): 1622 def test_format(self):
1575 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123) 1623 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1576 self.assertEqual(dt.__format__(''), str(dt)) 1624 self.assertEqual(dt.__format__(''), str(dt))
1577 1625
1578 with self.assertRaisesRegex(TypeError, 'must be str, not int'): 1626 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
1579 dt.__format__(123) 1627 dt.__format__(123)
1580 1628
1581 # check that a derived class's __str__() gets called 1629 # check that a derived class's __str__() gets called
1582 class A(self.theclass): 1630 class A(self.theclass):
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after
1682 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1) 1730 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1683 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60) 1731 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1684 # bad microseconds 1732 # bad microseconds
1685 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception 1733 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1686 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception 1734 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1687 self.assertRaises(ValueError, self.theclass, 1735 self.assertRaises(ValueError, self.theclass,
1688 2000, 1, 31, 23, 59, 59, -1) 1736 2000, 1, 31, 23, 59, 59, -1)
1689 self.assertRaises(ValueError, self.theclass, 1737 self.assertRaises(ValueError, self.theclass,
1690 2000, 1, 31, 23, 59, 59, 1738 2000, 1, 31, 23, 59, 59,
1691 1000000) 1739 1000000)
1740 # Positional fold:
1741 self.assertRaises(TypeError, self.theclass,
1742 2000, 1, 31, 23, 59, 59, 0, None, 1)
1692 1743
1693 def test_hash_equality(self): 1744 def test_hash_equality(self):
1694 d = self.theclass(2000, 12, 31, 23, 30, 17) 1745 d = self.theclass(2000, 12, 31, 23, 30, 17)
1695 e = self.theclass(2000, 12, 31, 23, 30, 17) 1746 e = self.theclass(2000, 12, 31, 23, 30, 17)
1696 self.assertEqual(d, e) 1747 self.assertEqual(d, e)
1697 self.assertEqual(hash(d), hash(e)) 1748 self.assertEqual(hash(d), hash(e))
1698 1749
1699 dic = {d: 1} 1750 dic = {d: 1}
1700 dic[e] = 2 1751 dic[e] = 2
1701 self.assertEqual(len(dic), 1) 1752 self.assertEqual(len(dic), 1)
(...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after
1840 1891
1841 # A helper for timestamp constructor tests. 1892 # A helper for timestamp constructor tests.
1842 def verify_field_equality(self, expected, got): 1893 def verify_field_equality(self, expected, got):
1843 self.assertEqual(expected.tm_year, got.year) 1894 self.assertEqual(expected.tm_year, got.year)
1844 self.assertEqual(expected.tm_mon, got.month) 1895 self.assertEqual(expected.tm_mon, got.month)
1845 self.assertEqual(expected.tm_mday, got.day) 1896 self.assertEqual(expected.tm_mday, got.day)
1846 self.assertEqual(expected.tm_hour, got.hour) 1897 self.assertEqual(expected.tm_hour, got.hour)
1847 self.assertEqual(expected.tm_min, got.minute) 1898 self.assertEqual(expected.tm_min, got.minute)
1848 self.assertEqual(expected.tm_sec, got.second) 1899 self.assertEqual(expected.tm_sec, got.second)
1849 1900
1850 def test_fromisoformat(self):
1851 # Valid inputs
1852 parse_datetime = self.theclass.fromisoformat
1853 self.assertEqual(parse_datetime('2012-04-23T09:15:00'),
1854 datetime(2012, 4, 23, 9, 15))
1855 self.assertEqual(parse_datetime('2012-4-9 4:8:16'),
1856 datetime(2012, 4, 9, 4, 8, 16))
1857 self.assertEqual(parse_datetime('2012-04-23T09:15:00Z'),
1858 datetime(2012, 4, 23, 9, 15, 0, 0, timezone(timedelta(minutes=0))))
1859 self.assertEqual(parse_datetime('2012-4-9 4:8:16-0320'),
1860 datetime(2012, 4, 9, 4, 8, 16, 0, timezone(timedelta(hours=-3, minut es=-20))))
1861 self.assertEqual(parse_datetime('2012-04-23T10:20:30.400+02:30'),
1862 datetime(2012, 4, 23, 10, 20, 30, 400000, timezone(timedelta(hours=2 , minutes=30))))
1863 self.assertEqual(parse_datetime('2012-04-23T10:20:30.400+02'),
1864 datetime(2012, 4, 23, 10, 20, 30, 400000, timezone(timedelta(hours=2 ))))
1865 self.assertEqual(parse_datetime('2012-04-23T10:20:30.400-02'),
1866 datetime(2012, 4, 23, 10, 20, 30, 400000, timezone(timedelta(hours=- 2))))
1867 self.assertEqual(parse_datetime('2012-04-23T10:20:30.4000099-02'),
1868 datetime(2012, 4, 23, 10, 20, 30, 400009, timezone(timedelta(hours=- 2))))
1869
1870 # Invalid inputs
1871 with self.assertRaises(ValueError):
1872 parse_datetime('20120423091500')
1873 parse_datetime('2012-04-56T09:15:90')
1874
1875
1876 def test_fromtimestamp(self): 1901 def test_fromtimestamp(self):
1877 import time 1902 import time
1878 1903
1879 ts = time.time() 1904 ts = time.time()
1880 expected = time.localtime(ts) 1905 expected = time.localtime(ts)
1881 got = self.theclass.fromtimestamp(ts) 1906 got = self.theclass.fromtimestamp(ts)
1882 self.verify_field_equality(expected, got) 1907 self.verify_field_equality(expected, got)
1883 1908
1884 def test_utcfromtimestamp(self): 1909 def test_utcfromtimestamp(self):
1885 import time 1910 import time
1886 1911
1887 ts = time.time() 1912 ts = time.time()
1888 expected = time.gmtime(ts) 1913 expected = time.gmtime(ts)
1889 got = self.theclass.utcfromtimestamp(ts) 1914 got = self.theclass.utcfromtimestamp(ts)
1890 self.verify_field_equality(expected, got) 1915 self.verify_field_equality(expected, got)
1916
1917 def test_fromisoformat(self):
1918 self.assertEqual(self.theclass.fromisoformat('2015-12-31T14:27:00'),
1919 self.theclass(2015, 12, 31, 14, 27, 0))
1920 self.assertEqual(self.theclass.fromisoformat('2015-12-31 14:27:00'),
1921 self.theclass(2015, 12, 31, 14, 27, 0))
1922 # lowercase 'T' date-time separator. Uncommon but tolerated (rfc 3339)
1923 self.assertEqual(self.theclass.fromisoformat('2015-12-31t14:27:00'),
1924 self.theclass(2015, 12, 31, 14, 27, 0))
1925
1926 with self.assertRaises(ValueError):
1927 self.theclass.fromisoformat('2015-01-07X00:00:00')
1891 1928
1892 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in 1929 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
1893 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0). 1930 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
1894 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0') 1931 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
1895 def test_timestamp_naive(self): 1932 def test_timestamp_naive(self):
1896 t = self.theclass(1970, 1, 1) 1933 t = self.theclass(1970, 1, 1)
1897 self.assertEqual(t.timestamp(), 18000.0) 1934 self.assertEqual(t.timestamp(), 18000.0)
1898 t = self.theclass(1970, 1, 1, 1, 2, 3, 4) 1935 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
1899 self.assertEqual(t.timestamp(), 1936 self.assertEqual(t.timestamp(),
1900 18000.0 + 3600 + 2*60 + 3 + 4*1e-6) 1937 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
1901 # Missing hour may produce platform-dependent result 1938 # Missing hour
1902 t = self.theclass(2012, 3, 11, 2, 30) 1939 t0 = self.theclass(2012, 3, 11, 2, 30)
1903 self.assertIn(self.theclass.fromtimestamp(t.timestamp()), 1940 t1 = t0.replace(fold=1)
1904 [t - timedelta(hours=1), t + timedelta(hours=1)]) 1941 self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
1942 t0 - timedelta(hours=1))
1943 self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
1944 t1 + timedelta(hours=1))
1905 # Ambiguous hour defaults to DST 1945 # Ambiguous hour defaults to DST
1906 t = self.theclass(2012, 11, 4, 1, 30) 1946 t = self.theclass(2012, 11, 4, 1, 30)
1907 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t) 1947 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
1908 1948
1909 # Timestamp may raise an overflow error on some platforms 1949 # Timestamp may raise an overflow error on some platforms
1910 for t in [self.theclass(1,1,1), self.theclass(9999,12,12)]: 1950 # XXX: Do we care to support the first and last year?
1951 for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
1911 try: 1952 try:
1912 s = t.timestamp() 1953 s = t.timestamp()
1913 except OverflowError: 1954 except OverflowError:
1914 pass 1955 pass
1915 else: 1956 else:
1916 self.assertEqual(self.theclass.fromtimestamp(s), t) 1957 self.assertEqual(self.theclass.fromtimestamp(s), t)
1917 1958
1918 def test_timestamp_aware(self): 1959 def test_timestamp_aware(self):
1919 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc) 1960 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
1920 self.assertEqual(t.timestamp(), 0.0) 1961 self.assertEqual(t.timestamp(), 0.0)
1921 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc) 1962 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
1922 self.assertEqual(t.timestamp(), 1963 self.assertEqual(t.timestamp(),
1923 3600 + 2*60 + 3 + 4*1e-6) 1964 3600 + 2*60 + 3 + 4*1e-6)
1924 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, 1965 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
1925 tzinfo=timezone(timedelta(hours=-5), 'EST')) 1966 tzinfo=timezone(timedelta(hours=-5), 'EST'))
1926 self.assertEqual(t.timestamp(), 1967 self.assertEqual(t.timestamp(),
1927 18000 + 3600 + 2*60 + 3 + 4*1e-6) 1968 18000 + 3600 + 2*60 + 3 + 4*1e-6)
1928 1969
1970 @support.run_with_tz('MSK-03') # Something east of Greenwich
1929 def test_microsecond_rounding(self): 1971 def test_microsecond_rounding(self):
1930 for fts in [self.theclass.fromtimestamp, 1972 for fts in [self.theclass.fromtimestamp,
1931 self.theclass.utcfromtimestamp]: 1973 self.theclass.utcfromtimestamp]:
1932 zero = fts(0) 1974 zero = fts(0)
1933 self.assertEqual(zero.second, 0) 1975 self.assertEqual(zero.second, 0)
1934 self.assertEqual(zero.microsecond, 0) 1976 self.assertEqual(zero.microsecond, 0)
1935 one = fts(1e-6) 1977 one = fts(1e-6)
1936 try: 1978 try:
1937 minus_one = fts(-1e-6) 1979 minus_one = fts(-1e-6)
1938 except OSError: 1980 except OSError:
(...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after
2093 dt = combine(time=t, date=d) 2135 dt = combine(time=t, date=d)
2094 self.assertEqual(dt, expected) 2136 self.assertEqual(dt, expected)
2095 2137
2096 self.assertEqual(d, dt.date()) 2138 self.assertEqual(d, dt.date())
2097 self.assertEqual(t, dt.time()) 2139 self.assertEqual(t, dt.time())
2098 self.assertEqual(dt, combine(dt.date(), dt.time())) 2140 self.assertEqual(dt, combine(dt.date(), dt.time()))
2099 2141
2100 self.assertRaises(TypeError, combine) # need an arg 2142 self.assertRaises(TypeError, combine) # need an arg
2101 self.assertRaises(TypeError, combine, d) # need two args 2143 self.assertRaises(TypeError, combine, d) # need two args
2102 self.assertRaises(TypeError, combine, t, d) # args reversed 2144 self.assertRaises(TypeError, combine, t, d) # args reversed
2103 self.assertRaises(TypeError, combine, d, t, 1) # too many args 2145 self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2146 self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
2104 self.assertRaises(TypeError, combine, "date", "time") # wrong types 2147 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2105 self.assertRaises(TypeError, combine, d, "time") # wrong type 2148 self.assertRaises(TypeError, combine, d, "time") # wrong type
2106 self.assertRaises(TypeError, combine, "date", t) # wrong type 2149 self.assertRaises(TypeError, combine, "date", t) # wrong type
2150
2151 # tzinfo= argument
2152 dt = combine(d, t, timezone.utc)
2153 self.assertIs(dt.tzinfo, timezone.utc)
2154 dt = combine(d, t, tzinfo=timezone.utc)
2155 self.assertIs(dt.tzinfo, timezone.utc)
2156 t = time()
2157 dt = combine(dt, t)
2158 self.assertEqual(dt.date(), d)
2159 self.assertEqual(dt.time(), t)
2107 2160
2108 def test_replace(self): 2161 def test_replace(self):
2109 cls = self.theclass 2162 cls = self.theclass
2110 args = [1, 2, 3, 4, 5, 6, 7] 2163 args = [1, 2, 3, 4, 5, 6, 7]
2111 base = cls(*args) 2164 base = cls(*args)
2112 self.assertEqual(base, base.replace()) 2165 self.assertEqual(base, base.replace())
2113 2166
2114 i = 0 2167 i = 0
2115 for name, newval in (("year", 2), 2168 for name, newval in (("year", 2),
2116 ("month", 3), 2169 ("month", 3),
2117 ("day", 4), 2170 ("day", 4),
2118 ("hour", 5), 2171 ("hour", 5),
2119 ("minute", 6), 2172 ("minute", 6),
2120 ("second", 7), 2173 ("second", 7),
2121 ("microsecond", 8)): 2174 ("microsecond", 8)):
2122 newargs = args[:] 2175 newargs = args[:]
2123 newargs[i] = newval 2176 newargs[i] = newval
2124 expected = cls(*newargs) 2177 expected = cls(*newargs)
2125 got = base.replace(**{name: newval}) 2178 got = base.replace(**{name: newval})
2126 self.assertEqual(expected, got) 2179 self.assertEqual(expected, got)
2127 i += 1 2180 i += 1
2128 2181
2129 # Out of bounds. 2182 # Out of bounds.
2130 base = cls(2000, 2, 29) 2183 base = cls(2000, 2, 29)
2131 self.assertRaises(ValueError, base.replace, year=2001) 2184 self.assertRaises(ValueError, base.replace, year=2001)
2132 2185
2133 def test_astimezone(self): 2186 def test_astimezone(self):
2187 return # The rest is no longer applicable
2134 # Pretty boring! The TZ test is more interesting here. astimezone() 2188 # Pretty boring! The TZ test is more interesting here. astimezone()
2135 # simply can't be applied to a naive object. 2189 # simply can't be applied to a naive object.
2136 dt = self.theclass.now() 2190 dt = self.theclass.now()
2137 f = FixedOffset(44, "") 2191 f = FixedOffset(44, "")
2138 self.assertRaises(ValueError, dt.astimezone) # naive 2192 self.assertRaises(ValueError, dt.astimezone) # naive
2139 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args 2193 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2140 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type 2194 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2141 self.assertRaises(ValueError, dt.astimezone, f) # naive 2195 self.assertRaises(ValueError, dt.astimezone, f) # naive
2142 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive 2196 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
2143 2197
(...skipping 197 matching lines...) Expand 10 before | Expand all | Expand 10 after
2341 self.assertEqual(t.isoformat(), str(t)) 2395 self.assertEqual(t.isoformat(), str(t))
2342 2396
2343 t = self.theclass(microsecond=10000) 2397 t = self.theclass(microsecond=10000)
2344 self.assertEqual(t.isoformat(), "00:00:00.010000") 2398 self.assertEqual(t.isoformat(), "00:00:00.010000")
2345 self.assertEqual(t.isoformat(), str(t)) 2399 self.assertEqual(t.isoformat(), str(t))
2346 2400
2347 t = self.theclass(microsecond=100000) 2401 t = self.theclass(microsecond=100000)
2348 self.assertEqual(t.isoformat(), "00:00:00.100000") 2402 self.assertEqual(t.isoformat(), "00:00:00.100000")
2349 self.assertEqual(t.isoformat(), str(t)) 2403 self.assertEqual(t.isoformat(), str(t))
2350 2404
2405 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2406 self.assertEqual(t.isoformat(timespec='hours'), "12")
2407 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2408 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2409 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2410 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456" )
2411 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2412 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
2413
2414 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2415 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2416
2417 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2418 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2419 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000" )
2420 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2421
2422 def test_fromisoformat(self):
2423 # basic
2424 self.assertEqual(self.theclass.fromisoformat('04:05:01.000123'),
2425 self.theclass(4, 5, 1, 123))
2426 self.assertEqual(self.theclass.fromisoformat('00:00:00'),
2427 self.theclass(0, 0, 0))
2428 # usec, rounding high
2429 self.assertEqual(self.theclass.fromisoformat('10:20:30.40000059'),
2430 self.theclass(10, 20, 30, 400001))
2431 # usec, rounding low + long digits we don't care about
2432 self.assertEqual(self.theclass.fromisoformat('10:20:30.400003434'),
2433 self.theclass(10, 20, 30, 400003))
2434 with self.assertRaises(ValueError):
2435 self.theclass.fromisoformat('12:00AM')
2436 with self.assertRaises(ValueError):
2437 self.theclass.fromisoformat('120000')
2438 with self.assertRaises(ValueError):
2439 self.theclass.fromisoformat('1:00')
2440 with self.assertRaises(ValueError):
2441 self.theclass.fromisoformat('17:54:43.')
2442
2443 def tz(h, m):
2444 return timezone(timedelta(hours=h, minutes=m))
2445
2446 self.assertEqual(self.theclass.fromisoformat('00:00:00Z'),
2447 self.theclass(0, 0, 0, tzinfo=timezone.utc))
2448 # lowercase UTC timezone. Uncommon but tolerated (rfc 3339)
2449 self.assertEqual(self.theclass.fromisoformat('00:00:00z'),
2450 self.theclass(0, 0, 0, tzinfo=timezone.utc))
2451 self.assertEqual(self.theclass.fromisoformat('00:00:00-00:00'),
2452 self.theclass(0, 0, 0, tzinfo=tz(0, 0)))
2453 self.assertEqual(self.theclass.fromisoformat('08:30:00.004255+02:30'),
2454 self.theclass(8, 30, 0, 4255, tz(2, 30)))
2455 self.assertEqual(self.theclass.fromisoformat('08:30:00.004255-02:30'),
2456 self.theclass(8, 30, 0, 4255, tz(-2, -30)))
2457
2351 def test_1653736(self): 2458 def test_1653736(self):
2352 # verify it doesn't accept extra keyword arguments 2459 # verify it doesn't accept extra keyword arguments
2353 t = self.theclass(second=1) 2460 t = self.theclass(second=1)
2354 self.assertRaises(TypeError, t.isoformat, foo=3) 2461 self.assertRaises(TypeError, t.isoformat, foo=3)
2355 2462
2356 def test_strftime(self): 2463 def test_strftime(self):
2357 t = self.theclass(1, 2, 3, 4) 2464 t = self.theclass(1, 2, 3, 4)
2358 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004") 2465 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2359 # A naive object replaces %z and %Z with empty strings. 2466 # A naive object replaces %z and %Z with empty strings.
2360 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") 2467 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
(...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after
2493 # see TestDate.test_backdoor_resistance(). 2600 # see TestDate.test_backdoor_resistance().
2494 base = '2:59.0' 2601 base = '2:59.0'
2495 for hour_byte in ' ', '9', chr(24), '\xff': 2602 for hour_byte in ' ', '9', chr(24), '\xff':
2496 self.assertRaises(TypeError, self.theclass, 2603 self.assertRaises(TypeError, self.theclass,
2497 hour_byte + base[1:]) 2604 hour_byte + base[1:])
2498 # Good bytes, but bad tzinfo: 2605 # Good bytes, but bad tzinfo:
2499 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'): 2606 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
2500 self.theclass(bytes([1] * len(base)), 'EST') 2607 self.theclass(bytes([1] * len(base)), 'EST')
2501 2608
2502 # A mixin for classes with a tzinfo= argument. Subclasses must define 2609 # A mixin for classes with a tzinfo= argument. Subclasses must define
2503 # theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever) 2610 # theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
2504 # must be legit (which is true for time and datetime). 2611 # must be legit (which is true for time and datetime).
2505 class TZInfoBase: 2612 class TZInfoBase:
2506 2613
2507 def test_argument_passing(self): 2614 def test_argument_passing(self):
2508 cls = self.theclass 2615 cls = self.theclass
2509 # A datetime passes itself on, a time passes None. 2616 # A datetime passes itself on, a time passes None.
2510 class introspective(tzinfo): 2617 class introspective(tzinfo):
2511 def tzname(self, dt): return dt and "real" or "none" 2618 def tzname(self, dt): return dt and "real" or "none"
2512 def utcoffset(self, dt): 2619 def utcoffset(self, dt):
2513 return timedelta(minutes = dt and 42 or -42) 2620 return timedelta(minutes = dt and 42 or -42)
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after
2599 self.assertRaises(TypeError, t.tzname) 2706 self.assertRaises(TypeError, t.tzname)
2600 2707
2601 # Offset out of range. 2708 # Offset out of range.
2602 class C6(tzinfo): 2709 class C6(tzinfo):
2603 def utcoffset(self, dt): return timedelta(hours=-24) 2710 def utcoffset(self, dt): return timedelta(hours=-24)
2604 def dst(self, dt): return timedelta(hours=24) 2711 def dst(self, dt): return timedelta(hours=24)
2605 t = cls(1, 1, 1, tzinfo=C6()) 2712 t = cls(1, 1, 1, tzinfo=C6())
2606 self.assertRaises(ValueError, t.utcoffset) 2713 self.assertRaises(ValueError, t.utcoffset)
2607 self.assertRaises(ValueError, t.dst) 2714 self.assertRaises(ValueError, t.dst)
2608 2715
2609 # Not a whole number of minutes. 2716 # Not a whole number of seconds.
2610 class C7(tzinfo): 2717 class C7(tzinfo):
2611 def utcoffset(self, dt): return timedelta(seconds=61) 2718 def utcoffset(self, dt): return timedelta(microseconds=61)
2612 def dst(self, dt): return timedelta(microseconds=-81) 2719 def dst(self, dt): return timedelta(microseconds=-81)
2613 t = cls(1, 1, 1, tzinfo=C7()) 2720 t = cls(1, 1, 1, tzinfo=C7())
2614 self.assertRaises(ValueError, t.utcoffset) 2721 self.assertRaises(ValueError, t.utcoffset)
2615 self.assertRaises(ValueError, t.dst) 2722 self.assertRaises(ValueError, t.dst)
2616 2723
2617 def test_aware_compare(self): 2724 def test_aware_compare(self):
2618 cls = self.theclass 2725 cls = self.theclass
2619 2726
2620 # Ensure that utcoffset() gets ignored if the comparands have 2727 # Ensure that utcoffset() gets ignored if the comparands have
2621 # the same tzinfo member. 2728 # the same tzinfo member.
(...skipping 821 matching lines...) Expand 10 before | Expand all | Expand 10 after
3443 def test_astimezone_default_eastern(self): 3550 def test_astimezone_default_eastern(self):
3444 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc) 3551 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3445 local = dt.astimezone() 3552 local = dt.astimezone()
3446 self.assertEqual(dt, local) 3553 self.assertEqual(dt, local)
3447 self.assertEqual(local.strftime("%z %Z"), "-0500 EST") 3554 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
3448 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc) 3555 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3449 local = dt.astimezone() 3556 local = dt.astimezone()
3450 self.assertEqual(dt, local) 3557 self.assertEqual(dt, local)
3451 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT") 3558 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
3452 3559
3560 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3561 def test_astimezone_default_near_fold(self):
3562 # Issue #26616.
3563 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
3564 t = u.astimezone()
3565 s = t.astimezone()
3566 self.assertEqual(t.tzinfo, s.tzinfo)
3567
3453 def test_aware_subtract(self): 3568 def test_aware_subtract(self):
3454 cls = self.theclass 3569 cls = self.theclass
3455 3570
3456 # Ensure that utcoffset() is ignored when the operands have the 3571 # Ensure that utcoffset() is ignored when the operands have the
3457 # same tzinfo member. 3572 # same tzinfo member.
3458 class OperandDependentOffset(tzinfo): 3573 class OperandDependentOffset(tzinfo):
3459 def utcoffset(self, t): 3574 def utcoffset(self, t):
3460 if t.minute < 10: 3575 if t.minute < 10:
3461 # d0 and d1 equal after adjustment 3576 # d0 and d1 equal after adjustment
3462 return timedelta(minutes=t.minute) 3577 return timedelta(minutes=t.minute)
(...skipping 503 matching lines...) Expand 10 before | Expand all | Expand 10 after
3966 datetime(10, 10, 10.) 4081 datetime(10, 10, 10.)
3967 with self.assertRaises(TypeError): 4082 with self.assertRaises(TypeError):
3968 datetime(10, 10, 10, 10.) 4083 datetime(10, 10, 10, 10.)
3969 with self.assertRaises(TypeError): 4084 with self.assertRaises(TypeError):
3970 datetime(10, 10, 10, 10, 10.) 4085 datetime(10, 10, 10, 10, 10.)
3971 with self.assertRaises(TypeError): 4086 with self.assertRaises(TypeError):
3972 datetime(10, 10, 10, 10, 10, 10.) 4087 datetime(10, 10, 10, 10, 10, 10.)
3973 with self.assertRaises(TypeError): 4088 with self.assertRaises(TypeError):
3974 datetime(10, 10, 10, 10, 10, 10, 10.) 4089 datetime(10, 10, 10, 10, 10, 10, 10.)
3975 4090
4091 #############################################################################
4092 # Local Time Disambiguation
4093
4094 # An experimental reimplementation of fromutc that respects the "fold" flag.
4095
4096 class tzinfo2(tzinfo):
4097
4098 def fromutc(self, dt):
4099 "datetime in UTC -> datetime in local time."
4100
4101 if not isinstance(dt, datetime):
4102 raise TypeError("fromutc() requires a datetime argument")
4103 if dt.tzinfo is not self:
4104 raise ValueError("dt.tzinfo is not self")
4105 # Returned value satisfies
4106 # dt + ldt.utcoffset() = ldt
4107 off0 = dt.replace(fold=0).utcoffset()
4108 off1 = dt.replace(fold=1).utcoffset()
4109 if off0 is None or off1 is None or dt.dst() is None:
4110 raise ValueError
4111 if off0 == off1:
4112 ldt = dt + off0
4113 off1 = ldt.utcoffset()
4114 if off0 == off1:
4115 return ldt
4116 # Now, we discovered both possible offsets, so
4117 # we can just try four possible solutions:
4118 for off in [off0, off1]:
4119 ldt = dt + off
4120 if ldt.utcoffset() == off:
4121 return ldt
4122 ldt = ldt.replace(fold=1)
4123 if ldt.utcoffset() == off:
4124 return ldt
4125
4126 raise ValueError("No suitable local time found")
4127
4128 # Reimplementing simplified US timezones to respect the "fold" flag:
4129
4130 class USTimeZone2(tzinfo2):
4131
4132 def __init__(self, hours, reprname, stdname, dstname):
4133 self.stdoffset = timedelta(hours=hours)
4134 self.reprname = reprname
4135 self.stdname = stdname
4136 self.dstname = dstname
4137
4138 def __repr__(self):
4139 return self.reprname
4140
4141 def tzname(self, dt):
4142 if self.dst(dt):
4143 return self.dstname
4144 else:
4145 return self.stdname
4146
4147 def utcoffset(self, dt):
4148 return self.stdoffset + self.dst(dt)
4149
4150 def dst(self, dt):
4151 if dt is None or dt.tzinfo is None:
4152 # An exception instead may be sensible here, in one or more of
4153 # the cases.
4154 return ZERO
4155 assert dt.tzinfo is self
4156
4157 # Find first Sunday in April.
4158 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4159 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4160
4161 # Find last Sunday in October.
4162 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4163 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4164
4165 # Can't compare naive to aware objects, so strip the timezone from
4166 # dt first.
4167 dt = dt.replace(tzinfo=None)
4168 if start + HOUR <= dt < end:
4169 # DST is in effect.
4170 return HOUR
4171 elif end <= dt < end + HOUR:
4172 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4173 return ZERO if dt.fold else HOUR
4174 elif start <= dt < start + HOUR:
4175 # Gap (a non-existent hour): reverse the fold rule.
4176 return HOUR if dt.fold else ZERO
4177 else:
4178 # DST is off.
4179 return ZERO
4180
4181 Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4182 Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4183 Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4184 Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4185
4186 # Europe_Vilnius_1941 tzinfo implementation reproduces the following
4187 # 1941 transition from Olson's tzdist:
4188 #
4189 # Zone NAME GMTOFF RULES FORMAT [UNTIL]
4190 # ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4191 # 3:00 - MSK 1941 Jun 24
4192 # 1:00 C-Eur CE%sT 1944 Aug
4193 #
4194 # $ zdump -v Europe/Vilnius | grep 1941
4195 # Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK is dst=0 gmtoff=10800
4196 # Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST i sdst=1 gmtoff=7200
4197
4198 class Europe_Vilnius_1941(tzinfo):
4199 def _utc_fold(self):
4200 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1 941 UTC
4201 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1 941 UTC
4202
4203 def _loc_fold(self):
4204 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1 941 MSK / CEST
4205 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1 941 CEST
4206
4207 def utcoffset(self, dt):
4208 fold_start, fold_stop = self._loc_fold()
4209 if dt < fold_start:
4210 return 3 * HOUR
4211 if dt < fold_stop:
4212 return (2 if dt.fold else 3) * HOUR
4213 # if dt >= fold_stop
4214 return 2 * HOUR
4215
4216 def dst(self, dt):
4217 fold_start, fold_stop = self._loc_fold()
4218 if dt < fold_start:
4219 return 0 * HOUR
4220 if dt < fold_stop:
4221 return (1 if dt.fold else 0) * HOUR
4222 # if dt >= fold_stop
4223 return 1 * HOUR
4224
4225 def tzname(self, dt):
4226 fold_start, fold_stop = self._loc_fold()
4227 if dt < fold_start:
4228 return 'MSK'
4229 if dt < fold_stop:
4230 return ('MSK', 'CEST')[dt.fold]
4231 # if dt >= fold_stop
4232 return 'CEST'
4233
4234 def fromutc(self, dt):
4235 assert dt.fold == 0
4236 assert dt.tzinfo is self
4237 if dt.year != 1941:
4238 raise NotImplementedError
4239 fold_start, fold_stop = self._utc_fold()
4240 if dt < fold_start:
4241 return dt + 3 * HOUR
4242 if dt < fold_stop:
4243 return (dt + 2 * HOUR).replace(fold=1)
4244 # if dt >= fold_stop
4245 return dt + 2 * HOUR
4246
4247
4248 class TestLocalTimeDisambiguation(unittest.TestCase):
4249
4250 def test_vilnius_1941_fromutc(self):
4251 Vilnius = Europe_Vilnius_1941()
4252
4253 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4254 ldt = gdt.astimezone(Vilnius)
4255 self.assertEqual(ldt.strftime("%c %Z%z"),
4256 'Mon Jun 23 23:59:59 1941 MSK+0300')
4257 self.assertEqual(ldt.fold, 0)
4258 self.assertFalse(ldt.dst())
4259
4260 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4261 ldt = gdt.astimezone(Vilnius)
4262 self.assertEqual(ldt.strftime("%c %Z%z"),
4263 'Mon Jun 23 23:00:00 1941 CEST+0200')
4264 self.assertEqual(ldt.fold, 1)
4265 self.assertTrue(ldt.dst())
4266
4267 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4268 ldt = gdt.astimezone(Vilnius)
4269 self.assertEqual(ldt.strftime("%c %Z%z"),
4270 'Tue Jun 24 00:00:00 1941 CEST+0200')
4271 self.assertEqual(ldt.fold, 0)
4272 self.assertTrue(ldt.dst())
4273
4274 def test_vilnius_1941_toutc(self):
4275 Vilnius = Europe_Vilnius_1941()
4276
4277 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4278 gdt = ldt.astimezone(timezone.utc)
4279 self.assertEqual(gdt.strftime("%c %Z"),
4280 'Mon Jun 23 19:59:59 1941 UTC')
4281
4282 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4283 gdt = ldt.astimezone(timezone.utc)
4284 self.assertEqual(gdt.strftime("%c %Z"),
4285 'Mon Jun 23 20:59:59 1941 UTC')
4286
4287 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4288 gdt = ldt.astimezone(timezone.utc)
4289 self.assertEqual(gdt.strftime("%c %Z"),
4290 'Mon Jun 23 21:59:59 1941 UTC')
4291
4292 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4293 gdt = ldt.astimezone(timezone.utc)
4294 self.assertEqual(gdt.strftime("%c %Z"),
4295 'Mon Jun 23 22:00:00 1941 UTC')
4296
4297
4298 def test_constructors(self):
4299 t = time(0, fold=1)
4300 dt = datetime(1, 1, 1, fold=1)
4301 self.assertEqual(t.fold, 1)
4302 self.assertEqual(dt.fold, 1)
4303 with self.assertRaises(TypeError):
4304 time(0, 0, 0, 0, None, 0)
4305
4306 def test_member(self):
4307 dt = datetime(1, 1, 1, fold=1)
4308 t = dt.time()
4309 self.assertEqual(t.fold, 1)
4310 t = dt.timetz()
4311 self.assertEqual(t.fold, 1)
4312
4313 def test_replace(self):
4314 t = time(0)
4315 dt = datetime(1, 1, 1)
4316 self.assertEqual(t.replace(fold=1).fold, 1)
4317 self.assertEqual(dt.replace(fold=1).fold, 1)
4318 self.assertEqual(t.replace(fold=0).fold, 0)
4319 self.assertEqual(dt.replace(fold=0).fold, 0)
4320 # Check that replacement of other fields does not change "fold".
4321 t = t.replace(fold=1, tzinfo=Eastern)
4322 dt = dt.replace(fold=1, tzinfo=Eastern)
4323 self.assertEqual(t.replace(tzinfo=None).fold, 1)
4324 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
4325 # Check that fold is a keyword-only argument
4326 with self.assertRaises(TypeError):
4327 t.replace(1, 1, 1, None, 1)
4328 with self.assertRaises(TypeError):
4329 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
4330
4331 def test_comparison(self):
4332 t = time(0)
4333 dt = datetime(1, 1, 1)
4334 self.assertEqual(t, t.replace(fold=1))
4335 self.assertEqual(dt, dt.replace(fold=1))
4336
4337 def test_hash(self):
4338 t = time(0)
4339 dt = datetime(1, 1, 1)
4340 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4341 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4342
4343 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4344 def test_fromtimestamp(self):
4345 s = 1414906200
4346 dt0 = datetime.fromtimestamp(s)
4347 dt1 = datetime.fromtimestamp(s + 3600)
4348 self.assertEqual(dt0.fold, 0)
4349 self.assertEqual(dt1.fold, 1)
4350
4351 @support.run_with_tz('Australia/Lord_Howe')
4352 def test_fromtimestamp_lord_howe(self):
4353 tm = _time.localtime(1.4e9)
4354 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4355 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4356 # $ TZ=Australia/Lord_Howe date -r 1428158700
4357 # Sun Apr 5 01:45:00 LHDT 2015
4358 # $ TZ=Australia/Lord_Howe date -r 1428160500
4359 # Sun Apr 5 01:45:00 LHST 2015
4360 s = 1428158700
4361 t0 = datetime.fromtimestamp(s)
4362 t1 = datetime.fromtimestamp(s + 1800)
4363 self.assertEqual(t0, t1)
4364 self.assertEqual(t0.fold, 0)
4365 self.assertEqual(t1.fold, 1)
4366
4367
4368 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4369 def test_timestamp(self):
4370 dt0 = datetime(2014, 11, 2, 1, 30)
4371 dt1 = dt0.replace(fold=1)
4372 self.assertEqual(dt0.timestamp() + 3600,
4373 dt1.timestamp())
4374
4375 @support.run_with_tz('Australia/Lord_Howe')
4376 def test_timestamp_lord_howe(self):
4377 tm = _time.localtime(1.4e9)
4378 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4379 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4380 t = datetime(2015, 4, 5, 1, 45)
4381 s0 = t.replace(fold=0).timestamp()
4382 s1 = t.replace(fold=1).timestamp()
4383 self.assertEqual(s0 + 1800, s1)
4384
4385
4386 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4387 def test_astimezone(self):
4388 dt0 = datetime(2014, 11, 2, 1, 30)
4389 dt1 = dt0.replace(fold=1)
4390 # Convert both naive instances to aware.
4391 adt0 = dt0.astimezone()
4392 adt1 = dt1.astimezone()
4393 # Check that the first instance in DST zone and the second in STD
4394 self.assertEqual(adt0.tzname(), 'EDT')
4395 self.assertEqual(adt1.tzname(), 'EST')
4396 self.assertEqual(adt0 + HOUR, adt1)
4397 # Aware instances with fixed offset tzinfo's always have fold=0
4398 self.assertEqual(adt0.fold, 0)
4399 self.assertEqual(adt1.fold, 0)
4400
4401
4402 def test_pickle_fold(self):
4403 t = time(fold=1)
4404 dt = datetime(1, 1, 1, fold=1)
4405 for pickler, unpickler, proto in pickle_choices:
4406 for x in [t, dt]:
4407 s = pickler.dumps(x, proto)
4408 y = unpickler.loads(s)
4409 self.assertEqual(x, y)
4410 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
4411
4412 def test_repr(self):
4413 t = time(fold=1)
4414 dt = datetime(1, 1, 1, fold=1)
4415 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
4416 self.assertEqual(repr(dt),
4417 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
4418
4419 def test_dst(self):
4420 # Let's first establish that things work in regular times.
4421 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resol ution
4422 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4423 self.assertEqual(dt_summer.dst(), HOUR)
4424 self.assertEqual(dt_winter.dst(), ZERO)
4425 # The disambiguation flag is ignored
4426 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
4427 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
4428
4429 # Pick local time in the fold.
4430 for minute in [0, 30, 59]:
4431 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
4432 # With fold=0 (the default) it is in DST.
4433 self.assertEqual(dt.dst(), HOUR)
4434 # With fold=1 it is in STD.
4435 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
4436
4437 # Pick local time in the gap.
4438 for minute in [0, 30, 59]:
4439 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
4440 # With fold=0 (the default) it is in STD.
4441 self.assertEqual(dt.dst(), ZERO)
4442 # With fold=1 it is in DST.
4443 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
4444
4445
4446 def test_utcoffset(self):
4447 # Let's first establish that things work in regular times.
4448 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resol ution
4449 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4450 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
4451 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
4452 # The disambiguation flag is ignored
4453 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
4454 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
4455
4456 def test_fromutc(self):
4457 # Let's first establish that things work in regular times.
4458 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolu tion
4459 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
4460 t_summer = Eastern2.fromutc(u_summer)
4461 t_winter = Eastern2.fromutc(u_winter)
4462 self.assertEqual(t_summer, u_summer - 4 * HOUR)
4463 self.assertEqual(t_winter, u_winter - 5 * HOUR)
4464 self.assertEqual(t_summer.fold, 0)
4465 self.assertEqual(t_winter.fold, 0)
4466
4467 # What happens in the fall-back fold?
4468 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
4469 t0 = Eastern2.fromutc(u)
4470 u += HOUR
4471 t1 = Eastern2.fromutc(u)
4472 self.assertEqual(t0, t1)
4473 self.assertEqual(t0.fold, 0)
4474 self.assertEqual(t1.fold, 1)
4475 # The tricky part is when u is in the local fold:
4476 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
4477 t = Eastern2.fromutc(u)
4478 self.assertEqual((t.day, t.hour), (26, 21))
4479 # .. or gets into the local fold after a standard time adjustment
4480 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
4481 t = Eastern2.fromutc(u)
4482 self.assertEqual((t.day, t.hour), (27, 1))
4483
4484 # What happens in the spring-forward gap?
4485 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
4486 t = Eastern2.fromutc(u)
4487 self.assertEqual((t.day, t.hour), (6, 21))
4488
4489 def test_mixed_compare_regular(self):
4490 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4491 self.assertEqual(t, t.astimezone(timezone.utc))
4492 t = datetime(2000, 6, 1, tzinfo=Eastern2)
4493 self.assertEqual(t, t.astimezone(timezone.utc))
4494
4495 def test_mixed_compare_fold(self):
4496 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4497 t_fold_utc = t_fold.astimezone(timezone.utc)
4498 self.assertNotEqual(t_fold, t_fold_utc)
4499
4500 def test_mixed_compare_gap(self):
4501 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4502 t_gap_utc = t_gap.astimezone(timezone.utc)
4503 self.assertNotEqual(t_gap, t_gap_utc)
4504
4505 def test_hash_aware(self):
4506 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4507 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4508 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4509 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4510 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
4511 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
4512
4513 SEC = timedelta(0, 1)
4514
4515 def pairs(iterable):
4516 a, b = itertools.tee(iterable)
4517 next(b, None)
4518 return zip(a, b)
4519
4520 class ZoneInfo(tzinfo):
4521 zoneroot = '/usr/share/zoneinfo'
4522 def __init__(self, ut, ti):
4523 """
4524
4525 :param ut: array
4526 Array of transition point timestamps
4527 :param ti: list
4528 A list of (offset, isdst, abbr) tuples
4529 :return: None
4530 """
4531 self.ut = ut
4532 self.ti = ti
4533 self.lt = self.invert(ut, ti)
4534
4535 @staticmethod
4536 def invert(ut, ti):
4537 lt = (ut.__copy__(), ut.__copy__())
4538 if ut:
4539 offset = ti[0][0] // SEC
4540 lt[0][0] = max(-2**31, lt[0][0] + offset)
4541 lt[1][0] = max(-2**31, lt[1][0] + offset)
4542 for i in range(1, len(ut)):
4543 lt[0][i] += ti[i-1][0] // SEC
4544 lt[1][i] += ti[i][0] // SEC
4545 return lt
4546
4547 @classmethod
4548 def fromfile(cls, fileobj):
4549 if fileobj.read(4).decode() != "TZif":
4550 raise ValueError("not a zoneinfo file")
4551 fileobj.seek(32)
4552 counts = array('i')
4553 counts.fromfile(fileobj, 3)
4554 if sys.byteorder != 'big':
4555 counts.byteswap()
4556
4557 ut = array('i')
4558 ut.fromfile(fileobj, counts[0])
4559 if sys.byteorder != 'big':
4560 ut.byteswap()
4561
4562 type_indices = array('B')
4563 type_indices.fromfile(fileobj, counts[0])
4564
4565 ttis = []
4566 for i in range(counts[1]):
4567 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
4568
4569 abbrs = fileobj.read(counts[2])
4570
4571 # Convert ttis
4572 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
4573 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
4574 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
4575
4576 ti = [None] * len(ut)
4577 for i, idx in enumerate(type_indices):
4578 ti[i] = ttis[idx]
4579
4580 self = cls(ut, ti)
4581
4582 return self
4583
4584 @classmethod
4585 def fromname(cls, name):
4586 path = os.path.join(cls.zoneroot, name)
4587 with open(path, 'rb') as f:
4588 return cls.fromfile(f)
4589
4590 EPOCHORDINAL = date(1970, 1, 1).toordinal()
4591
4592 def fromutc(self, dt):
4593 """datetime in UTC -> datetime in local time."""
4594
4595 if not isinstance(dt, datetime):
4596 raise TypeError("fromutc() requires a datetime argument")
4597 if dt.tzinfo is not self:
4598 raise ValueError("dt.tzinfo is not self")
4599
4600 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4601 + dt.hour * 3600
4602 + dt.minute * 60
4603 + dt.second)
4604
4605 if timestamp < self.ut[1]:
4606 tti = self.ti[0]
4607 fold = 0
4608 else:
4609 idx = bisect.bisect_right(self.ut, timestamp)
4610 assert self.ut[idx-1] <= timestamp
4611 assert idx == len(self.ut) or timestamp < self.ut[idx]
4612 tti_prev, tti = self.ti[idx-2:idx]
4613 # Detect fold
4614 shift = tti_prev[0] - tti[0]
4615 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
4616 dt += tti[0]
4617 if fold:
4618 return dt.replace(fold=1)
4619 else:
4620 return dt
4621
4622 def _find_ti(self, dt, i):
4623 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4624 + dt.hour * 3600
4625 + dt.minute * 60
4626 + dt.second)
4627 lt = self.lt[dt.fold]
4628 idx = bisect.bisect_right(lt, timestamp)
4629
4630 return self.ti[max(0, idx - 1)][i]
4631
4632 def utcoffset(self, dt):
4633 return self._find_ti(dt, 0)
4634
4635 def dst(self, dt):
4636 isdst = self._find_ti(dt, 1)
4637 # XXX: We cannot accurately determine the "save" value,
4638 # so let's return 1h whenever DST is in effect. Since
4639 # we don't use dst() in fromutc(), it is unlikely that
4640 # it will be needed for anything more than bool(dst()).
4641 return ZERO if isdst else HOUR
4642
4643 def tzname(self, dt):
4644 return self._find_ti(dt, 2)
4645
4646 @classmethod
4647 def zonenames(cls, zonedir=None):
4648 if zonedir is None:
4649 zonedir = cls.zoneroot
4650 for root, _, files in os.walk(zonedir):
4651 for f in files:
4652 p = os.path.join(root, f)
4653 with open(p, 'rb') as o:
4654 magic = o.read(4)
4655 if magic == b'TZif':
4656 yield p[len(zonedir) + 1:]
4657
4658 @classmethod
4659 def stats(cls, start_year=1):
4660 count = gap_count = fold_count = zeros_count = 0
4661 min_gap = min_fold = timedelta.max
4662 max_gap = max_fold = ZERO
4663 min_gap_datetime = max_gap_datetime = datetime.min
4664 min_gap_zone = max_gap_zone = None
4665 min_fold_datetime = max_fold_datetime = datetime.min
4666 min_fold_zone = max_fold_zone = None
4667 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
4668 for zonename in cls.zonenames():
4669 count += 1
4670 tz = cls.fromname(zonename)
4671 for dt, shift in tz.transitions():
4672 if dt < stats_since:
4673 continue
4674 if shift > ZERO:
4675 gap_count += 1
4676 if (shift, dt) > (max_gap, max_gap_datetime):
4677 max_gap = shift
4678 max_gap_zone = zonename
4679 max_gap_datetime = dt
4680 if (shift, datetime.max - dt) < (min_gap, datetime.max - min _gap_datetime):
4681 min_gap = shift
4682 min_gap_zone = zonename
4683 min_gap_datetime = dt
4684 elif shift < ZERO:
4685 fold_count += 1
4686 shift = -shift
4687 if (shift, dt) > (max_fold, max_fold_datetime):
4688 max_fold = shift
4689 max_fold_zone = zonename
4690 max_fold_datetime = dt
4691 if (shift, datetime.max - dt) < (min_fold, datetime.max - mi n_fold_datetime):
4692 min_fold = shift
4693 min_fold_zone = zonename
4694 min_fold_datetime = dt
4695 else:
4696 zeros_count += 1
4697 trans_counts = (gap_count, fold_count, zeros_count)
4698 print("Number of zones: %5d" % count)
4699 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
4700 ((sum(trans_counts),) + trans_counts))
4701 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
4702 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
4703 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime , min_fold_zone))
4704 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime , max_fold_zone))
4705
4706
4707 def transitions(self):
4708 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4709 shift = ti[0] - prev_ti[0]
4710 yield datetime.utcfromtimestamp(t), shift
4711
4712 def nondst_folds(self):
4713 """Find all folds with the same value of isdst on both sides of the tran sition."""
4714 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4715 shift = ti[0] - prev_ti[0]
4716 if shift < ZERO and ti[1] == prev_ti[1]:
4717 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
4718
4719 @classmethod
4720 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
4721 count = 0
4722 for zonename in cls.zonenames():
4723 tz = cls.fromname(zonename)
4724 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
4725 if dt.year < start_year or same_abbr and prev_abbr != abbr:
4726 continue
4727 count += 1
4728 print("%3d) %-30s %s %10s %5s -> %s" %
4729 (count, zonename, dt, shift, prev_abbr, abbr))
4730
4731 def folds(self):
4732 for t, shift in self.transitions():
4733 if shift < ZERO:
4734 yield t, -shift
4735
4736 def gaps(self):
4737 for t, shift in self.transitions():
4738 if shift > ZERO:
4739 yield t, shift
4740
4741 def zeros(self):
4742 for t, shift in self.transitions():
4743 if not shift:
4744 yield t
4745
4746
4747 class ZoneInfoTest(unittest.TestCase):
4748 zonename = 'America/New_York'
4749
4750 def setUp(self):
4751 self.sizeof_time_t = sysconfig.get_config_var('SIZEOF_TIME_T')
4752 if sys.platform == "win32":
4753 self.skipTest("Skipping zoneinfo tests on Windows")
4754 try:
4755 self.tz = ZoneInfo.fromname(self.zonename)
4756 except FileNotFoundError as err:
4757 self.skipTest("Skipping %s: %s" % (self.zonename, err))
4758
4759 def assertEquivDatetimes(self, a, b):
4760 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
4761 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
4762
4763 def test_folds(self):
4764 tz = self.tz
4765 for dt, shift in tz.folds():
4766 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4767 udt = dt + x
4768 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4769 self.assertEqual(ldt.fold, 1)
4770 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4771 self.assertEquivDatetimes(adt, ldt)
4772 utcoffset = ldt.utcoffset()
4773 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
4774 # Round trip
4775 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
4776 udt.replace(tzinfo=timezone.utc))
4777
4778
4779 for x in [-timedelta.resolution, shift]:
4780 udt = dt + x
4781 udt = udt.replace(tzinfo=tz)
4782 ldt = tz.fromutc(udt)
4783 self.assertEqual(ldt.fold, 0)
4784
4785 def test_gaps(self):
4786 tz = self.tz
4787 for dt, shift in tz.gaps():
4788 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4789 udt = dt + x
4790 udt = udt.replace(tzinfo=tz)
4791 ldt = tz.fromutc(udt)
4792 self.assertEqual(ldt.fold, 0)
4793 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4794 self.assertEquivDatetimes(adt, ldt)
4795 utcoffset = ldt.utcoffset()
4796 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=No ne) + utcoffset)
4797 # Create a local time inside the gap
4798 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
4799 self.assertLess(ldt.replace(fold=1).utcoffset(),
4800 ldt.replace(fold=0).utcoffset(),
4801 "At %s." % ldt)
4802
4803 for x in [-timedelta.resolution, shift]:
4804 udt = dt + x
4805 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4806 self.assertEqual(ldt.fold, 0)
4807
4808 def test_system_transitions(self):
4809 if ('Riyadh8' in self.zonename or
4810 # From tzdata NEWS file:
4811 # The files solar87, solar88, and solar89 are no longer distributed.
4812 # They were a negative experiment - that is, a demonstration that
4813 # tz data can represent solar time only with some difficulty and err or.
4814 # Their presence in the distribution caused confusion, as Riyadh
4815 # civil time was generally not solar time in those years.
4816 self.zonename.startswith('right/')):
4817 self.skipTest("Skipping %s" % self.zonename)
4818 tz = self.tz
4819 TZ = os.environ.get('TZ')
4820 os.environ['TZ'] = self.zonename
4821 try:
4822 _time.tzset()
4823 for udt, shift in tz.transitions():
4824 if self.zonename == 'Europe/Tallinn' and udt.date() == date(1999 , 10, 31):
4825 print("Skip %s %s transition" % (self.zonename, udt))
4826 continue
4827 if self.sizeof_time_t == 4 and udt.year >= 2037:
4828 print("Skip %s %s transition for 32-bit time_t" % (self.zone name, udt))
4829 continue
4830 s0 = (udt - datetime(1970, 1, 1)) // SEC
4831 ss = shift // SEC # shift seconds
4832 for x in [-40 * 3600, -20*3600, -1, 0,
4833 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
4834 s = s0 + x
4835 sdt = datetime.fromtimestamp(s)
4836 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
4837 self.assertEquivDatetimes(sdt, tzdt)
4838 s1 = sdt.timestamp()
4839 self.assertEqual(s, s1)
4840 if ss > 0: # gap
4841 # Create local time inside the gap
4842 dt = datetime.fromtimestamp(s0) - shift / 2
4843 ts0 = dt.timestamp()
4844 ts1 = dt.replace(fold=1).timestamp()
4845 self.assertEqual(ts0, s0 + ss / 2)
4846 self.assertEqual(ts1, s0 - ss / 2)
4847 finally:
4848 if TZ is None:
4849 del os.environ['TZ']
4850 else:
4851 os.environ['TZ'] = TZ
4852 _time.tzset()
4853
4854
4855 class ZoneInfoCompleteTest(unittest.TestSuite):
4856 def __init__(self):
4857 tests = []
4858 if is_resource_enabled('tzdata'):
4859 for name in ZoneInfo.zonenames():
4860 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
4861 Test.zonename = name
4862 for method in dir(Test):
4863 if method.startswith('test_'):
4864 tests.append(Test(method))
4865 super().__init__(tests)
4866
4867 # Iran had a sub-minute UTC offset before 1946.
4868 class IranTest(ZoneInfoTest):
4869 zonename = 'Asia/Tehran'
4870
4871 def load_tests(loader, standard_tests, pattern):
4872 standard_tests.addTest(ZoneInfoCompleteTest())
4873 return standard_tests
4874
4875
3976 if __name__ == "__main__": 4876 if __name__ == "__main__":
3977 unittest.main() 4877 unittest.main()
LEFTRIGHT

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+