1# -*- coding: utf-8 -*- 2from __future__ import unicode_literals 3from ._common import PicklableMixin 4from ._common import TZEnvContext, TZWinContext 5from ._common import WarningTestMixin 6from ._common import ComparesEqual 7 8from datetime import datetime, timedelta 9from datetime import time as dt_time 10from datetime import tzinfo 11from six import PY2 12from io import BytesIO, StringIO 13import unittest 14 15import sys 16import base64 17import copy 18import gc 19import weakref 20 21from functools import partial 22 23IS_WIN = sys.platform.startswith('win') 24 25import pytest 26 27# dateutil imports 28from dateutil.relativedelta import relativedelta, SU, TH 29from dateutil.parser import parse 30from dateutil import tz as tz 31from dateutil import zoneinfo 32 33try: 34 from dateutil import tzwin 35except ImportError as e: 36 if IS_WIN: 37 raise e 38 else: 39 pass 40 41MISSING_TARBALL = ("This test fails if you don't have the dateutil " 42 "timezone file installed. Please read the README") 43 44TZFILE_EST5EDT = b""" 45VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAADrAAAABAAAABCeph5wn7rrYKCGAHCh 46ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e 47S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0 48YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg 49yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db 50wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW 518uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b 52YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g 53BGD9cAVQ4GAGQN9wBzDCYAeNGXAJEKRgCa2U8ArwhmAL4IVwDNmi4A3AZ3AOuYTgD6mD8BCZZuAR 54iWXwEnlI4BNpR/AUWSrgFUkp8BY5DOAXKQvwGCIpYBkI7fAaAgtgGvIKcBvh7WAc0exwHcHPYB6x 55znAfobFgIHYA8CGBk2AiVeLwI2qv4CQ1xPAlSpHgJhWm8Ccqc+An/sNwKQpV4CnepXAq6jfgK76H 56cCzTVGAtnmlwLrM2YC9+S3AwkxhgMWdn8DJy+mAzR0nwNFLcYDUnK/A2Mr5gNwcN8Dgb2uA45u/w 57Ofu84DrG0fA7257gPK/ucD27gOA+j9BwP5ti4EBvsnBBhH9gQk+UcENkYWBEL3ZwRURDYEYPWHBH 58JCVgR/h08EkEB2BJ2FbwSuPpYEu4OPBMzQXgTZga8E6s5+BPd/zwUIzJ4FFhGXBSbKvgU0D7cFRM 59jeBVIN1wVixv4FcAv3BYFYxgWOChcFn1bmBawINwW9VQYFypn/BdtTJgXomB8F+VFGBgaWPwYX4w 604GJJRfBjXhLgZCkn8GU99OBmEkRwZx3W4GfyJnBo/bjgadIIcGrdmuBrsepwbMa3YG2RzHBupplg 61b3GucHCGe2BxWsrwcmZdYHM6rPB0Rj9gdRqO8HYvW+B2+nDweA894HjaUvB57x/gero08HvPAeB8 62o1Fwfa7j4H6DM3B/jsXgAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB 63AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB 64AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA 65AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB 66AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU 67AEVQVAAAAAABAAAAAQ== 68""" 69 70EUROPE_HELSINKI = b""" 71VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABQAAAAAAAAB1AAAABQAAAA2kc28Yy85RYMy/hdAV 72I+uQFhPckBcDzZAX876QGOOvkBnToJAaw5GQG7y9EBysrhAdnJ8QHoyQEB98gRAgbHIQIVxjECJM 73VBAjPEUQJCw2ECUcJxAmDBgQJwVDkCf1NJAo5SWQKdUWkCrFB5ArtPiQLKTpkC2U2pAuhMuQL3S8 74kDBkrZAxXdkQMnK0EDM9uxA0UpYQNR2dEDYyeBA2/X8QOBuUkDjdYRA5+3aQOr1DEDvbWJA8pl+Q 75Pbs6kD6GQZA/mxyQQGYjkEGEORBCRgWQQ2QbEEQl55BFQ/0QRgXJkEcj3xBH7uYQSQPBEEnOyBBK 7646MQS66qEEzMv5BNjowQTqyhkE9ubhBQjIOQUVeKkFJsZZBTN2yQVExHkFUXTpBWLCmQVvcwkFgV 77RhBY1xKQWfUoEFq29JBb1QoQXKAREF207BBef/MQX5TOEGBf1RBhfeqQYj+3EGNdzJBkH5kQZT2u 78kGYItZBnHZCQZ+iXkGj9cpBpyHmQat1UkGuoW5BsxnEQbYg9kG6mUxBvaB+QcIY1EHFRPBByZhcQ 79czEeEHRF+RB1EQAQdi8VkHbw4hB4DveQeNDEEHnu2ZB6sKYQe867kHyZwpB9rp2QfnmkkH+Of5AC 80AQIDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQD 81BAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAME 82AwQAABdoAAAAACowAQQAABwgAAkAACowAQQAABwgAAlITVQARUVTVABFRVQAAAAAAQEAAAABAQ== 83""" 84 85NEW_YORK = b""" 86VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAABcAAADrAAAABAAAABCeph5wn7rrYKCGAHCh 87ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e 88S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0 89YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg 90yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db 91wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW 928uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b 93YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g 94BGD9cAVQ4GEGQN9yBzDCYgeNGXMJEKRjCa2U9ArwhmQL4IV1DNmi5Q3AZ3YOuYTmD6mD9xCZZucR 95iWX4EnlI6BNpR/kUWSrpFUkp+RY5DOoXKQv6GCIpaxkI7fsaAgtsGvIKfBvh7Wwc0ex8HcHPbR6x 96zn0fobFtIHYA/SGBk20iVeL+I2qv7iQ1xP4lSpHuJhWm/ycqc+8n/sOAKQpV8CnepYAq6jfxK76H 97gSzTVHItnmmCLrM2cy9+S4MwkxhzMWdoBDJy+nQzR0oENFLcdTUnLAU2Mr51NwcOBjgb2vY45vAG 98Ofu89jrG0gY72572PK/uhj27gPY+j9CGP5ti9kBvsoZBhH92Qk+UhkNkYXZEL3aHRURDd0XzqQdH 99LV/3R9OLB0kNQfdJs20HSu0j90uciYdM1kB3TXxrh062IndPXE2HUJYEd1E8L4dSdeZ3UxwRh1RV 100yHdU+/OHVjWqd1blEAdYHsb3WMTyB1n+qPdapNQHW96K91yEtgddvmz3XmSYB1+eTvdgTbSHYYdr 101d2ItlodjZ013ZA14h2VHL3dl7VqHZycRd2fNPIdpBvN3aa0eh2rm1XdrljsHbM/x9212HQdur9P3 102b1X/B3CPtfdxNeEHcm+X93MVwwd0T3n3dP7fh3Y4lnd23sGHeBh4d3i+o4d5+Fp3ep6Fh3vYPHd8 103fmeHfbged35eSYd/mAB3AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB 104AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB 105AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA 106AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB 107AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU 108AEVQVAAEslgAAAAAAQWk7AEAAAACB4YfggAAAAMJZ1MDAAAABAtIhoQAAAAFDSsLhQAAAAYPDD8G 109AAAABxDtcocAAAAIEs6mCAAAAAkVn8qJAAAACheA/goAAAALGWIxiwAAAAwdJeoMAAAADSHa5Q0A 110AAAOJZ6djgAAAA8nf9EPAAAAECpQ9ZAAAAARLDIpEQAAABIuE1ySAAAAEzDnJBMAAAAUM7hIlAAA 111ABU2jBAVAAAAFkO3G5YAAAAXAAAAAQAAAAE= 112""" 113 114TZICAL_EST5EDT = """ 115BEGIN:VTIMEZONE 116TZID:US-Eastern 117LAST-MODIFIED:19870101T000000Z 118TZURL:http://zones.stds_r_us.net/tz/US-Eastern 119BEGIN:STANDARD 120DTSTART:19671029T020000 121RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 122TZOFFSETFROM:-0400 123TZOFFSETTO:-0500 124TZNAME:EST 125END:STANDARD 126BEGIN:DAYLIGHT 127DTSTART:19870405T020000 128RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 129TZOFFSETFROM:-0500 130TZOFFSETTO:-0400 131TZNAME:EDT 132END:DAYLIGHT 133END:VTIMEZONE 134""" 135 136TZICAL_PST8PDT = """ 137BEGIN:VTIMEZONE 138TZID:US-Pacific 139LAST-MODIFIED:19870101T000000Z 140BEGIN:STANDARD 141DTSTART:19671029T020000 142RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 143TZOFFSETFROM:-0700 144TZOFFSETTO:-0800 145TZNAME:PST 146END:STANDARD 147BEGIN:DAYLIGHT 148DTSTART:19870405T020000 149RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 150TZOFFSETFROM:-0800 151TZOFFSETTO:-0700 152TZNAME:PDT 153END:DAYLIGHT 154END:VTIMEZONE 155""" 156 157EST_TUPLE = ('EST', timedelta(hours=-5), timedelta(hours=0)) 158EDT_TUPLE = ('EDT', timedelta(hours=-4), timedelta(hours=1)) 159 160SUPPORTS_SUB_MINUTE_OFFSETS = sys.version_info >= (3, 6) 161 162 163### 164# Helper functions 165def get_timezone_tuple(dt): 166 """Retrieve a (tzname, utcoffset, dst) tuple for a given DST""" 167 return dt.tzname(), dt.utcoffset(), dt.dst() 168 169 170### 171# Mix-ins 172class context_passthrough(object): 173 def __init__(*args, **kwargs): 174 pass 175 176 def __enter__(*args, **kwargs): 177 pass 178 179 def __exit__(*args, **kwargs): 180 pass 181 182 183class TzFoldMixin(object): 184 """ Mix-in class for testing ambiguous times """ 185 def gettz(self, tzname): 186 raise NotImplementedError 187 188 def _get_tzname(self, tzname): 189 return tzname 190 191 def _gettz_context(self, tzname): 192 return context_passthrough() 193 194 def testFoldPositiveUTCOffset(self): 195 # Test that we can resolve ambiguous times 196 tzname = self._get_tzname('Australia/Sydney') 197 198 with self._gettz_context(tzname): 199 SYD = self.gettz(tzname) 200 201 t0_u = datetime(2012, 3, 31, 15, 30, tzinfo=tz.tzutc()) # AEST 202 t1_u = datetime(2012, 3, 31, 16, 30, tzinfo=tz.tzutc()) # AEDT 203 204 t0_syd0 = t0_u.astimezone(SYD) 205 t1_syd1 = t1_u.astimezone(SYD) 206 207 self.assertEqual(t0_syd0.replace(tzinfo=None), 208 datetime(2012, 4, 1, 2, 30)) 209 210 self.assertEqual(t1_syd1.replace(tzinfo=None), 211 datetime(2012, 4, 1, 2, 30)) 212 213 self.assertEqual(t0_syd0.utcoffset(), timedelta(hours=11)) 214 self.assertEqual(t1_syd1.utcoffset(), timedelta(hours=10)) 215 216 def testGapPositiveUTCOffset(self): 217 # Test that we don't have a problem around gaps. 218 tzname = self._get_tzname('Australia/Sydney') 219 220 with self._gettz_context(tzname): 221 SYD = self.gettz(tzname) 222 223 t0_u = datetime(2012, 10, 6, 15, 30, tzinfo=tz.tzutc()) # AEST 224 t1_u = datetime(2012, 10, 6, 16, 30, tzinfo=tz.tzutc()) # AEDT 225 226 t0 = t0_u.astimezone(SYD) 227 t1 = t1_u.astimezone(SYD) 228 229 self.assertEqual(t0.replace(tzinfo=None), 230 datetime(2012, 10, 7, 1, 30)) 231 232 self.assertEqual(t1.replace(tzinfo=None), 233 datetime(2012, 10, 7, 3, 30)) 234 235 self.assertEqual(t0.utcoffset(), timedelta(hours=10)) 236 self.assertEqual(t1.utcoffset(), timedelta(hours=11)) 237 238 def testFoldNegativeUTCOffset(self): 239 # Test that we can resolve ambiguous times 240 tzname = self._get_tzname('America/Toronto') 241 242 with self._gettz_context(tzname): 243 TOR = self.gettz(tzname) 244 245 t0_u = datetime(2011, 11, 6, 5, 30, tzinfo=tz.tzutc()) 246 t1_u = datetime(2011, 11, 6, 6, 30, tzinfo=tz.tzutc()) 247 248 t0_tor = t0_u.astimezone(TOR) 249 t1_tor = t1_u.astimezone(TOR) 250 251 self.assertEqual(t0_tor.replace(tzinfo=None), 252 datetime(2011, 11, 6, 1, 30)) 253 254 self.assertEqual(t1_tor.replace(tzinfo=None), 255 datetime(2011, 11, 6, 1, 30)) 256 257 self.assertNotEqual(t0_tor.tzname(), t1_tor.tzname()) 258 self.assertEqual(t0_tor.utcoffset(), timedelta(hours=-4.0)) 259 self.assertEqual(t1_tor.utcoffset(), timedelta(hours=-5.0)) 260 261 def testGapNegativeUTCOffset(self): 262 # Test that we don't have a problem around gaps. 263 tzname = self._get_tzname('America/Toronto') 264 265 with self._gettz_context(tzname): 266 TOR = self.gettz(tzname) 267 268 t0_u = datetime(2011, 3, 13, 6, 30, tzinfo=tz.tzutc()) 269 t1_u = datetime(2011, 3, 13, 7, 30, tzinfo=tz.tzutc()) 270 271 t0 = t0_u.astimezone(TOR) 272 t1 = t1_u.astimezone(TOR) 273 274 self.assertEqual(t0.replace(tzinfo=None), 275 datetime(2011, 3, 13, 1, 30)) 276 277 self.assertEqual(t1.replace(tzinfo=None), 278 datetime(2011, 3, 13, 3, 30)) 279 280 self.assertNotEqual(t0, t1) 281 self.assertEqual(t0.utcoffset(), timedelta(hours=-5.0)) 282 self.assertEqual(t1.utcoffset(), timedelta(hours=-4.0)) 283 284 def testFoldLondon(self): 285 tzname = self._get_tzname('Europe/London') 286 287 with self._gettz_context(tzname): 288 LON = self.gettz(tzname) 289 UTC = tz.tzutc() 290 291 t0_u = datetime(2013, 10, 27, 0, 30, tzinfo=UTC) # BST 292 t1_u = datetime(2013, 10, 27, 1, 30, tzinfo=UTC) # GMT 293 294 t0 = t0_u.astimezone(LON) 295 t1 = t1_u.astimezone(LON) 296 297 self.assertEqual(t0.replace(tzinfo=None), 298 datetime(2013, 10, 27, 1, 30)) 299 300 self.assertEqual(t1.replace(tzinfo=None), 301 datetime(2013, 10, 27, 1, 30)) 302 303 self.assertEqual(t0.utcoffset(), timedelta(hours=1)) 304 self.assertEqual(t1.utcoffset(), timedelta(hours=0)) 305 306 def testFoldIndependence(self): 307 tzname = self._get_tzname('America/New_York') 308 309 with self._gettz_context(tzname): 310 NYC = self.gettz(tzname) 311 UTC = tz.tzutc() 312 hour = timedelta(hours=1) 313 314 # Firmly 2015-11-01 0:30 EDT-4 315 pre_dst = datetime(2015, 11, 1, 0, 30, tzinfo=NYC) 316 317 # Ambiguous between 2015-11-01 1:30 EDT-4 and 2015-11-01 1:30 EST-5 318 in_dst = pre_dst + hour 319 in_dst_tzname_0 = in_dst.tzname() # Stash the tzname - EDT 320 321 # Doing the arithmetic in UTC creates a date that is unambiguously 322 # 2015-11-01 1:30 EDT-5 323 in_dst_via_utc = (pre_dst.astimezone(UTC) + 2*hour).astimezone(NYC) 324 325 # Make sure the dates are actually ambiguous 326 self.assertEqual(in_dst, in_dst_via_utc) 327 328 # Make sure we got the right folding behavior 329 self.assertNotEqual(in_dst_via_utc.tzname(), in_dst_tzname_0) 330 331 # Now check to make sure in_dst's tzname hasn't changed 332 self.assertEqual(in_dst_tzname_0, in_dst.tzname()) 333 334 def testInZoneFoldEquality(self): 335 # Two datetimes in the same zone are considered to be equal if their 336 # wall times are equal, even if they have different absolute times. 337 338 tzname = self._get_tzname('America/New_York') 339 340 with self._gettz_context(tzname): 341 NYC = self.gettz(tzname) 342 UTC = tz.tzutc() 343 344 dt0 = datetime(2011, 11, 6, 1, 30, tzinfo=NYC) 345 dt1 = tz.enfold(dt0, fold=1) 346 347 # Make sure these actually represent different times 348 self.assertNotEqual(dt0.astimezone(UTC), dt1.astimezone(UTC)) 349 350 # Test that they compare equal 351 self.assertEqual(dt0, dt1) 352 353 def _test_ambiguous_time(self, dt, tzid, ambiguous): 354 # This is a test to check that the individual is_ambiguous values 355 # on the _tzinfo subclasses work. 356 tzname = self._get_tzname(tzid) 357 358 with self._gettz_context(tzname): 359 tzi = self.gettz(tzname) 360 361 self.assertEqual(tz.datetime_ambiguous(dt, tz=tzi), ambiguous) 362 363 def testAmbiguousNegativeUTCOffset(self): 364 self._test_ambiguous_time(datetime(2015, 11, 1, 1, 30), 365 'America/New_York', True) 366 367 def testAmbiguousPositiveUTCOffset(self): 368 self._test_ambiguous_time(datetime(2012, 4, 1, 2, 30), 369 'Australia/Sydney', True) 370 371 def testUnambiguousNegativeUTCOffset(self): 372 self._test_ambiguous_time(datetime(2015, 11, 1, 2, 30), 373 'America/New_York', False) 374 375 def testUnambiguousPositiveUTCOffset(self): 376 self._test_ambiguous_time(datetime(2012, 4, 1, 3, 30), 377 'Australia/Sydney', False) 378 379 def testUnambiguousGapNegativeUTCOffset(self): 380 # Imaginary time 381 self._test_ambiguous_time(datetime(2011, 3, 13, 2, 30), 382 'America/New_York', False) 383 384 def testUnambiguousGapPositiveUTCOffset(self): 385 # Imaginary time 386 self._test_ambiguous_time(datetime(2012, 10, 7, 2, 30), 387 'Australia/Sydney', False) 388 389 def _test_imaginary_time(self, dt, tzid, exists): 390 tzname = self._get_tzname(tzid) 391 with self._gettz_context(tzname): 392 tzi = self.gettz(tzname) 393 394 self.assertEqual(tz.datetime_exists(dt, tz=tzi), exists) 395 396 def testImaginaryNegativeUTCOffset(self): 397 self._test_imaginary_time(datetime(2011, 3, 13, 2, 30), 398 'America/New_York', False) 399 400 def testNotImaginaryNegativeUTCOffset(self): 401 self._test_imaginary_time(datetime(2011, 3, 13, 1, 30), 402 'America/New_York', True) 403 404 def testImaginaryPositiveUTCOffset(self): 405 self._test_imaginary_time(datetime(2012, 10, 7, 2, 30), 406 'Australia/Sydney', False) 407 408 def testNotImaginaryPositiveUTCOffset(self): 409 self._test_imaginary_time(datetime(2012, 10, 7, 1, 30), 410 'Australia/Sydney', True) 411 412 def testNotImaginaryFoldNegativeUTCOffset(self): 413 self._test_imaginary_time(datetime(2015, 11, 1, 1, 30), 414 'America/New_York', True) 415 416 def testNotImaginaryFoldPositiveUTCOffset(self): 417 self._test_imaginary_time(datetime(2012, 4, 1, 3, 30), 418 'Australia/Sydney', True) 419 420 @unittest.skip("Known failure in Python 3.6.") 421 def testEqualAmbiguousComparison(self): 422 tzname = self._get_tzname('Australia/Sydney') 423 424 with self._gettz_context(tzname): 425 SYD0 = self.gettz(tzname) 426 SYD1 = self.gettz(tzname) 427 428 t0_u = datetime(2012, 3, 31, 14, 30, tzinfo=tz.tzutc()) # AEST 429 430 t0_syd0 = t0_u.astimezone(SYD0) 431 t0_syd1 = t0_u.astimezone(SYD1) 432 433 # This is considered an "inter-zone comparison" because it's an 434 # ambiguous datetime. 435 self.assertEqual(t0_syd0, t0_syd1) 436 437 438class TzWinFoldMixin(object): 439 def get_args(self, tzname): 440 return (tzname, ) 441 442 class context(object): 443 def __init__(*args, **kwargs): 444 pass 445 446 def __enter__(*args, **kwargs): 447 pass 448 449 def __exit__(*args, **kwargs): 450 pass 451 452 def get_utc_transitions(self, tzi, year, gap): 453 dston, dstoff = tzi.transitions(year) 454 if gap: 455 t_n = dston - timedelta(minutes=30) 456 457 t0_u = t_n.replace(tzinfo=tzi).astimezone(tz.tzutc()) 458 t1_u = t0_u + timedelta(hours=1) 459 else: 460 # Get 1 hour before the first ambiguous date 461 t_n = dstoff - timedelta(minutes=30) 462 463 t0_u = t_n.replace(tzinfo=tzi).astimezone(tz.tzutc()) 464 t_n += timedelta(hours=1) # Naive ambiguous date 465 t0_u = t0_u + timedelta(hours=1) # First ambiguous date 466 t1_u = t0_u + timedelta(hours=1) # Second ambiguous date 467 468 return t_n, t0_u, t1_u 469 470 def testFoldPositiveUTCOffset(self): 471 # Test that we can resolve ambiguous times 472 tzname = 'AUS Eastern Standard Time' 473 args = self.get_args(tzname) 474 475 with self.context(tzname): 476 # Calling fromutc() alters the tzfile object 477 SYD = self.tzclass(*args) 478 479 # Get the transition time in UTC from the object, because 480 # Windows doesn't store historical info 481 t_n, t0_u, t1_u = self.get_utc_transitions(SYD, 2012, False) 482 483 # Using fresh tzfiles 484 t0_syd = t0_u.astimezone(SYD) 485 t1_syd = t1_u.astimezone(SYD) 486 487 self.assertEqual(t0_syd.replace(tzinfo=None), t_n) 488 489 self.assertEqual(t1_syd.replace(tzinfo=None), t_n) 490 491 self.assertEqual(t0_syd.utcoffset(), timedelta(hours=11)) 492 self.assertEqual(t1_syd.utcoffset(), timedelta(hours=10)) 493 self.assertNotEqual(t0_syd.tzname(), t1_syd.tzname()) 494 495 def testGapPositiveUTCOffset(self): 496 # Test that we don't have a problem around gaps. 497 tzname = 'AUS Eastern Standard Time' 498 args = self.get_args(tzname) 499 500 with self.context(tzname): 501 SYD = self.tzclass(*args) 502 503 t_n, t0_u, t1_u = self.get_utc_transitions(SYD, 2012, True) 504 505 t0 = t0_u.astimezone(SYD) 506 t1 = t1_u.astimezone(SYD) 507 508 self.assertEqual(t0.replace(tzinfo=None), t_n) 509 510 self.assertEqual(t1.replace(tzinfo=None), t_n + timedelta(hours=2)) 511 512 self.assertEqual(t0.utcoffset(), timedelta(hours=10)) 513 self.assertEqual(t1.utcoffset(), timedelta(hours=11)) 514 515 def testFoldNegativeUTCOffset(self): 516 # Test that we can resolve ambiguous times 517 tzname = 'Eastern Standard Time' 518 args = self.get_args(tzname) 519 520 with self.context(tzname): 521 TOR = self.tzclass(*args) 522 523 t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, False) 524 525 t0_tor = t0_u.astimezone(TOR) 526 t1_tor = t1_u.astimezone(TOR) 527 528 self.assertEqual(t0_tor.replace(tzinfo=None), t_n) 529 self.assertEqual(t1_tor.replace(tzinfo=None), t_n) 530 531 self.assertNotEqual(t0_tor.tzname(), t1_tor.tzname()) 532 self.assertEqual(t0_tor.utcoffset(), timedelta(hours=-4.0)) 533 self.assertEqual(t1_tor.utcoffset(), timedelta(hours=-5.0)) 534 535 def testGapNegativeUTCOffset(self): 536 # Test that we don't have a problem around gaps. 537 tzname = 'Eastern Standard Time' 538 args = self.get_args(tzname) 539 540 with self.context(tzname): 541 TOR = self.tzclass(*args) 542 543 t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, True) 544 545 t0 = t0_u.astimezone(TOR) 546 t1 = t1_u.astimezone(TOR) 547 548 self.assertEqual(t0.replace(tzinfo=None), 549 t_n) 550 551 self.assertEqual(t1.replace(tzinfo=None), 552 t_n + timedelta(hours=2)) 553 554 self.assertNotEqual(t0.tzname(), t1.tzname()) 555 self.assertEqual(t0.utcoffset(), timedelta(hours=-5.0)) 556 self.assertEqual(t1.utcoffset(), timedelta(hours=-4.0)) 557 558 def testFoldIndependence(self): 559 tzname = 'Eastern Standard Time' 560 args = self.get_args(tzname) 561 562 with self.context(tzname): 563 NYC = self.tzclass(*args) 564 UTC = tz.tzutc() 565 hour = timedelta(hours=1) 566 567 # Firmly 2015-11-01 0:30 EDT-4 568 t_n, t0_u, t1_u = self.get_utc_transitions(NYC, 2015, False) 569 570 pre_dst = (t_n - hour).replace(tzinfo=NYC) 571 572 # Currently, there's no way around the fact that this resolves to an 573 # ambiguous date, which defaults to EST. I'm not hard-coding in the 574 # answer, though, because the preferred behavior would be that this 575 # results in a time on the EDT side. 576 577 # Ambiguous between 2015-11-01 1:30 EDT-4 and 2015-11-01 1:30 EST-5 578 in_dst = pre_dst + hour 579 in_dst_tzname_0 = in_dst.tzname() # Stash the tzname - EDT 580 581 # Doing the arithmetic in UTC creates a date that is unambiguously 582 # 2015-11-01 1:30 EDT-5 583 in_dst_via_utc = (pre_dst.astimezone(UTC) + 2*hour).astimezone(NYC) 584 585 # Make sure we got the right folding behavior 586 self.assertNotEqual(in_dst_via_utc.tzname(), in_dst_tzname_0) 587 588 # Now check to make sure in_dst's tzname hasn't changed 589 self.assertEqual(in_dst_tzname_0, in_dst.tzname()) 590 591 def testInZoneFoldEquality(self): 592 # Two datetimes in the same zone are considered to be equal if their 593 # wall times are equal, even if they have different absolute times. 594 tzname = 'Eastern Standard Time' 595 args = self.get_args(tzname) 596 597 with self.context(tzname): 598 NYC = self.tzclass(*args) 599 UTC = tz.tzutc() 600 601 t_n, t0_u, t1_u = self.get_utc_transitions(NYC, 2011, False) 602 603 dt0 = t_n.replace(tzinfo=NYC) 604 dt1 = tz.enfold(dt0, fold=1) 605 606 # Make sure these actually represent different times 607 self.assertNotEqual(dt0.astimezone(UTC), dt1.astimezone(UTC)) 608 609 # Test that they compare equal 610 self.assertEqual(dt0, dt1) 611 612### 613# Test Cases 614class TzUTCTest(unittest.TestCase): 615 def testSingleton(self): 616 UTC_0 = tz.tzutc() 617 UTC_1 = tz.tzutc() 618 619 self.assertIs(UTC_0, UTC_1) 620 621 def testOffset(self): 622 ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc()) 623 624 self.assertEqual(ct.utcoffset(), timedelta(seconds=0)) 625 626 def testDst(self): 627 ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc()) 628 629 self.assertEqual(ct.dst(), timedelta(seconds=0)) 630 631 def testTzName(self): 632 ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc()) 633 self.assertEqual(ct.tzname(), 'UTC') 634 635 def testEquality(self): 636 UTC0 = tz.tzutc() 637 UTC1 = tz.tzutc() 638 639 self.assertEqual(UTC0, UTC1) 640 641 def testInequality(self): 642 UTC = tz.tzutc() 643 UTCp4 = tz.tzoffset('UTC+4', 14400) 644 645 self.assertNotEqual(UTC, UTCp4) 646 647 def testInequalityInteger(self): 648 self.assertFalse(tz.tzutc() == 7) 649 self.assertNotEqual(tz.tzutc(), 7) 650 651 def testInequalityUnsupported(self): 652 self.assertEqual(tz.tzutc(), ComparesEqual) 653 654 def testRepr(self): 655 UTC = tz.tzutc() 656 self.assertEqual(repr(UTC), 'tzutc()') 657 658 def testTimeOnlyUTC(self): 659 # https://github.com/dateutil/dateutil/issues/132 660 # tzutc doesn't care 661 tz_utc = tz.tzutc() 662 self.assertEqual(dt_time(13, 20, tzinfo=tz_utc).utcoffset(), 663 timedelta(0)) 664 665 def testAmbiguity(self): 666 # Pick an arbitrary datetime, this should always return False. 667 dt = datetime(2011, 9, 1, 2, 30, tzinfo=tz.tzutc()) 668 669 self.assertFalse(tz.datetime_ambiguous(dt)) 670 671 672@pytest.mark.tzoffset 673class TzOffsetTest(unittest.TestCase): 674 def testTimedeltaOffset(self): 675 est = tz.tzoffset('EST', timedelta(hours=-5)) 676 est_s = tz.tzoffset('EST', -18000) 677 678 self.assertEqual(est, est_s) 679 680 def testTzNameNone(self): 681 gmt5 = tz.tzoffset(None, -18000) # -5:00 682 self.assertIs(datetime(2003, 10, 26, 0, 0, tzinfo=gmt5).tzname(), 683 None) 684 685 def testTimeOnlyOffset(self): 686 # tzoffset doesn't care 687 tz_offset = tz.tzoffset('+3', 3600) 688 self.assertEqual(dt_time(13, 20, tzinfo=tz_offset).utcoffset(), 689 timedelta(seconds=3600)) 690 691 def testTzOffsetRepr(self): 692 tname = 'EST' 693 tzo = tz.tzoffset(tname, -5 * 3600) 694 self.assertEqual(repr(tzo), "tzoffset(" + repr(tname) + ", -18000)") 695 696 def testEquality(self): 697 utc = tz.tzoffset('UTC', 0) 698 gmt = tz.tzoffset('GMT', 0) 699 700 self.assertEqual(utc, gmt) 701 702 def testUTCEquality(self): 703 utc = tz.tzutc() 704 o_utc = tz.tzoffset('UTC', 0) 705 706 self.assertEqual(utc, o_utc) 707 self.assertEqual(o_utc, utc) 708 709 def testInequalityInvalid(self): 710 tzo = tz.tzoffset('-3', -3 * 3600) 711 self.assertFalse(tzo == -3) 712 self.assertNotEqual(tzo, -3) 713 714 def testInequalityUnsupported(self): 715 tzo = tz.tzoffset('-5', -5 * 3600) 716 717 self.assertTrue(tzo == ComparesEqual) 718 self.assertFalse(tzo != ComparesEqual) 719 self.assertEqual(tzo, ComparesEqual) 720 721 def testAmbiguity(self): 722 # Pick an arbitrary datetime, this should always return False. 723 dt = datetime(2011, 9, 1, 2, 30, tzinfo=tz.tzoffset("EST", -5 * 3600)) 724 725 self.assertFalse(tz.datetime_ambiguous(dt)) 726 727 def testTzOffsetInstance(self): 728 tz1 = tz.tzoffset.instance('EST', timedelta(hours=-5)) 729 tz2 = tz.tzoffset.instance('EST', timedelta(hours=-5)) 730 731 assert tz1 is not tz2 732 733 def testTzOffsetSingletonDifferent(self): 734 tz1 = tz.tzoffset('EST', timedelta(hours=-5)) 735 tz2 = tz.tzoffset('EST', -18000) 736 737 assert tz1 is tz2 738 739 740@pytest.mark.smoke 741@pytest.mark.tzoffset 742def test_tzoffset_weakref(): 743 UTC1 = tz.tzoffset('UTC', 0) 744 UTC_ref = weakref.ref(tz.tzoffset('UTC', 0)) 745 UTC1 is UTC_ref() 746 del UTC1 747 gc.collect() 748 749 assert UTC_ref() is not None # Should be in the strong cache 750 assert UTC_ref() is tz.tzoffset('UTC', 0) 751 752 # Fill the strong cache with other items 753 for offset in range(5,15): 754 tz.tzoffset('RandomZone', offset) 755 756 gc.collect() 757 assert UTC_ref() is None 758 assert UTC_ref() is not tz.tzoffset('UTC', 0) 759 760 761@pytest.mark.tzoffset 762@pytest.mark.parametrize('args', [ 763 ('UTC', 0), 764 ('EST', -18000), 765 ('EST', timedelta(hours=-5)), 766 (None, timedelta(hours=3)), 767]) 768def test_tzoffset_singleton(args): 769 tz1 = tz.tzoffset(*args) 770 tz2 = tz.tzoffset(*args) 771 772 assert tz1 is tz2 773 774 775@pytest.mark.tzoffset 776@pytest.mark.skipif(not SUPPORTS_SUB_MINUTE_OFFSETS, 777 reason='Sub-minute offsets not supported') 778def test_tzoffset_sub_minute(): 779 delta = timedelta(hours=12, seconds=30) 780 test_datetime = datetime(2000, 1, 1, tzinfo=tz.tzoffset(None, delta)) 781 assert test_datetime.utcoffset() == delta 782 783 784@pytest.mark.tzoffset 785@pytest.mark.skipif(SUPPORTS_SUB_MINUTE_OFFSETS, 786 reason='Sub-minute offsets supported') 787def test_tzoffset_sub_minute_rounding(): 788 delta = timedelta(hours=12, seconds=30) 789 test_date = datetime(2000, 1, 1, tzinfo=tz.tzoffset(None, delta)) 790 assert test_date.utcoffset() == timedelta(hours=12, minutes=1) 791 792 793@pytest.mark.tzlocal 794class TzLocalTest(unittest.TestCase): 795 def testEquality(self): 796 tz1 = tz.tzlocal() 797 tz2 = tz.tzlocal() 798 799 # Explicitly calling == and != here to ensure the operators work 800 self.assertTrue(tz1 == tz2) 801 self.assertFalse(tz1 != tz2) 802 803 def testInequalityFixedOffset(self): 804 tzl = tz.tzlocal() 805 tzos = tz.tzoffset('LST', tzl._std_offset.total_seconds()) 806 tzod = tz.tzoffset('LDT', tzl._std_offset.total_seconds()) 807 808 self.assertFalse(tzl == tzos) 809 self.assertFalse(tzl == tzod) 810 self.assertTrue(tzl != tzos) 811 self.assertTrue(tzl != tzod) 812 813 def testInequalityInvalid(self): 814 tzl = tz.tzlocal() 815 816 self.assertTrue(tzl != 1) 817 self.assertFalse(tzl == 1) 818 819 # TODO: Use some sort of universal local mocking so that it's clear 820 # that we're expecting tzlocal to *not* be Pacific/Kiritimati 821 LINT = tz.gettz('Pacific/Kiritimati') 822 self.assertTrue(tzl != LINT) 823 self.assertFalse(tzl == LINT) 824 825 def testInequalityUnsupported(self): 826 tzl = tz.tzlocal() 827 828 self.assertTrue(tzl == ComparesEqual) 829 self.assertFalse(tzl != ComparesEqual) 830 831 def testRepr(self): 832 tzl = tz.tzlocal() 833 834 self.assertEqual(repr(tzl), 'tzlocal()') 835 836 837@pytest.mark.parametrize('args,kwargs', [ 838 (('EST', -18000), {}), 839 (('EST', timedelta(hours=-5)), {}), 840 (('EST',), {'offset': -18000}), 841 (('EST',), {'offset': timedelta(hours=-5)}), 842 (tuple(), {'name': 'EST', 'offset': -18000}) 843]) 844def test_tzoffset_is(args, kwargs): 845 tz_ref = tz.tzoffset('EST', -18000) 846 assert tz.tzoffset(*args, **kwargs) is tz_ref 847 848 849def test_tzoffset_is_not(): 850 assert tz.tzoffset('EDT', -14400) is not tz.tzoffset('EST', -18000) 851 852 853@pytest.mark.tzlocal 854@unittest.skipIf(IS_WIN, "requires Unix") 855@unittest.skipUnless(TZEnvContext.tz_change_allowed(), 856 TZEnvContext.tz_change_disallowed_message()) 857class TzLocalNixTest(unittest.TestCase, TzFoldMixin): 858 # This is a set of tests for `tzlocal()` on *nix systems 859 860 # POSIX string indicating change to summer time on the 2nd Sunday in March 861 # at 2AM, and ending the 1st Sunday in November at 2AM. (valid >= 2007) 862 TZ_EST = 'EST+5EDT,M3.2.0/2,M11.1.0/2' 863 864 # POSIX string for AEST/AEDT (valid >= 2008) 865 TZ_AEST = 'AEST-10AEDT,M10.1.0/2,M4.1.0/3' 866 867 # POSIX string for BST/GMT 868 TZ_LON = 'GMT0BST,M3.5.0,M10.5.0' 869 870 # POSIX string for UTC 871 UTC = 'UTC' 872 873 def gettz(self, tzname): 874 # Actual time zone changes are handled by the _gettz_context function 875 return tz.tzlocal() 876 877 def _gettz_context(self, tzname): 878 tzname_map = {'Australia/Sydney': self.TZ_AEST, 879 'America/Toronto': self.TZ_EST, 880 'America/New_York': self.TZ_EST, 881 'Europe/London': self.TZ_LON} 882 883 return TZEnvContext(tzname_map.get(tzname, tzname)) 884 885 def _testTzFunc(self, tzval, func, std_val, dst_val): 886 """ 887 This generates tests about how the behavior of a function ``func`` 888 changes between STD and DST (e.g. utcoffset, tzname, dst). 889 890 It assume that DST starts the 2nd Sunday in March and ends the 1st 891 Sunday in November 892 """ 893 with TZEnvContext(tzval): 894 dt1 = datetime(2015, 2, 1, 12, 0, tzinfo=tz.tzlocal()) # STD 895 dt2 = datetime(2015, 5, 1, 12, 0, tzinfo=tz.tzlocal()) # DST 896 897 self.assertEqual(func(dt1), std_val) 898 self.assertEqual(func(dt2), dst_val) 899 900 def _testTzName(self, tzval, std_name, dst_name): 901 func = datetime.tzname 902 903 self._testTzFunc(tzval, func, std_name, dst_name) 904 905 def testTzNameDST(self): 906 # Test tzname in a zone with DST 907 self._testTzName(self.TZ_EST, 'EST', 'EDT') 908 909 def testTzNameUTC(self): 910 # Test tzname in a zone without DST 911 self._testTzName(self.UTC, 'UTC', 'UTC') 912 913 def _testOffset(self, tzval, std_off, dst_off): 914 func = datetime.utcoffset 915 916 self._testTzFunc(tzval, func, std_off, dst_off) 917 918 def testOffsetDST(self): 919 self._testOffset(self.TZ_EST, timedelta(hours=-5), timedelta(hours=-4)) 920 921 def testOffsetUTC(self): 922 self._testOffset(self.UTC, timedelta(0), timedelta(0)) 923 924 def _testDST(self, tzval, dst_dst): 925 func = datetime.dst 926 std_dst = timedelta(0) 927 928 self._testTzFunc(tzval, func, std_dst, dst_dst) 929 930 def testDSTDST(self): 931 self._testDST(self.TZ_EST, timedelta(hours=1)) 932 933 def testDSTUTC(self): 934 self._testDST(self.UTC, timedelta(0)) 935 936 def testTimeOnlyOffsetLocalUTC(self): 937 with TZEnvContext(self.UTC): 938 self.assertEqual(dt_time(13, 20, tzinfo=tz.tzlocal()).utcoffset(), 939 timedelta(0)) 940 941 def testTimeOnlyOffsetLocalDST(self): 942 with TZEnvContext(self.TZ_EST): 943 self.assertIs(dt_time(13, 20, tzinfo=tz.tzlocal()).utcoffset(), 944 None) 945 946 def testTimeOnlyDSTLocalUTC(self): 947 with TZEnvContext(self.UTC): 948 self.assertEqual(dt_time(13, 20, tzinfo=tz.tzlocal()).dst(), 949 timedelta(0)) 950 951 def testTimeOnlyDSTLocalDST(self): 952 with TZEnvContext(self.TZ_EST): 953 self.assertIs(dt_time(13, 20, tzinfo=tz.tzlocal()).dst(), 954 None) 955 956 def testUTCEquality(self): 957 with TZEnvContext(self.UTC): 958 assert tz.tzlocal() == tz.tzutc() 959 960 961# TODO: Maybe a better hack than this? 962def mark_tzlocal_nix(f): 963 marks = [ 964 pytest.mark.tzlocal, 965 pytest.mark.skipif(IS_WIN, reason='requires Unix'), 966 pytest.mark.skipif(not TZEnvContext.tz_change_allowed, 967 reason=TZEnvContext.tz_change_disallowed_message()) 968 ] 969 970 for mark in reversed(marks): 971 f = mark(f) 972 973 return f 974 975 976@mark_tzlocal_nix 977@pytest.mark.parametrize('tzvar', ['UTC', 'GMT0', 'UTC0']) 978def test_tzlocal_utc_equal(tzvar): 979 with TZEnvContext(tzvar): 980 assert tz.tzlocal() == tz.UTC 981 982 983@mark_tzlocal_nix 984@pytest.mark.parametrize('tzvar', [ 985 'Europe/London', 'America/New_York', 986 'GMT0BST', 'EST5EDT']) 987def test_tzlocal_utc_unequal(tzvar): 988 with TZEnvContext(tzvar): 989 assert tz.tzlocal() != tz.UTC 990 991 992@mark_tzlocal_nix 993def test_tzlocal_local_time_trim_colon(): 994 with TZEnvContext(':/etc/localtime'): 995 assert tz.gettz() is not None 996 997 998@mark_tzlocal_nix 999@pytest.mark.parametrize('tzvar, tzoff', [ 1000 ('EST5', tz.tzoffset('EST', -18000)), 1001 ('GMT', tz.tzoffset('GMT', 0)), 1002 ('YAKT-9', tz.tzoffset('YAKT', timedelta(hours=9))), 1003 ('JST-9', tz.tzoffset('JST', timedelta(hours=9))), 1004]) 1005def test_tzlocal_offset_equal(tzvar, tzoff): 1006 with TZEnvContext(tzvar): 1007 # Including both to test both __eq__ and __ne__ 1008 assert tz.tzlocal() == tzoff 1009 assert not (tz.tzlocal() != tzoff) 1010 1011 1012@mark_tzlocal_nix 1013@pytest.mark.parametrize('tzvar, tzoff', [ 1014 ('EST5EDT', tz.tzoffset('EST', -18000)), 1015 ('GMT0BST', tz.tzoffset('GMT', 0)), 1016 ('EST5', tz.tzoffset('EST', -14400)), 1017 ('YAKT-9', tz.tzoffset('JST', timedelta(hours=9))), 1018 ('JST-9', tz.tzoffset('YAKT', timedelta(hours=9))), 1019]) 1020def test_tzlocal_offset_unequal(tzvar, tzoff): 1021 with TZEnvContext(tzvar): 1022 # Including both to test both __eq__ and __ne__ 1023 assert tz.tzlocal() != tzoff 1024 assert not (tz.tzlocal() == tzoff) 1025 1026 1027@pytest.mark.gettz 1028class GettzTest(unittest.TestCase, TzFoldMixin): 1029 gettz = staticmethod(tz.gettz) 1030 1031 def testGettz(self): 1032 # bug 892569 1033 str(self.gettz('UTC')) 1034 1035 def testGetTzEquality(self): 1036 self.assertEqual(self.gettz('UTC'), self.gettz('UTC')) 1037 1038 def testTimeOnlyGettz(self): 1039 # gettz returns None 1040 tz_get = self.gettz('Europe/Minsk') 1041 self.assertIs(dt_time(13, 20, tzinfo=tz_get).utcoffset(), None) 1042 1043 def testTimeOnlyGettzDST(self): 1044 # gettz returns None 1045 tz_get = self.gettz('Europe/Minsk') 1046 self.assertIs(dt_time(13, 20, tzinfo=tz_get).dst(), None) 1047 1048 def testTimeOnlyGettzTzName(self): 1049 tz_get = self.gettz('Europe/Minsk') 1050 self.assertIs(dt_time(13, 20, tzinfo=tz_get).tzname(), None) 1051 1052 def testTimeOnlyFormatZ(self): 1053 tz_get = self.gettz('Europe/Minsk') 1054 t = dt_time(13, 20, tzinfo=tz_get) 1055 1056 self.assertEqual(t.strftime('%H%M%Z'), '1320') 1057 1058 def testPortugalDST(self): 1059 # In 1996, Portugal changed from CET to WET 1060 PORTUGAL = self.gettz('Portugal') 1061 1062 t_cet = datetime(1996, 3, 31, 1, 59, tzinfo=PORTUGAL) 1063 1064 self.assertEqual(t_cet.tzname(), 'CET') 1065 self.assertEqual(t_cet.utcoffset(), timedelta(hours=1)) 1066 self.assertEqual(t_cet.dst(), timedelta(0)) 1067 1068 t_west = datetime(1996, 3, 31, 2, 1, tzinfo=PORTUGAL) 1069 1070 self.assertEqual(t_west.tzname(), 'WEST') 1071 self.assertEqual(t_west.utcoffset(), timedelta(hours=1)) 1072 self.assertEqual(t_west.dst(), timedelta(hours=1)) 1073 1074 def testGettzCacheTzFile(self): 1075 NYC1 = tz.gettz('America/New_York') 1076 NYC2 = tz.gettz('America/New_York') 1077 1078 assert NYC1 is NYC2 1079 1080 def testGettzCacheTzLocal(self): 1081 local1 = tz.gettz() 1082 local2 = tz.gettz() 1083 1084 assert local1 is not local2 1085 1086 1087@pytest.mark.gettz 1088@pytest.mark.parametrize('badzone', [ 1089 'Fake.Region/Abcdefghijklmnop', # Violates several tz project name rules 1090]) 1091def test_gettz_badzone(badzone): 1092 # Make sure passing a bad TZ string to gettz returns None (GH #800) 1093 tzi = tz.gettz(badzone) 1094 assert tzi is None 1095 1096 1097@pytest.mark.gettz 1098def test_gettz_badzone_unicode(): 1099 # Make sure a unicode string can be passed to TZ (GH #802) 1100 # When fixed, combine this with test_gettz_badzone 1101 tzi = tz.gettz('') 1102 assert tzi is None 1103 1104 1105@pytest.mark.gettz 1106@pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached') 1107def test_gettz_cache_clear(): 1108 NYC1 = tz.gettz('America/New_York') 1109 tz.gettz.cache_clear() 1110 1111 NYC2 = tz.gettz('America/New_York') 1112 1113 assert NYC1 is not NYC2 1114 1115@pytest.mark.gettz 1116@pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached') 1117def test_gettz_set_cache_size(): 1118 tz.gettz.cache_clear() 1119 tz.gettz.set_cache_size(3) 1120 1121 MONACO_ref = weakref.ref(tz.gettz('Europe/Monaco')) 1122 EASTER_ref = weakref.ref(tz.gettz('Pacific/Easter')) 1123 CURRIE_ref = weakref.ref(tz.gettz('Australia/Currie')) 1124 1125 gc.collect() 1126 1127 assert MONACO_ref() is not None 1128 assert EASTER_ref() is not None 1129 assert CURRIE_ref() is not None 1130 1131 tz.gettz.set_cache_size(2) 1132 gc.collect() 1133 1134 assert MONACO_ref() is None 1135 1136@pytest.mark.xfail(IS_WIN, reason="Windows does not use system zoneinfo") 1137@pytest.mark.smoke 1138@pytest.mark.gettz 1139def test_gettz_weakref(): 1140 tz.gettz.cache_clear() 1141 tz.gettz.set_cache_size(2) 1142 NYC1 = tz.gettz('America/New_York') 1143 NYC_ref = weakref.ref(tz.gettz('America/New_York')) 1144 1145 assert NYC1 is NYC_ref() 1146 1147 del NYC1 1148 gc.collect() 1149 1150 assert NYC_ref() is not None # Should still be in the strong cache 1151 assert tz.gettz('America/New_York') is NYC_ref() 1152 1153 # Populate strong cache with other timezones 1154 tz.gettz('Europe/Monaco') 1155 tz.gettz('Pacific/Easter') 1156 tz.gettz('Australia/Currie') 1157 1158 gc.collect() 1159 assert NYC_ref() is None # Should have been pushed out 1160 assert tz.gettz('America/New_York') is not NYC_ref() 1161 1162class ZoneInfoGettzTest(GettzTest, WarningTestMixin): 1163 def gettz(self, name): 1164 zoneinfo_file = zoneinfo.get_zonefile_instance() 1165 return zoneinfo_file.get(name) 1166 1167 def testZoneInfoFileStart1(self): 1168 tz = self.gettz("EST5EDT") 1169 self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname(), "EST", 1170 MISSING_TARBALL) 1171 self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname(), "EDT") 1172 1173 def testZoneInfoFileEnd1(self): 1174 tzc = self.gettz("EST5EDT") 1175 self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(), 1176 "EDT", MISSING_TARBALL) 1177 1178 end_est = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tzc), fold=1) 1179 self.assertEqual(end_est.tzname(), "EST") 1180 1181 def testZoneInfoOffsetSignal(self): 1182 utc = self.gettz("UTC") 1183 nyc = self.gettz("America/New_York") 1184 self.assertNotEqual(utc, None, MISSING_TARBALL) 1185 self.assertNotEqual(nyc, None) 1186 t0 = datetime(2007, 11, 4, 0, 30, tzinfo=nyc) 1187 t1 = t0.astimezone(utc) 1188 t2 = t1.astimezone(nyc) 1189 self.assertEqual(t0, t2) 1190 self.assertEqual(nyc.dst(t0), timedelta(hours=1)) 1191 1192 def testZoneInfoCopy(self): 1193 # copy.copy() called on a ZoneInfo file was returning the same instance 1194 CHI = self.gettz('America/Chicago') 1195 CHI_COPY = copy.copy(CHI) 1196 1197 self.assertIsNot(CHI, CHI_COPY) 1198 self.assertEqual(CHI, CHI_COPY) 1199 1200 def testZoneInfoDeepCopy(self): 1201 CHI = self.gettz('America/Chicago') 1202 CHI_COPY = copy.deepcopy(CHI) 1203 1204 self.assertIsNot(CHI, CHI_COPY) 1205 self.assertEqual(CHI, CHI_COPY) 1206 1207 def testZoneInfoInstanceCaching(self): 1208 zif_0 = zoneinfo.get_zonefile_instance() 1209 zif_1 = zoneinfo.get_zonefile_instance() 1210 1211 self.assertIs(zif_0, zif_1) 1212 1213 def testZoneInfoNewInstance(self): 1214 zif_0 = zoneinfo.get_zonefile_instance() 1215 zif_1 = zoneinfo.get_zonefile_instance(new_instance=True) 1216 zif_2 = zoneinfo.get_zonefile_instance() 1217 1218 self.assertIsNot(zif_0, zif_1) 1219 self.assertIs(zif_1, zif_2) 1220 1221 def testZoneInfoDeprecated(self): 1222 with self.assertWarns(DeprecationWarning): 1223 zoneinfo.gettz('US/Eastern') 1224 1225 def testZoneInfoMetadataDeprecated(self): 1226 with self.assertWarns(DeprecationWarning): 1227 zoneinfo.gettz_db_metadata() 1228 1229 1230class TZRangeTest(unittest.TestCase, TzFoldMixin): 1231 TZ_EST = tz.tzrange('EST', timedelta(hours=-5), 1232 'EDT', timedelta(hours=-4), 1233 start=relativedelta(month=3, day=1, hour=2, 1234 weekday=SU(+2)), 1235 end=relativedelta(month=11, day=1, hour=1, 1236 weekday=SU(+1))) 1237 1238 TZ_AEST = tz.tzrange('AEST', timedelta(hours=10), 1239 'AEDT', timedelta(hours=11), 1240 start=relativedelta(month=10, day=1, hour=2, 1241 weekday=SU(+1)), 1242 end=relativedelta(month=4, day=1, hour=2, 1243 weekday=SU(+1))) 1244 1245 TZ_LON = tz.tzrange('GMT', timedelta(hours=0), 1246 'BST', timedelta(hours=1), 1247 start=relativedelta(month=3, day=31, weekday=SU(-1), 1248 hours=2), 1249 end=relativedelta(month=10, day=31, weekday=SU(-1), 1250 hours=1)) 1251 # POSIX string for UTC 1252 UTC = 'UTC' 1253 1254 def gettz(self, tzname): 1255 tzname_map = {'Australia/Sydney': self.TZ_AEST, 1256 'America/Toronto': self.TZ_EST, 1257 'America/New_York': self.TZ_EST, 1258 'Europe/London': self.TZ_LON} 1259 1260 return tzname_map[tzname] 1261 1262 def testRangeCmp1(self): 1263 self.assertEqual(tz.tzstr("EST5EDT"), 1264 tz.tzrange("EST", -18000, "EDT", -14400, 1265 relativedelta(hours=+2, 1266 month=4, day=1, 1267 weekday=SU(+1)), 1268 relativedelta(hours=+1, 1269 month=10, day=31, 1270 weekday=SU(-1)))) 1271 1272 def testRangeCmp2(self): 1273 self.assertEqual(tz.tzstr("EST5EDT"), 1274 tz.tzrange("EST", -18000, "EDT")) 1275 1276 def testRangeOffsets(self): 1277 TZR = tz.tzrange('EST', -18000, 'EDT', -14400, 1278 start=relativedelta(hours=2, month=4, day=1, 1279 weekday=SU(+2)), 1280 end=relativedelta(hours=1, month=10, day=31, 1281 weekday=SU(-1))) 1282 1283 dt_std = datetime(2014, 4, 11, 12, 0, tzinfo=TZR) # STD 1284 dt_dst = datetime(2016, 4, 11, 12, 0, tzinfo=TZR) # DST 1285 1286 dst_zero = timedelta(0) 1287 dst_hour = timedelta(hours=1) 1288 1289 std_offset = timedelta(hours=-5) 1290 dst_offset = timedelta(hours=-4) 1291 1292 # Check dst() 1293 self.assertEqual(dt_std.dst(), dst_zero) 1294 self.assertEqual(dt_dst.dst(), dst_hour) 1295 1296 # Check utcoffset() 1297 self.assertEqual(dt_std.utcoffset(), std_offset) 1298 self.assertEqual(dt_dst.utcoffset(), dst_offset) 1299 1300 # Check tzname 1301 self.assertEqual(dt_std.tzname(), 'EST') 1302 self.assertEqual(dt_dst.tzname(), 'EDT') 1303 1304 def testTimeOnlyRangeFixed(self): 1305 # This is a fixed-offset zone, so tzrange allows this 1306 tz_range = tz.tzrange('dflt', stdoffset=timedelta(hours=-3)) 1307 self.assertEqual(dt_time(13, 20, tzinfo=tz_range).utcoffset(), 1308 timedelta(hours=-3)) 1309 1310 def testTimeOnlyRange(self): 1311 # tzrange returns None because this zone has DST 1312 tz_range = tz.tzrange('EST', timedelta(hours=-5), 1313 'EDT', timedelta(hours=-4)) 1314 self.assertIs(dt_time(13, 20, tzinfo=tz_range).utcoffset(), None) 1315 1316 def testBrokenIsDstHandling(self): 1317 # tzrange._isdst() was using a date() rather than a datetime(). 1318 # Issue reported by Lennart Regebro. 1319 dt = datetime(2007, 8, 6, 4, 10, tzinfo=tz.tzutc()) 1320 self.assertEqual(dt.astimezone(tz=tz.gettz("GMT+2")), 1321 datetime(2007, 8, 6, 6, 10, tzinfo=tz.tzstr("GMT+2"))) 1322 1323 def testRangeTimeDelta(self): 1324 # Test that tzrange can be specified with a timedelta instead of an int. 1325 EST5EDT_td = tz.tzrange('EST', timedelta(hours=-5), 1326 'EDT', timedelta(hours=-4)) 1327 1328 EST5EDT_sec = tz.tzrange('EST', -18000, 1329 'EDT', -14400) 1330 1331 self.assertEqual(EST5EDT_td, EST5EDT_sec) 1332 1333 def testRangeEquality(self): 1334 TZR1 = tz.tzrange('EST', -18000, 'EDT', -14400) 1335 1336 # Standard abbreviation different 1337 TZR2 = tz.tzrange('ET', -18000, 'EDT', -14400) 1338 self.assertNotEqual(TZR1, TZR2) 1339 1340 # DST abbreviation different 1341 TZR3 = tz.tzrange('EST', -18000, 'EMT', -14400) 1342 self.assertNotEqual(TZR1, TZR3) 1343 1344 # STD offset different 1345 TZR4 = tz.tzrange('EST', -14000, 'EDT', -14400) 1346 self.assertNotEqual(TZR1, TZR4) 1347 1348 # DST offset different 1349 TZR5 = tz.tzrange('EST', -18000, 'EDT', -18000) 1350 self.assertNotEqual(TZR1, TZR5) 1351 1352 # Start delta different 1353 TZR6 = tz.tzrange('EST', -18000, 'EDT', -14400, 1354 start=relativedelta(hours=+1, month=3, 1355 day=1, weekday=SU(+2))) 1356 self.assertNotEqual(TZR1, TZR6) 1357 1358 # End delta different 1359 TZR7 = tz.tzrange('EST', -18000, 'EDT', -14400, 1360 end=relativedelta(hours=+1, month=11, 1361 day=1, weekday=SU(+2))) 1362 self.assertNotEqual(TZR1, TZR7) 1363 1364 def testRangeInequalityUnsupported(self): 1365 TZR = tz.tzrange('EST', -18000, 'EDT', -14400) 1366 1367 self.assertFalse(TZR == 4) 1368 self.assertTrue(TZR == ComparesEqual) 1369 self.assertFalse(TZR != ComparesEqual) 1370 1371 1372@pytest.mark.tzstr 1373class TZStrTest(unittest.TestCase, TzFoldMixin): 1374 # POSIX string indicating change to summer time on the 2nd Sunday in March 1375 # at 2AM, and ending the 1st Sunday in November at 2AM. (valid >= 2007) 1376 TZ_EST = 'EST+5EDT,M3.2.0/2,M11.1.0/2' 1377 1378 # POSIX string for AEST/AEDT (valid >= 2008) 1379 TZ_AEST = 'AEST-10AEDT,M10.1.0/2,M4.1.0/3' 1380 1381 # POSIX string for GMT/BST 1382 TZ_LON = 'GMT0BST,M3.5.0,M10.5.0' 1383 1384 def gettz(self, tzname): 1385 # Actual time zone changes are handled by the _gettz_context function 1386 tzname_map = {'Australia/Sydney': self.TZ_AEST, 1387 'America/Toronto': self.TZ_EST, 1388 'America/New_York': self.TZ_EST, 1389 'Europe/London': self.TZ_LON} 1390 1391 return tz.tzstr(tzname_map[tzname]) 1392 1393 def testStrStr(self): 1394 # Test that tz.tzstr() won't throw an error if given a str instead 1395 # of a unicode literal. 1396 self.assertEqual(datetime(2003, 4, 6, 1, 59, 1397 tzinfo=tz.tzstr(str("EST5EDT"))).tzname(), "EST") 1398 self.assertEqual(datetime(2003, 4, 6, 2, 00, 1399 tzinfo=tz.tzstr(str("EST5EDT"))).tzname(), "EDT") 1400 1401 def testStrInequality(self): 1402 TZS1 = tz.tzstr('EST5EDT4') 1403 1404 # Standard abbreviation different 1405 TZS2 = tz.tzstr('ET5EDT4') 1406 self.assertNotEqual(TZS1, TZS2) 1407 1408 # DST abbreviation different 1409 TZS3 = tz.tzstr('EST5EMT') 1410 self.assertNotEqual(TZS1, TZS3) 1411 1412 # STD offset different 1413 TZS4 = tz.tzstr('EST4EDT4') 1414 self.assertNotEqual(TZS1, TZS4) 1415 1416 # DST offset different 1417 TZS5 = tz.tzstr('EST5EDT3') 1418 self.assertNotEqual(TZS1, TZS5) 1419 1420 def testStrInequalityStartEnd(self): 1421 TZS1 = tz.tzstr('EST5EDT4') 1422 1423 # Start delta different 1424 TZS2 = tz.tzstr('EST5EDT4,M4.2.0/02:00:00,M10-5-0/02:00') 1425 self.assertNotEqual(TZS1, TZS2) 1426 1427 # End delta different 1428 TZS3 = tz.tzstr('EST5EDT4,M4.2.0/02:00:00,M11-5-0/02:00') 1429 self.assertNotEqual(TZS1, TZS3) 1430 1431 def testPosixOffset(self): 1432 TZ1 = tz.tzstr('UTC-3') 1433 self.assertEqual(datetime(2015, 1, 1, tzinfo=TZ1).utcoffset(), 1434 timedelta(hours=-3)) 1435 1436 TZ2 = tz.tzstr('UTC-3', posix_offset=True) 1437 self.assertEqual(datetime(2015, 1, 1, tzinfo=TZ2).utcoffset(), 1438 timedelta(hours=+3)) 1439 1440 def testStrInequalityUnsupported(self): 1441 TZS = tz.tzstr('EST5EDT') 1442 1443 self.assertFalse(TZS == 4) 1444 self.assertTrue(TZS == ComparesEqual) 1445 self.assertFalse(TZS != ComparesEqual) 1446 1447 def testTzStrRepr(self): 1448 TZS1 = tz.tzstr('EST5EDT4') 1449 TZS2 = tz.tzstr('EST') 1450 1451 self.assertEqual(repr(TZS1), "tzstr(" + repr('EST5EDT4') + ")") 1452 self.assertEqual(repr(TZS2), "tzstr(" + repr('EST') + ")") 1453 1454 def testTzStrFailure(self): 1455 with self.assertRaises(ValueError): 1456 tz.tzstr('InvalidString;439999') 1457 1458 def testTzStrSingleton(self): 1459 tz1 = tz.tzstr('EST5EDT') 1460 tz2 = tz.tzstr('CST4CST') 1461 tz3 = tz.tzstr('EST5EDT') 1462 1463 self.assertIsNot(tz1, tz2) 1464 self.assertIs(tz1, tz3) 1465 1466 def testTzStrSingletonPosix(self): 1467 tz_t1 = tz.tzstr('GMT+3', posix_offset=True) 1468 tz_f1 = tz.tzstr('GMT+3', posix_offset=False) 1469 1470 tz_t2 = tz.tzstr('GMT+3', posix_offset=True) 1471 tz_f2 = tz.tzstr('GMT+3', posix_offset=False) 1472 1473 self.assertIs(tz_t1, tz_t2) 1474 self.assertIsNot(tz_t1, tz_f1) 1475 1476 self.assertIs(tz_f1, tz_f2) 1477 1478 def testTzStrInstance(self): 1479 tz1 = tz.tzstr('EST5EDT') 1480 tz2 = tz.tzstr.instance('EST5EDT') 1481 tz3 = tz.tzstr.instance('EST5EDT') 1482 1483 assert tz1 is not tz2 1484 assert tz2 is not tz3 1485 1486 # Ensure that these still are all the same zone 1487 assert tz1 == tz2 == tz3 1488 1489 1490@pytest.mark.smoke 1491@pytest.mark.tzstr 1492def test_tzstr_weakref(): 1493 tz_t1 = tz.tzstr('EST5EDT') 1494 tz_t2_ref = weakref.ref(tz.tzstr('EST5EDT')) 1495 assert tz_t1 is tz_t2_ref() 1496 1497 del tz_t1 1498 gc.collect() 1499 1500 assert tz_t2_ref() is not None 1501 assert tz.tzstr('EST5EDT') is tz_t2_ref() 1502 1503 for offset in range(5,15): 1504 tz.tzstr('GMT+{}'.format(offset)) 1505 gc.collect() 1506 1507 assert tz_t2_ref() is None 1508 assert tz.tzstr('EST5EDT') is not tz_t2_ref() 1509 1510 1511@pytest.mark.tzstr 1512@pytest.mark.parametrize('tz_str,expected', [ 1513 # From https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html 1514 ('', tz.tzrange(None)), # TODO: Should change this so tz.tzrange('') works 1515 ('EST+5EDT,M3.2.0/2,M11.1.0/12', 1516 tz.tzrange('EST', -18000, 'EDT', -14400, 1517 start=relativedelta(month=3, day=1, weekday=SU(2), hours=2), 1518 end=relativedelta(month=11, day=1, weekday=SU(1), hours=11))), 1519 ('WART4WARST,J1/0,J365/25', # This is DST all year, Western Argentina Summer Time 1520 tz.tzrange('WART', timedelta(hours=-4), 'WARST', 1521 start=relativedelta(month=1, day=1, hours=0), 1522 end=relativedelta(month=12, day=31, days=1))), 1523 ('IST-2IDT,M3.4.4/26,M10.5.0', # Israel Standard / Daylight Time 1524 tz.tzrange('IST', timedelta(hours=2), 'IDT', 1525 start=relativedelta(month=3, day=1, weekday=TH(4), days=1, hours=2), 1526 end=relativedelta(month=10, day=31, weekday=SU(-1), hours=1))), 1527 ('WGT3WGST,M3.5.0/2,M10.5.0/1', 1528 tz.tzrange('WGT', timedelta(hours=-3), 'WGST', 1529 start=relativedelta(month=3, day=31, weekday=SU(-1), hours=2), 1530 end=relativedelta(month=10, day=31, weekday=SU(-1), hours=0))), 1531 1532 # Different offset specifications 1533 ('WGT0300WGST', 1534 tz.tzrange('WGT', timedelta(hours=-3), 'WGST')), 1535 ('WGT03:00WGST', 1536 tz.tzrange('WGT', timedelta(hours=-3), 'WGST')), 1537 ('AEST-1100AEDT', 1538 tz.tzrange('AEST', timedelta(hours=11), 'AEDT')), 1539 ('AEST-11:00AEDT', 1540 tz.tzrange('AEST', timedelta(hours=11), 'AEDT')), 1541 1542 # Different time formats 1543 ('EST5EDT,M3.2.0/4:00,M11.1.0/3:00', 1544 tz.tzrange('EST', timedelta(hours=-5), 'EDT', 1545 start=relativedelta(month=3, day=1, weekday=SU(2), hours=4), 1546 end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))), 1547 ('EST5EDT,M3.2.0/04:00,M11.1.0/03:00', 1548 tz.tzrange('EST', timedelta(hours=-5), 'EDT', 1549 start=relativedelta(month=3, day=1, weekday=SU(2), hours=4), 1550 end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))), 1551 ('EST5EDT,M3.2.0/0400,M11.1.0/0300', 1552 tz.tzrange('EST', timedelta(hours=-5), 'EDT', 1553 start=relativedelta(month=3, day=1, weekday=SU(2), hours=4), 1554 end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))), 1555]) 1556def test_valid_GNU_tzstr(tz_str, expected): 1557 tzi = tz.tzstr(tz_str) 1558 1559 assert tzi == expected 1560 1561 1562@pytest.mark.tzstr 1563@pytest.mark.parametrize('tz_str, expected', [ 1564 ('EST5EDT,5,4,0,7200,11,3,0,7200', 1565 tz.tzrange('EST', timedelta(hours=-5), 'EDT', 1566 start=relativedelta(month=5, day=1, weekday=SU(+4), hours=+2), 1567 end=relativedelta(month=11, day=1, weekday=SU(+3), hours=+1))), 1568 ('EST5EDT,5,-4,0,7200,11,3,0,7200', 1569 tz.tzrange('EST', timedelta(hours=-5), 'EDT', 1570 start=relativedelta(hours=+2, month=5, day=31, weekday=SU(-4)), 1571 end=relativedelta(hours=+1, month=11, day=1, weekday=SU(+3)))), 1572 ('EST5EDT,5,4,0,7200,11,-3,0,7200', 1573 tz.tzrange('EST', timedelta(hours=-5), 'EDT', 1574 start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), 1575 end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), 1576 ('EST5EDT,5,4,0,7200,11,-3,0,7200,3600', 1577 tz.tzrange('EST', timedelta(hours=-5), 'EDT', 1578 start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), 1579 end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), 1580 ('EST5EDT,5,4,0,7200,11,-3,0,7200,3600', 1581 tz.tzrange('EST', timedelta(hours=-5), 'EDT', 1582 start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), 1583 end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), 1584 ('EST5EDT,5,4,0,7200,11,-3,0,7200,-3600', 1585 tz.tzrange('EST', timedelta(hours=-5), 'EDT', timedelta(hours=-6), 1586 start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), 1587 end=relativedelta(hours=+3, month=11, day=31, weekday=SU(-3)))), 1588 ('EST5EDT,5,4,0,7200,11,-3,0,7200,+7200', 1589 tz.tzrange('EST', timedelta(hours=-5), 'EDT', timedelta(hours=-3), 1590 start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), 1591 end=relativedelta(hours=0, month=11, day=31, weekday=SU(-3)))), 1592 ('EST5EDT,5,4,0,7200,11,-3,0,7200,+3600', 1593 tz.tzrange('EST', timedelta(hours=-5), 'EDT', 1594 start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), 1595 end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), 1596]) 1597def test_valid_dateutil_format(tz_str, expected): 1598 # This tests the dateutil-specific format that is used widely in the tests 1599 # and examples. It is unclear where this format originated from. 1600 with pytest.warns(tz.DeprecatedTzFormatWarning): 1601 tzi = tz.tzstr.instance(tz_str) 1602 1603 assert tzi == expected 1604 1605 1606@pytest.mark.tzstr 1607@pytest.mark.parametrize('tz_str', [ 1608 'hdfiughdfuig,dfughdfuigpu87ñ::', 1609 ',dfughdfuigpu87ñ::', 1610 '-1:WART4WARST,J1,J365/25', 1611 'WART4WARST,J1,J365/-25', 1612 'IST-2IDT,M3.4.-1/26,M10.5.0', 1613 'IST-2IDT,M3,2000,1/26,M10,5,0' 1614]) 1615def test_invalid_GNU_tzstr(tz_str): 1616 with pytest.raises(ValueError): 1617 tz.tzstr(tz_str) 1618 1619 1620# Different representations of the same default rule set 1621DEFAULT_TZSTR_RULES_EQUIV_2003 = [ 1622 'EST5EDT', 1623 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00', 1624 'EST5EDT4,95/02:00:00,298/02:00', 1625 'EST5EDT4,J96/02:00:00,J299/02:00', 1626 'EST5EDT4,J96/02:00:00,J299/02' 1627] 1628 1629 1630@pytest.mark.tzstr 1631@pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003) 1632def test_tzstr_default_start(tz_str): 1633 tzi = tz.tzstr(tz_str) 1634 dt_std = datetime(2003, 4, 6, 1, 59, tzinfo=tzi) 1635 dt_dst = datetime(2003, 4, 6, 2, 00, tzinfo=tzi) 1636 1637 assert get_timezone_tuple(dt_std) == EST_TUPLE 1638 assert get_timezone_tuple(dt_dst) == EDT_TUPLE 1639 1640 1641@pytest.mark.tzstr 1642@pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003) 1643def test_tzstr_default_end(tz_str): 1644 tzi = tz.tzstr(tz_str) 1645 dt_dst = datetime(2003, 10, 26, 0, 59, tzinfo=tzi) 1646 dt_dst_ambig = datetime(2003, 10, 26, 1, 00, tzinfo=tzi) 1647 dt_std_ambig = tz.enfold(dt_dst_ambig, fold=1) 1648 dt_std = datetime(2003, 10, 26, 2, 00, tzinfo=tzi) 1649 1650 assert get_timezone_tuple(dt_dst) == EDT_TUPLE 1651 assert get_timezone_tuple(dt_dst_ambig) == EDT_TUPLE 1652 assert get_timezone_tuple(dt_std_ambig) == EST_TUPLE 1653 assert get_timezone_tuple(dt_std) == EST_TUPLE 1654 1655 1656@pytest.mark.tzstr 1657@pytest.mark.parametrize('tzstr_1', ['EST5EDT', 1658 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00']) 1659@pytest.mark.parametrize('tzstr_2', ['EST5EDT', 1660 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00']) 1661def test_tzstr_default_cmp(tzstr_1, tzstr_2): 1662 tz1 = tz.tzstr(tzstr_1) 1663 tz2 = tz.tzstr(tzstr_2) 1664 1665 assert tz1 == tz2 1666 1667class TZICalTest(unittest.TestCase, TzFoldMixin): 1668 def _gettz_str_tuple(self, tzname): 1669 TZ_EST = ( 1670 'BEGIN:VTIMEZONE', 1671 'TZID:US-Eastern', 1672 'BEGIN:STANDARD', 1673 'DTSTART:19971029T020000', 1674 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=11', 1675 'TZOFFSETFROM:-0400', 1676 'TZOFFSETTO:-0500', 1677 'TZNAME:EST', 1678 'END:STANDARD', 1679 'BEGIN:DAYLIGHT', 1680 'DTSTART:19980301T020000', 1681 'RRULE:FREQ=YEARLY;BYDAY=+2SU;BYMONTH=03', 1682 'TZOFFSETFROM:-0500', 1683 'TZOFFSETTO:-0400', 1684 'TZNAME:EDT', 1685 'END:DAYLIGHT', 1686 'END:VTIMEZONE' 1687 ) 1688 1689 TZ_PST = ( 1690 'BEGIN:VTIMEZONE', 1691 'TZID:US-Pacific', 1692 'BEGIN:STANDARD', 1693 'DTSTART:19971029T020000', 1694 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=11', 1695 'TZOFFSETFROM:-0700', 1696 'TZOFFSETTO:-0800', 1697 'TZNAME:PST', 1698 'END:STANDARD', 1699 'BEGIN:DAYLIGHT', 1700 'DTSTART:19980301T020000', 1701 'RRULE:FREQ=YEARLY;BYDAY=+2SU;BYMONTH=03', 1702 'TZOFFSETFROM:-0800', 1703 'TZOFFSETTO:-0700', 1704 'TZNAME:PDT', 1705 'END:DAYLIGHT', 1706 'END:VTIMEZONE' 1707 ) 1708 1709 TZ_AEST = ( 1710 'BEGIN:VTIMEZONE', 1711 'TZID:Australia-Sydney', 1712 'BEGIN:STANDARD', 1713 'DTSTART:19980301T030000', 1714 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=04', 1715 'TZOFFSETFROM:+1100', 1716 'TZOFFSETTO:+1000', 1717 'TZNAME:AEST', 1718 'END:STANDARD', 1719 'BEGIN:DAYLIGHT', 1720 'DTSTART:19971029T020000', 1721 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=10', 1722 'TZOFFSETFROM:+1000', 1723 'TZOFFSETTO:+1100', 1724 'TZNAME:AEDT', 1725 'END:DAYLIGHT', 1726 'END:VTIMEZONE' 1727 ) 1728 1729 TZ_LON = ( 1730 'BEGIN:VTIMEZONE', 1731 'TZID:Europe-London', 1732 'BEGIN:STANDARD', 1733 'DTSTART:19810301T030000', 1734 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10;BYHOUR=02', 1735 'TZOFFSETFROM:+0100', 1736 'TZOFFSETTO:+0000', 1737 'TZNAME:GMT', 1738 'END:STANDARD', 1739 'BEGIN:DAYLIGHT', 1740 'DTSTART:19961001T030000', 1741 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=03;BYHOUR=01', 1742 'TZOFFSETFROM:+0000', 1743 'TZOFFSETTO:+0100', 1744 'TZNAME:BST', 1745 'END:DAYLIGHT', 1746 'END:VTIMEZONE' 1747 ) 1748 1749 tzname_map = {'Australia/Sydney': TZ_AEST, 1750 'America/Toronto': TZ_EST, 1751 'America/New_York': TZ_EST, 1752 'America/Los_Angeles': TZ_PST, 1753 'Europe/London': TZ_LON} 1754 1755 return tzname_map[tzname] 1756 1757 def _gettz_str(self, tzname): 1758 return '\n'.join(self._gettz_str_tuple(tzname)) 1759 1760 def _tzstr_dtstart_with_params(self, tzname, param_str): 1761 # Adds parameters to the DTSTART values of a given tzstr 1762 tz_str_tuple = self._gettz_str_tuple(tzname) 1763 1764 out_tz = [] 1765 for line in tz_str_tuple: 1766 if line.startswith('DTSTART'): 1767 name, value = line.split(':', 1) 1768 line = name + ';' + param_str + ':' + value 1769 1770 out_tz.append(line) 1771 1772 return '\n'.join(out_tz) 1773 1774 def gettz(self, tzname): 1775 tz_str = self._gettz_str(tzname) 1776 1777 tzc = tz.tzical(StringIO(tz_str)).get() 1778 1779 return tzc 1780 1781 def testRepr(self): 1782 instr = StringIO(TZICAL_PST8PDT) 1783 instr.name = 'StringIO(PST8PDT)' 1784 tzc = tz.tzical(instr) 1785 1786 self.assertEqual(repr(tzc), "tzical(" + repr(instr.name) + ")") 1787 1788 # Test performance 1789 def _test_us_zone(self, tzc, func, values, start): 1790 if start: 1791 dt1 = datetime(2003, 3, 9, 1, 59) 1792 dt2 = datetime(2003, 3, 9, 2, 00) 1793 fold = [0, 0] 1794 else: 1795 dt1 = datetime(2003, 11, 2, 0, 59) 1796 dt2 = datetime(2003, 11, 2, 1, 00) 1797 fold = [0, 1] 1798 1799 dts = (tz.enfold(dt.replace(tzinfo=tzc), fold=f) 1800 for dt, f in zip((dt1, dt2), fold)) 1801 1802 for value, dt in zip(values, dts): 1803 self.assertEqual(func(dt), value) 1804 1805 def _test_multi_zones(self, tzstrs, tzids, func, values, start): 1806 tzic = tz.tzical(StringIO('\n'.join(tzstrs))) 1807 for tzid, vals in zip(tzids, values): 1808 tzc = tzic.get(tzid) 1809 1810 self._test_us_zone(tzc, func, vals, start) 1811 1812 def _prepare_EST(self): 1813 tz_str = self._gettz_str('America/New_York') 1814 return tz.tzical(StringIO(tz_str)).get() 1815 1816 def _testEST(self, start, test_type, tzc=None): 1817 if tzc is None: 1818 tzc = self._prepare_EST() 1819 1820 argdict = { 1821 'name': (datetime.tzname, ('EST', 'EDT')), 1822 'offset': (datetime.utcoffset, (timedelta(hours=-5), 1823 timedelta(hours=-4))), 1824 'dst': (datetime.dst, (timedelta(hours=0), 1825 timedelta(hours=1))) 1826 } 1827 1828 func, values = argdict[test_type] 1829 1830 if not start: 1831 values = reversed(values) 1832 1833 self._test_us_zone(tzc, func, values, start=start) 1834 1835 def testESTStartName(self): 1836 self._testEST(start=True, test_type='name') 1837 1838 def testESTEndName(self): 1839 self._testEST(start=False, test_type='name') 1840 1841 def testESTStartOffset(self): 1842 self._testEST(start=True, test_type='offset') 1843 1844 def testESTEndOffset(self): 1845 self._testEST(start=False, test_type='offset') 1846 1847 def testESTStartDST(self): 1848 self._testEST(start=True, test_type='dst') 1849 1850 def testESTEndDST(self): 1851 self._testEST(start=False, test_type='dst') 1852 1853 def testESTValueDatetime(self): 1854 # Violating one-test-per-test rule because we're not set up to do 1855 # parameterized tests and the manual proliferation is getting a bit 1856 # out of hand. 1857 tz_str = self._tzstr_dtstart_with_params('America/New_York', 1858 'VALUE=DATE-TIME') 1859 1860 tzc = tz.tzical(StringIO(tz_str)).get() 1861 1862 for start in (True, False): 1863 for test_type in ('name', 'offset', 'dst'): 1864 self._testEST(start=start, test_type=test_type, tzc=tzc) 1865 1866 def _testMultizone(self, start, test_type): 1867 tzstrs = (self._gettz_str('America/New_York'), 1868 self._gettz_str('America/Los_Angeles')) 1869 tzids = ('US-Eastern', 'US-Pacific') 1870 1871 argdict = { 1872 'name': (datetime.tzname, (('EST', 'EDT'), 1873 ('PST', 'PDT'))), 1874 'offset': (datetime.utcoffset, ((timedelta(hours=-5), 1875 timedelta(hours=-4)), 1876 (timedelta(hours=-8), 1877 timedelta(hours=-7)))), 1878 'dst': (datetime.dst, ((timedelta(hours=0), 1879 timedelta(hours=1)), 1880 (timedelta(hours=0), 1881 timedelta(hours=1)))) 1882 } 1883 1884 func, values = argdict[test_type] 1885 1886 if not start: 1887 values = map(reversed, values) 1888 1889 self._test_multi_zones(tzstrs, tzids, func, values, start) 1890 1891 def testMultiZoneStartName(self): 1892 self._testMultizone(start=True, test_type='name') 1893 1894 def testMultiZoneEndName(self): 1895 self._testMultizone(start=False, test_type='name') 1896 1897 def testMultiZoneStartOffset(self): 1898 self._testMultizone(start=True, test_type='offset') 1899 1900 def testMultiZoneEndOffset(self): 1901 self._testMultizone(start=False, test_type='offset') 1902 1903 def testMultiZoneStartDST(self): 1904 self._testMultizone(start=True, test_type='dst') 1905 1906 def testMultiZoneEndDST(self): 1907 self._testMultizone(start=False, test_type='dst') 1908 1909 def testMultiZoneKeys(self): 1910 est_str = self._gettz_str('America/New_York') 1911 pst_str = self._gettz_str('America/Los_Angeles') 1912 tzic = tz.tzical(StringIO('\n'.join((est_str, pst_str)))) 1913 1914 # Sort keys because they are in a random order, being dictionary keys 1915 keys = sorted(tzic.keys()) 1916 1917 self.assertEqual(keys, ['US-Eastern', 'US-Pacific']) 1918 1919 # Test error conditions 1920 def testEmptyString(self): 1921 with self.assertRaises(ValueError): 1922 tz.tzical(StringIO("")) 1923 1924 def testMultiZoneGet(self): 1925 tzic = tz.tzical(StringIO(TZICAL_EST5EDT + TZICAL_PST8PDT)) 1926 1927 with self.assertRaises(ValueError): 1928 tzic.get() 1929 1930 def testDtstartDate(self): 1931 tz_str = self._tzstr_dtstart_with_params('America/New_York', 1932 'VALUE=DATE') 1933 with self.assertRaises(ValueError): 1934 tz.tzical(StringIO(tz_str)) 1935 1936 def testDtstartTzid(self): 1937 tz_str = self._tzstr_dtstart_with_params('America/New_York', 1938 'TZID=UTC') 1939 with self.assertRaises(ValueError): 1940 tz.tzical(StringIO(tz_str)) 1941 1942 def testDtstartBadParam(self): 1943 tz_str = self._tzstr_dtstart_with_params('America/New_York', 1944 'FOO=BAR') 1945 with self.assertRaises(ValueError): 1946 tz.tzical(StringIO(tz_str)) 1947 1948 # Test Parsing 1949 def testGap(self): 1950 tzic = tz.tzical(StringIO('\n'.join((TZICAL_EST5EDT, TZICAL_PST8PDT)))) 1951 1952 keys = sorted(tzic.keys()) 1953 self.assertEqual(keys, ['US-Eastern', 'US-Pacific']) 1954 1955 1956class TZTest(unittest.TestCase): 1957 def testFileStart1(self): 1958 tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) 1959 self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tzc).tzname(), "EST") 1960 self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tzc).tzname(), "EDT") 1961 1962 def testFileEnd1(self): 1963 tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) 1964 self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(), 1965 "EDT") 1966 end_est = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tzc)) 1967 self.assertEqual(end_est.tzname(), "EST") 1968 1969 def testFileLastTransition(self): 1970 # After the last transition, it goes to standard time in perpetuity 1971 tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) 1972 self.assertEqual(datetime(2037, 10, 25, 0, 59, tzinfo=tzc).tzname(), 1973 "EDT") 1974 1975 last_date = tz.enfold(datetime(2037, 10, 25, 1, 00, tzinfo=tzc), fold=1) 1976 self.assertEqual(last_date.tzname(), 1977 "EST") 1978 1979 self.assertEqual(datetime(2038, 5, 25, 12, 0, tzinfo=tzc).tzname(), 1980 "EST") 1981 1982 def testInvalidFile(self): 1983 # Should throw a ValueError if an invalid file is passed 1984 with self.assertRaises(ValueError): 1985 tz.tzfile(BytesIO(b'BadFile')) 1986 1987 def testFilestreamWithNameRepr(self): 1988 # If fileobj is a filestream with a "name" attribute this name should 1989 # be reflected in the tz object's repr 1990 fileobj = BytesIO(base64.b64decode(TZFILE_EST5EDT)) 1991 fileobj.name = 'foo' 1992 tzc = tz.tzfile(fileobj) 1993 self.assertEqual(repr(tzc), 'tzfile(' + repr('foo') + ')') 1994 1995 def testLeapCountDecodesProperly(self): 1996 # This timezone has leapcnt, and failed to decode until 1997 # Eugene Oden notified about the issue. 1998 1999 # As leap information is currently unused (and unstored) by tzfile() we 2000 # can only indirectly test this: Take advantage of tzfile() not closing 2001 # the input file if handed in as an opened file and assert that the 2002 # full file content has been read by tzfile(). Note: For this test to 2003 # work NEW_YORK must be in TZif version 1 format i.e. no more data 2004 # after TZif v1 header + data has been read 2005 fileobj = BytesIO(base64.b64decode(NEW_YORK)) 2006 tz.tzfile(fileobj) 2007 # we expect no remaining file content now, i.e. zero-length; if there's 2008 # still data we haven't read the file format correctly 2009 remaining_tzfile_content = fileobj.read() 2010 self.assertEqual(len(remaining_tzfile_content), 0) 2011 2012 def testIsStd(self): 2013 # NEW_YORK tzfile contains this isstd information: 2014 isstd_expected = (0, 0, 0, 1) 2015 tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK))) 2016 # gather the actual information as parsed by the tzfile class 2017 isstd = [] 2018 for ttinfo in tzc._ttinfo_list: 2019 # ttinfo objects contain boolean values 2020 isstd.append(int(ttinfo.isstd)) 2021 # ttinfo list may contain more entries than isstd file content 2022 isstd = tuple(isstd[:len(isstd_expected)]) 2023 self.assertEqual( 2024 isstd_expected, isstd, 2025 "isstd UTC/local indicators parsed: %s != tzfile contents: %s" 2026 % (isstd, isstd_expected)) 2027 2028 def testGMTHasNoDaylight(self): 2029 # tz.tzstr("GMT+2") improperly considered daylight saving time. 2030 # Issue reported by Lennart Regebro. 2031 dt = datetime(2007, 8, 6, 4, 10) 2032 self.assertEqual(tz.gettz("GMT+2").dst(dt), timedelta(0)) 2033 2034 def testGMTOffset(self): 2035 # GMT and UTC offsets have inverted signal when compared to the 2036 # usual TZ variable handling. 2037 dt = datetime(2007, 8, 6, 4, 10, tzinfo=tz.tzutc()) 2038 self.assertEqual(dt.astimezone(tz=tz.tzstr("GMT+2")), 2039 datetime(2007, 8, 6, 6, 10, tzinfo=tz.tzstr("GMT+2"))) 2040 self.assertEqual(dt.astimezone(tz=tz.gettz("UTC-2")), 2041 datetime(2007, 8, 6, 2, 10, tzinfo=tz.tzstr("UTC-2"))) 2042 2043 @unittest.skipIf(IS_WIN, "requires Unix") 2044 @unittest.skipUnless(TZEnvContext.tz_change_allowed(), 2045 TZEnvContext.tz_change_disallowed_message()) 2046 def testTZSetDoesntCorrupt(self): 2047 # if we start in non-UTC then tzset UTC make sure parse doesn't get 2048 # confused 2049 with TZEnvContext('UTC'): 2050 # this should parse to UTC timezone not the original timezone 2051 dt = parse('2014-07-20T12:34:56+00:00') 2052 self.assertEqual(str(dt), '2014-07-20 12:34:56+00:00') 2053 2054 2055@pytest.mark.tzfile 2056@pytest.mark.skipif(not SUPPORTS_SUB_MINUTE_OFFSETS, 2057 reason='Sub-minute offsets not supported') 2058def test_tzfile_sub_minute_offset(): 2059 # If user running python 3.6 or newer, exact offset is used 2060 tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI))) 2061 offset = timedelta(hours=1, minutes=39, seconds=52) 2062 assert datetime(1900, 1, 1, 0, 0, tzinfo=tzc).utcoffset() == offset 2063 2064 2065@pytest.mark.tzfile 2066@pytest.mark.skipif(SUPPORTS_SUB_MINUTE_OFFSETS, 2067 reason='Sub-minute offsets supported.') 2068def test_sub_minute_rounding_tzfile(): 2069 # This timezone has an offset of 5992 seconds in 1900-01-01. 2070 # For python version pre-3.6, this will be rounded 2071 tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI))) 2072 offset = timedelta(hours=1, minutes=40) 2073 assert datetime(1900, 1, 1, 0, 0, tzinfo=tzc).utcoffset() == offset 2074 2075 2076@pytest.mark.tzfile 2077def test_samoa_transition(): 2078 # utcoffset() was erroneously returning +14:00 an hour early (GH #812) 2079 APIA = tz.gettz('Pacific/Apia') 2080 dt = datetime(2011, 12, 29, 23, 59, tzinfo=APIA) 2081 assert dt.utcoffset() == timedelta(hours=-10) 2082 2083 # Make sure the transition actually works, too 2084 dt_after = (dt.astimezone(tz.UTC) + timedelta(minutes=1)).astimezone(APIA) 2085 assert dt_after == datetime(2011, 12, 31, tzinfo=APIA) 2086 assert dt_after.utcoffset() == timedelta(hours=14) 2087 2088 2089@unittest.skipUnless(IS_WIN, "Requires Windows") 2090class TzWinTest(unittest.TestCase, TzWinFoldMixin): 2091 def setUp(self): 2092 self.tzclass = tzwin.tzwin 2093 2094 def testTzResLoadName(self): 2095 # This may not work right on non-US locales. 2096 tzr = tzwin.tzres() 2097 self.assertEqual(tzr.load_name(112), "Eastern Standard Time") 2098 2099 def testTzResNameFromString(self): 2100 tzr = tzwin.tzres() 2101 self.assertEqual(tzr.name_from_string('@tzres.dll,-221'), 2102 'Alaskan Daylight Time') 2103 2104 self.assertEqual(tzr.name_from_string('Samoa Daylight Time'), 2105 'Samoa Daylight Time') 2106 2107 with self.assertRaises(ValueError): 2108 tzr.name_from_string('@tzres.dll,100') 2109 2110 def testIsdstZoneWithNoDaylightSaving(self): 2111 tz = tzwin.tzwin("UTC") 2112 dt = parse("2013-03-06 19:08:15") 2113 self.assertFalse(tz._isdst(dt)) 2114 2115 def testOffset(self): 2116 tz = tzwin.tzwin("Cape Verde Standard Time") 2117 self.assertEqual(tz.utcoffset(datetime(1995, 5, 21, 12, 9, 13)), 2118 timedelta(-1, 82800)) 2119 2120 def testTzwinName(self): 2121 # https://github.com/dateutil/dateutil/issues/143 2122 tw = tz.tzwin('Eastern Standard Time') 2123 2124 # Cover the transitions for at least two years. 2125 ESTs = 'Eastern Standard Time' 2126 EDTs = 'Eastern Daylight Time' 2127 transition_dates = [(datetime(2015, 3, 8, 0, 59), ESTs), 2128 (datetime(2015, 3, 8, 3, 1), EDTs), 2129 (datetime(2015, 11, 1, 0, 59), EDTs), 2130 (datetime(2015, 11, 1, 3, 1), ESTs), 2131 (datetime(2016, 3, 13, 0, 59), ESTs), 2132 (datetime(2016, 3, 13, 3, 1), EDTs), 2133 (datetime(2016, 11, 6, 0, 59), EDTs), 2134 (datetime(2016, 11, 6, 3, 1), ESTs)] 2135 2136 for t_date, expected in transition_dates: 2137 self.assertEqual(t_date.replace(tzinfo=tw).tzname(), expected) 2138 2139 def testTzwinRepr(self): 2140 tw = tz.tzwin('Yakutsk Standard Time') 2141 self.assertEqual(repr(tw), 'tzwin(' + 2142 repr('Yakutsk Standard Time') + ')') 2143 2144 def testTzWinEquality(self): 2145 # https://github.com/dateutil/dateutil/issues/151 2146 tzwin_names = ('Eastern Standard Time', 2147 'West Pacific Standard Time', 2148 'Yakutsk Standard Time', 2149 'Iran Standard Time', 2150 'UTC') 2151 2152 for tzwin_name in tzwin_names: 2153 # Get two different instances to compare 2154 tw1 = tz.tzwin(tzwin_name) 2155 tw2 = tz.tzwin(tzwin_name) 2156 2157 self.assertEqual(tw1, tw2) 2158 2159 def testTzWinInequality(self): 2160 # https://github.com/dateutil/dateutil/issues/151 2161 # Note these last two currently differ only in their name. 2162 tzwin_names = (('Eastern Standard Time', 'Yakutsk Standard Time'), 2163 ('Greenwich Standard Time', 'GMT Standard Time'), 2164 ('GMT Standard Time', 'UTC'), 2165 ('E. South America Standard Time', 2166 'Argentina Standard Time')) 2167 2168 for tzwn1, tzwn2 in tzwin_names: 2169 # Get two different instances to compare 2170 tw1 = tz.tzwin(tzwn1) 2171 tw2 = tz.tzwin(tzwn2) 2172 2173 self.assertNotEqual(tw1, tw2) 2174 2175 def testTzWinEqualityInvalid(self): 2176 # Compare to objects that do not implement comparison with this 2177 # (should default to False) 2178 UTC = tz.tzutc() 2179 EST = tz.tzwin('Eastern Standard Time') 2180 2181 self.assertFalse(EST == UTC) 2182 self.assertFalse(EST == 1) 2183 self.assertFalse(UTC == EST) 2184 2185 self.assertTrue(EST != UTC) 2186 self.assertTrue(EST != 1) 2187 2188 def testTzWinInequalityUnsupported(self): 2189 # Compare it to an object that is promiscuous about equality, but for 2190 # which tzwin does not implement an equality operator. 2191 EST = tz.tzwin('Eastern Standard Time') 2192 self.assertTrue(EST == ComparesEqual) 2193 self.assertFalse(EST != ComparesEqual) 2194 2195 def testTzwinTimeOnlyDST(self): 2196 # For zones with DST, .dst() should return None 2197 tw_est = tz.tzwin('Eastern Standard Time') 2198 self.assertIs(dt_time(14, 10, tzinfo=tw_est).dst(), None) 2199 2200 # This zone has no DST, so .dst() can return 0 2201 tw_sast = tz.tzwin('South Africa Standard Time') 2202 self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).dst(), 2203 timedelta(0)) 2204 2205 def testTzwinTimeOnlyUTCOffset(self): 2206 # For zones with DST, .utcoffset() should return None 2207 tw_est = tz.tzwin('Eastern Standard Time') 2208 self.assertIs(dt_time(14, 10, tzinfo=tw_est).utcoffset(), None) 2209 2210 # This zone has no DST, so .utcoffset() returns standard offset 2211 tw_sast = tz.tzwin('South Africa Standard Time') 2212 self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).utcoffset(), 2213 timedelta(hours=2)) 2214 2215 def testTzwinTimeOnlyTZName(self): 2216 # For zones with DST, the name defaults to standard time 2217 tw_est = tz.tzwin('Eastern Standard Time') 2218 self.assertEqual(dt_time(14, 10, tzinfo=tw_est).tzname(), 2219 'Eastern Standard Time') 2220 2221 # For zones with no DST, this should work normally. 2222 tw_sast = tz.tzwin('South Africa Standard Time') 2223 self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).tzname(), 2224 'South Africa Standard Time') 2225 2226 2227@unittest.skipUnless(IS_WIN, "Requires Windows") 2228@unittest.skipUnless(TZWinContext.tz_change_allowed(), 2229 TZWinContext.tz_change_disallowed_message()) 2230class TzWinLocalTest(unittest.TestCase, TzWinFoldMixin): 2231 2232 def setUp(self): 2233 self.tzclass = tzwin.tzwinlocal 2234 self.context = TZWinContext 2235 2236 def get_args(self, tzname): 2237 return () 2238 2239 def testLocal(self): 2240 # Not sure how to pin a local time zone, so for now we're just going 2241 # to run this and make sure it doesn't raise an error 2242 # See Github Issue #135: https://github.com/dateutil/dateutil/issues/135 2243 datetime.now(tzwin.tzwinlocal()) 2244 2245 def testTzwinLocalUTCOffset(self): 2246 with TZWinContext('Eastern Standard Time'): 2247 tzwl = tzwin.tzwinlocal() 2248 self.assertEqual(datetime(2014, 3, 11, tzinfo=tzwl).utcoffset(), 2249 timedelta(hours=-4)) 2250 2251 def testTzwinLocalName(self): 2252 # https://github.com/dateutil/dateutil/issues/143 2253 ESTs = 'Eastern Standard Time' 2254 EDTs = 'Eastern Daylight Time' 2255 transition_dates = [(datetime(2015, 3, 8, 0, 59), ESTs), 2256 (datetime(2015, 3, 8, 3, 1), EDTs), 2257 (datetime(2015, 11, 1, 0, 59), EDTs), 2258 (datetime(2015, 11, 1, 3, 1), ESTs), 2259 (datetime(2016, 3, 13, 0, 59), ESTs), 2260 (datetime(2016, 3, 13, 3, 1), EDTs), 2261 (datetime(2016, 11, 6, 0, 59), EDTs), 2262 (datetime(2016, 11, 6, 3, 1), ESTs)] 2263 2264 with TZWinContext('Eastern Standard Time'): 2265 tw = tz.tzwinlocal() 2266 2267 for t_date, expected in transition_dates: 2268 self.assertEqual(t_date.replace(tzinfo=tw).tzname(), expected) 2269 2270 def testTzWinLocalRepr(self): 2271 tw = tz.tzwinlocal() 2272 self.assertEqual(repr(tw), 'tzwinlocal()') 2273 2274 def testTzwinLocalRepr(self): 2275 # https://github.com/dateutil/dateutil/issues/143 2276 with TZWinContext('Eastern Standard Time'): 2277 tw = tz.tzwinlocal() 2278 2279 self.assertEqual(str(tw), 'tzwinlocal(' + 2280 repr('Eastern Standard Time') + ')') 2281 2282 with TZWinContext('Pacific Standard Time'): 2283 tw = tz.tzwinlocal() 2284 2285 self.assertEqual(str(tw), 'tzwinlocal(' + 2286 repr('Pacific Standard Time') + ')') 2287 2288 def testTzwinLocalEquality(self): 2289 tw_est = tz.tzwin('Eastern Standard Time') 2290 tw_pst = tz.tzwin('Pacific Standard Time') 2291 2292 with TZWinContext('Eastern Standard Time'): 2293 twl1 = tz.tzwinlocal() 2294 twl2 = tz.tzwinlocal() 2295 2296 self.assertEqual(twl1, twl2) 2297 self.assertEqual(twl1, tw_est) 2298 self.assertNotEqual(twl1, tw_pst) 2299 2300 with TZWinContext('Pacific Standard Time'): 2301 twl1 = tz.tzwinlocal() 2302 twl2 = tz.tzwinlocal() 2303 tw = tz.tzwin('Pacific Standard Time') 2304 2305 self.assertEqual(twl1, twl2) 2306 self.assertEqual(twl1, tw) 2307 self.assertEqual(twl1, tw_pst) 2308 self.assertNotEqual(twl1, tw_est) 2309 2310 def testTzwinLocalTimeOnlyDST(self): 2311 # For zones with DST, .dst() should return None 2312 with TZWinContext('Eastern Standard Time'): 2313 twl = tz.tzwinlocal() 2314 self.assertIs(dt_time(14, 10, tzinfo=twl).dst(), None) 2315 2316 # This zone has no DST, so .dst() can return 0 2317 with TZWinContext('South Africa Standard Time'): 2318 twl = tz.tzwinlocal() 2319 self.assertEqual(dt_time(14, 10, tzinfo=twl).dst(), timedelta(0)) 2320 2321 def testTzwinLocalTimeOnlyUTCOffset(self): 2322 # For zones with DST, .utcoffset() should return None 2323 with TZWinContext('Eastern Standard Time'): 2324 twl = tz.tzwinlocal() 2325 self.assertIs(dt_time(14, 10, tzinfo=twl).utcoffset(), None) 2326 2327 # This zone has no DST, so .utcoffset() returns standard offset 2328 with TZWinContext('South Africa Standard Time'): 2329 twl = tz.tzwinlocal() 2330 self.assertEqual(dt_time(14, 10, tzinfo=twl).utcoffset(), 2331 timedelta(hours=2)) 2332 2333 def testTzwinLocalTimeOnlyTZName(self): 2334 # For zones with DST, the name defaults to standard time 2335 with TZWinContext('Eastern Standard Time'): 2336 twl = tz.tzwinlocal() 2337 self.assertEqual(dt_time(14, 10, tzinfo=twl).tzname(), 2338 'Eastern Standard Time') 2339 2340 # For zones with no DST, this should work normally. 2341 with TZWinContext('South Africa Standard Time'): 2342 twl = tz.tzwinlocal() 2343 self.assertEqual(dt_time(14, 10, tzinfo=twl).tzname(), 2344 'South Africa Standard Time') 2345 2346 2347class TzPickleTest(PicklableMixin, unittest.TestCase): 2348 _asfile = False 2349 2350 def setUp(self): 2351 self.assertPicklable = partial(self.assertPicklable, 2352 asfile=self._asfile) 2353 2354 def testPickleTzUTC(self): 2355 self.assertPicklable(tz.tzutc(), singleton=True) 2356 2357 def testPickleTzOffsetZero(self): 2358 self.assertPicklable(tz.tzoffset('UTC', 0), singleton=True) 2359 2360 def testPickleTzOffsetPos(self): 2361 self.assertPicklable(tz.tzoffset('UTC+1', 3600), singleton=True) 2362 2363 def testPickleTzOffsetNeg(self): 2364 self.assertPicklable(tz.tzoffset('UTC-1', -3600), singleton=True) 2365 2366 @pytest.mark.tzlocal 2367 def testPickleTzLocal(self): 2368 self.assertPicklable(tz.tzlocal()) 2369 2370 def testPickleTzFileEST5EDT(self): 2371 tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) 2372 self.assertPicklable(tzc) 2373 2374 def testPickleTzFileEurope_Helsinki(self): 2375 tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI))) 2376 self.assertPicklable(tzc) 2377 2378 def testPickleTzFileNew_York(self): 2379 tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK))) 2380 self.assertPicklable(tzc) 2381 2382 @unittest.skip("Known failure") 2383 def testPickleTzICal(self): 2384 tzc = tz.tzical(StringIO(TZICAL_EST5EDT)).get() 2385 self.assertPicklable(tzc) 2386 2387 def testPickleTzGettz(self): 2388 self.assertPicklable(tz.gettz('America/New_York')) 2389 2390 def testPickleZoneFileGettz(self): 2391 zoneinfo_file = zoneinfo.get_zonefile_instance() 2392 tzi = zoneinfo_file.get('America/New_York') 2393 self.assertIsNot(tzi, None) 2394 self.assertPicklable(tzi) 2395 2396 2397class TzPickleFileTest(TzPickleTest): 2398 """ Run all the TzPickleTest tests, using a temporary file """ 2399 _asfile = True 2400 2401 2402class DatetimeAmbiguousTest(unittest.TestCase): 2403 """ Test the datetime_exists / datetime_ambiguous functions """ 2404 2405 def testNoTzSpecified(self): 2406 with self.assertRaises(ValueError): 2407 tz.datetime_ambiguous(datetime(2016, 4, 1, 2, 9)) 2408 2409 def _get_no_support_tzinfo_class(self, dt_start, dt_end, dst_only=False): 2410 # Generates a class of tzinfo with no support for is_ambiguous 2411 # where dates between dt_start and dt_end are ambiguous. 2412 2413 class FoldingTzInfo(tzinfo): 2414 def utcoffset(self, dt): 2415 if not dst_only: 2416 dt_n = dt.replace(tzinfo=None) 2417 2418 if dt_start <= dt_n < dt_end and getattr(dt_n, 'fold', 0): 2419 return timedelta(hours=-1) 2420 2421 return timedelta(hours=0) 2422 2423 def dst(self, dt): 2424 dt_n = dt.replace(tzinfo=None) 2425 2426 if dt_start <= dt_n < dt_end and getattr(dt_n, 'fold', 0): 2427 return timedelta(hours=1) 2428 else: 2429 return timedelta(0) 2430 2431 return FoldingTzInfo 2432 2433 def _get_no_support_tzinfo(self, dt_start, dt_end, dst_only=False): 2434 return self._get_no_support_tzinfo_class(dt_start, dt_end, dst_only)() 2435 2436 def testNoSupportAmbiguityFoldNaive(self): 2437 dt_start = datetime(2018, 9, 1, 1, 0) 2438 dt_end = datetime(2018, 9, 1, 2, 0) 2439 2440 tzi = self._get_no_support_tzinfo(dt_start, dt_end) 2441 2442 self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), 2443 tz=tzi)) 2444 2445 def testNoSupportAmbiguityFoldAware(self): 2446 dt_start = datetime(2018, 9, 1, 1, 0) 2447 dt_end = datetime(2018, 9, 1, 2, 0) 2448 2449 tzi = self._get_no_support_tzinfo(dt_start, dt_end) 2450 2451 self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30, 2452 tzinfo=tzi))) 2453 2454 def testNoSupportAmbiguityUnambiguousNaive(self): 2455 dt_start = datetime(2018, 9, 1, 1, 0) 2456 dt_end = datetime(2018, 9, 1, 2, 0) 2457 2458 tzi = self._get_no_support_tzinfo(dt_start, dt_end) 2459 2460 self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), 2461 tz=tzi)) 2462 2463 def testNoSupportAmbiguityUnambiguousAware(self): 2464 dt_start = datetime(2018, 9, 1, 1, 0) 2465 dt_end = datetime(2018, 9, 1, 2, 0) 2466 2467 tzi = self._get_no_support_tzinfo(dt_start, dt_end) 2468 2469 self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30, 2470 tzinfo=tzi))) 2471 2472 def testNoSupportAmbiguityFoldDSTOnly(self): 2473 dt_start = datetime(2018, 9, 1, 1, 0) 2474 dt_end = datetime(2018, 9, 1, 2, 0) 2475 2476 tzi = self._get_no_support_tzinfo(dt_start, dt_end, dst_only=True) 2477 2478 self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), 2479 tz=tzi)) 2480 2481 def testNoSupportAmbiguityUnambiguousDSTOnly(self): 2482 dt_start = datetime(2018, 9, 1, 1, 0) 2483 dt_end = datetime(2018, 9, 1, 2, 0) 2484 2485 tzi = self._get_no_support_tzinfo(dt_start, dt_end, dst_only=True) 2486 2487 self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), 2488 tz=tzi)) 2489 2490 def testSupportAmbiguityFoldNaive(self): 2491 tzi = tz.gettz('US/Eastern') 2492 2493 dt = datetime(2011, 11, 6, 1, 30) 2494 2495 self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi)) 2496 2497 def testSupportAmbiguityFoldAware(self): 2498 tzi = tz.gettz('US/Eastern') 2499 2500 dt = datetime(2011, 11, 6, 1, 30, tzinfo=tzi) 2501 2502 self.assertTrue(tz.datetime_ambiguous(dt)) 2503 2504 def testSupportAmbiguityUnambiguousAware(self): 2505 tzi = tz.gettz('US/Eastern') 2506 2507 dt = datetime(2011, 11, 6, 4, 30) 2508 2509 self.assertFalse(tz.datetime_ambiguous(dt, tz=tzi)) 2510 2511 def testSupportAmbiguityUnambiguousNaive(self): 2512 tzi = tz.gettz('US/Eastern') 2513 2514 dt = datetime(2011, 11, 6, 4, 30, tzinfo=tzi) 2515 2516 self.assertFalse(tz.datetime_ambiguous(dt)) 2517 2518 def _get_ambig_error_tzinfo(self, dt_start, dt_end, dst_only=False): 2519 cTzInfo = self._get_no_support_tzinfo_class(dt_start, dt_end, dst_only) 2520 2521 # Takes the wrong number of arguments and raises an error anyway. 2522 class FoldTzInfoRaises(cTzInfo): 2523 def is_ambiguous(self, dt, other_arg): 2524 raise NotImplementedError('This is not implemented') 2525 2526 return FoldTzInfoRaises() 2527 2528 def testIncompatibleAmbiguityFoldNaive(self): 2529 dt_start = datetime(2018, 9, 1, 1, 0) 2530 dt_end = datetime(2018, 9, 1, 2, 0) 2531 2532 tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) 2533 2534 self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), 2535 tz=tzi)) 2536 2537 def testIncompatibleAmbiguityFoldAware(self): 2538 dt_start = datetime(2018, 9, 1, 1, 0) 2539 dt_end = datetime(2018, 9, 1, 2, 0) 2540 2541 tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) 2542 2543 self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30, 2544 tzinfo=tzi))) 2545 2546 def testIncompatibleAmbiguityUnambiguousNaive(self): 2547 dt_start = datetime(2018, 9, 1, 1, 0) 2548 dt_end = datetime(2018, 9, 1, 2, 0) 2549 2550 tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) 2551 2552 self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), 2553 tz=tzi)) 2554 2555 def testIncompatibleAmbiguityUnambiguousAware(self): 2556 dt_start = datetime(2018, 9, 1, 1, 0) 2557 dt_end = datetime(2018, 9, 1, 2, 0) 2558 2559 tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) 2560 2561 self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30, 2562 tzinfo=tzi))) 2563 2564 def testIncompatibleAmbiguityFoldDSTOnly(self): 2565 dt_start = datetime(2018, 9, 1, 1, 0) 2566 dt_end = datetime(2018, 9, 1, 2, 0) 2567 2568 tzi = self._get_ambig_error_tzinfo(dt_start, dt_end, dst_only=True) 2569 2570 self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), 2571 tz=tzi)) 2572 2573 def testIncompatibleAmbiguityUnambiguousDSTOnly(self): 2574 dt_start = datetime(2018, 9, 1, 1, 0) 2575 dt_end = datetime(2018, 9, 1, 2, 0) 2576 2577 tzi = self._get_ambig_error_tzinfo(dt_start, dt_end, dst_only=True) 2578 2579 self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), 2580 tz=tzi)) 2581 2582 def testSpecifiedTzOverridesAttached(self): 2583 # If a tz is specified, the datetime will be treated as naive. 2584 2585 # This is not ambiguous in the local zone 2586 dt = datetime(2011, 11, 6, 1, 30, tzinfo=tz.gettz('Australia/Sydney')) 2587 2588 self.assertFalse(tz.datetime_ambiguous(dt)) 2589 2590 tzi = tz.gettz('US/Eastern') 2591 self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi)) 2592 2593 2594class DatetimeExistsTest(unittest.TestCase): 2595 def testNoTzSpecified(self): 2596 with self.assertRaises(ValueError): 2597 tz.datetime_exists(datetime(2016, 4, 1, 2, 9)) 2598 2599 def testInGapNaive(self): 2600 tzi = tz.gettz('Australia/Sydney') 2601 2602 dt = datetime(2012, 10, 7, 2, 30) 2603 2604 self.assertFalse(tz.datetime_exists(dt, tz=tzi)) 2605 2606 def testInGapAware(self): 2607 tzi = tz.gettz('Australia/Sydney') 2608 2609 dt = datetime(2012, 10, 7, 2, 30, tzinfo=tzi) 2610 2611 self.assertFalse(tz.datetime_exists(dt)) 2612 2613 def testExistsNaive(self): 2614 tzi = tz.gettz('Australia/Sydney') 2615 2616 dt = datetime(2012, 10, 7, 10, 30) 2617 2618 self.assertTrue(tz.datetime_exists(dt, tz=tzi)) 2619 2620 def testExistsAware(self): 2621 tzi = tz.gettz('Australia/Sydney') 2622 2623 dt = datetime(2012, 10, 7, 10, 30, tzinfo=tzi) 2624 2625 self.assertTrue(tz.datetime_exists(dt)) 2626 2627 def testSpecifiedTzOverridesAttached(self): 2628 EST = tz.gettz('US/Eastern') 2629 AEST = tz.gettz('Australia/Sydney') 2630 2631 dt = datetime(2012, 10, 7, 2, 30, tzinfo=EST) # This time exists 2632 2633 self.assertFalse(tz.datetime_exists(dt, tz=AEST)) 2634 2635 2636class TestEnfold: 2637 def test_enter_fold_default(self): 2638 dt = tz.enfold(datetime(2020, 1, 19, 3, 32)) 2639 2640 assert dt.fold == 1 2641 2642 def test_enter_fold(self): 2643 dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1) 2644 2645 assert dt.fold == 1 2646 2647 def test_exit_fold(self): 2648 dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=0) 2649 2650 # Before Python 3.6, dt.fold won't exist if fold is 0. 2651 assert getattr(dt, 'fold', 0) == 0 2652 2653 def test_defold(self): 2654 dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1) 2655 2656 dt2 = tz.enfold(dt, fold=0) 2657 2658 assert getattr(dt2, 'fold', 0) == 0 2659 2660 def test_fold_replace_args(self): 2661 # This test can be dropped when Python < 3.6 is dropped, since it 2662 # is mainly to cover the `replace` method on _DatetimeWithFold 2663 dt = tz.enfold(datetime(1950, 1, 2, 12, 30, 15, 8), fold=1) 2664 2665 dt2 = dt.replace(1952, 2, 3, 13, 31, 16, 9) 2666 assert dt2 == tz.enfold(datetime(1952, 2, 3, 13, 31, 16, 9), fold=1) 2667 assert dt2.fold == 1 2668 2669 def test_fold_replace_exception_duplicate_args(self): 2670 dt = tz.enfold(datetime(1999, 1, 3), fold=1) 2671 2672 with pytest.raises(TypeError): 2673 dt.replace(1950, year=2000) 2674 2675 2676@pytest.mark.tz_resolve_imaginary 2677class ImaginaryDateTest(unittest.TestCase): 2678 def testCanberraForward(self): 2679 tzi = tz.gettz('Australia/Canberra') 2680 dt = datetime(2018, 10, 7, 2, 30, tzinfo=tzi) 2681 dt_act = tz.resolve_imaginary(dt) 2682 dt_exp = datetime(2018, 10, 7, 3, 30, tzinfo=tzi) 2683 self.assertEqual(dt_act, dt_exp) 2684 2685 def testLondonForward(self): 2686 tzi = tz.gettz('Europe/London') 2687 dt = datetime(2018, 3, 25, 1, 30, tzinfo=tzi) 2688 dt_act = tz.resolve_imaginary(dt) 2689 dt_exp = datetime(2018, 3, 25, 2, 30, tzinfo=tzi) 2690 self.assertEqual(dt_act, dt_exp) 2691 2692 def testKeivForward(self): 2693 tzi = tz.gettz('Europe/Kiev') 2694 dt = datetime(2018, 3, 25, 3, 30, tzinfo=tzi) 2695 dt_act = tz.resolve_imaginary(dt) 2696 dt_exp = datetime(2018, 3, 25, 4, 30, tzinfo=tzi) 2697 self.assertEqual(dt_act, dt_exp) 2698 2699 2700@pytest.mark.tz_resolve_imaginary 2701@pytest.mark.parametrize('dt', [ 2702 datetime(2017, 11, 5, 1, 30, tzinfo=tz.gettz('America/New_York')), 2703 datetime(2018, 10, 28, 1, 30, tzinfo=tz.gettz('Europe/London')), 2704 datetime(2017, 4, 2, 2, 30, tzinfo=tz.gettz('Australia/Sydney')), 2705]) 2706def test_resolve_imaginary_ambiguous(dt): 2707 assert tz.resolve_imaginary(dt) is dt 2708 2709 dt_f = tz.enfold(dt) 2710 assert dt is not dt_f 2711 assert tz.resolve_imaginary(dt_f) is dt_f 2712 2713 2714@pytest.mark.tz_resolve_imaginary 2715@pytest.mark.parametrize('dt', [ 2716 datetime(2017, 6, 2, 12, 30, tzinfo=tz.gettz('America/New_York')), 2717 datetime(2018, 4, 2, 9, 30, tzinfo=tz.gettz('Europe/London')), 2718 datetime(2017, 2, 2, 16, 30, tzinfo=tz.gettz('Australia/Sydney')), 2719 datetime(2017, 12, 2, 12, 30, tzinfo=tz.gettz('America/New_York')), 2720 datetime(2018, 12, 2, 9, 30, tzinfo=tz.gettz('Europe/London')), 2721 datetime(2017, 6, 2, 16, 30, tzinfo=tz.gettz('Australia/Sydney')), 2722 datetime(2025, 9, 25, 1, 17, tzinfo=tz.tzutc()), 2723 datetime(2025, 9, 25, 1, 17, tzinfo=tz.tzoffset('EST', -18000)), 2724 datetime(2019, 3, 4, tzinfo=None) 2725]) 2726def test_resolve_imaginary_existing(dt): 2727 assert tz.resolve_imaginary(dt) is dt 2728 2729 2730def __get_kiritimati_resolve_imaginary_test(): 2731 # In the 2018d release of the IANA database, the Kiritimati "imaginary day" 2732 # data was corrected, so if the system zoneinfo is older than 2018d, the 2733 # Kiritimati test will fail. 2734 2735 tzi = tz.gettz('Pacific/Kiritimati') 2736 new_version = False 2737 if not tz.datetime_exists(datetime(1995, 1, 1, 12, 30), tzi): 2738 zif = zoneinfo.get_zonefile_instance() 2739 if zif.metadata is not None: 2740 new_version = zif.metadata['tzversion'] >= '2018d' 2741 2742 if new_version: 2743 tzi = zif.get('Pacific/Kiritimati') 2744 else: 2745 new_version = True 2746 2747 if new_version: 2748 dates = (datetime(1994, 12, 31, 12, 30), datetime(1995, 1, 1, 12, 30)) 2749 else: 2750 dates = (datetime(1995, 1, 1, 12, 30), datetime(1995, 1, 2, 12, 30)) 2751 2752 return (tzi, ) + dates 2753 2754 2755resolve_imaginary_tests = [ 2756 (tz.gettz('Europe/London'), 2757 datetime(2018, 3, 25, 1, 30), datetime(2018, 3, 25, 2, 30)), 2758 (tz.gettz('America/New_York'), 2759 datetime(2017, 3, 12, 2, 30), datetime(2017, 3, 12, 3, 30)), 2760 (tz.gettz('Australia/Sydney'), 2761 datetime(2014, 10, 5, 2, 0), datetime(2014, 10, 5, 3, 0)), 2762 __get_kiritimati_resolve_imaginary_test(), 2763] 2764 2765 2766if SUPPORTS_SUB_MINUTE_OFFSETS: 2767 resolve_imaginary_tests.append( 2768 (tz.gettz('Africa/Monrovia'), 2769 datetime(1972, 1, 7, 0, 30), datetime(1972, 1, 7, 1, 14, 30))) 2770 2771 2772@pytest.mark.tz_resolve_imaginary 2773@pytest.mark.parametrize('tzi, dt, dt_exp', resolve_imaginary_tests) 2774def test_resolve_imaginary(tzi, dt, dt_exp): 2775 dt = dt.replace(tzinfo=tzi) 2776 dt_exp = dt_exp.replace(tzinfo=tzi) 2777 2778 dt_r = tz.resolve_imaginary(dt) 2779 assert dt_r == dt_exp 2780 assert dt_r.tzname() == dt_exp.tzname() 2781 assert dt_r.utcoffset() == dt_exp.utcoffset() 2782