| # -*- coding: utf-8 -*- |
| from __future__ import unicode_literals |
| from ._common import PicklableMixin |
| from ._common import TZEnvContext, TZWinContext |
| from ._common import WarningTestMixin |
| from ._common import ComparesEqual |
| |
| from datetime import datetime, timedelta |
| from datetime import time as dt_time |
| from datetime import tzinfo |
| from six import PY2 |
| from io import BytesIO, StringIO |
| import unittest |
| |
| import sys |
| import base64 |
| import copy |
| import gc |
| import weakref |
| |
| from functools import partial |
| |
| IS_WIN = sys.platform.startswith('win') |
| |
| import pytest |
| |
| # dateutil imports |
| from dateutil.relativedelta import relativedelta, SU, TH |
| from dateutil.parser import parse |
| from dateutil import tz as tz |
| from dateutil import zoneinfo |
| |
| try: |
| from dateutil import tzwin |
| except ImportError as e: |
| if IS_WIN: |
| raise e |
| else: |
| pass |
| |
| MISSING_TARBALL = ("This test fails if you don't have the dateutil " |
| "timezone file installed. Please read the README") |
| |
| TZFILE_EST5EDT = b""" |
| VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAADrAAAABAAAABCeph5wn7rrYKCGAHCh |
| ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e |
| S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0 |
| YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg |
| yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db |
| wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW |
| 8uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b |
| YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g |
| BGD9cAVQ4GAGQN9wBzDCYAeNGXAJEKRgCa2U8ArwhmAL4IVwDNmi4A3AZ3AOuYTgD6mD8BCZZuAR |
| iWXwEnlI4BNpR/AUWSrgFUkp8BY5DOAXKQvwGCIpYBkI7fAaAgtgGvIKcBvh7WAc0exwHcHPYB6x |
| znAfobFgIHYA8CGBk2AiVeLwI2qv4CQ1xPAlSpHgJhWm8Ccqc+An/sNwKQpV4CnepXAq6jfgK76H |
| cCzTVGAtnmlwLrM2YC9+S3AwkxhgMWdn8DJy+mAzR0nwNFLcYDUnK/A2Mr5gNwcN8Dgb2uA45u/w |
| Ofu84DrG0fA7257gPK/ucD27gOA+j9BwP5ti4EBvsnBBhH9gQk+UcENkYWBEL3ZwRURDYEYPWHBH |
| JCVgR/h08EkEB2BJ2FbwSuPpYEu4OPBMzQXgTZga8E6s5+BPd/zwUIzJ4FFhGXBSbKvgU0D7cFRM |
| jeBVIN1wVixv4FcAv3BYFYxgWOChcFn1bmBawINwW9VQYFypn/BdtTJgXomB8F+VFGBgaWPwYX4w |
| 4GJJRfBjXhLgZCkn8GU99OBmEkRwZx3W4GfyJnBo/bjgadIIcGrdmuBrsepwbMa3YG2RzHBupplg |
| b3GucHCGe2BxWsrwcmZdYHM6rPB0Rj9gdRqO8HYvW+B2+nDweA894HjaUvB57x/gero08HvPAeB8 |
| o1Fwfa7j4H6DM3B/jsXgAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB |
| AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB |
| AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA |
| AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB |
| AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU |
| AEVQVAAAAAABAAAAAQ== |
| """ |
| |
| EUROPE_HELSINKI = b""" |
| VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABQAAAAAAAAB1AAAABQAAAA2kc28Yy85RYMy/hdAV |
| I+uQFhPckBcDzZAX876QGOOvkBnToJAaw5GQG7y9EBysrhAdnJ8QHoyQEB98gRAgbHIQIVxjECJM |
| VBAjPEUQJCw2ECUcJxAmDBgQJwVDkCf1NJAo5SWQKdUWkCrFB5ArtPiQLKTpkC2U2pAuhMuQL3S8 |
| kDBkrZAxXdkQMnK0EDM9uxA0UpYQNR2dEDYyeBA2/X8QOBuUkDjdYRA5+3aQOr1DEDvbWJA8pl+Q |
| Pbs6kD6GQZA/mxyQQGYjkEGEORBCRgWQQ2QbEEQl55BFQ/0QRgXJkEcj3xBH7uYQSQPBEEnOyBBK |
| 46MQS66qEEzMv5BNjowQTqyhkE9ubhBQjIOQUVeKkFJsZZBTN2yQVExHkFUXTpBWLCmQVvcwkFgV |
| RhBY1xKQWfUoEFq29JBb1QoQXKAREF207BBef/MQX5TOEGBf1RBhfeqQYj+3EGNdzJBkH5kQZT2u |
| kGYItZBnHZCQZ+iXkGj9cpBpyHmQat1UkGuoW5BsxnEQbYg9kG6mUxBvaB+QcIY1EHFRPBByZhcQ |
| czEeEHRF+RB1EQAQdi8VkHbw4hB4DveQeNDEEHnu2ZB6sKYQe867kHyZwpB9rp2QfnmkkH+Of5AC |
| AQIDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQD |
| BAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAME |
| AwQAABdoAAAAACowAQQAABwgAAkAACowAQQAABwgAAlITVQARUVTVABFRVQAAAAAAQEAAAABAQ== |
| """ |
| |
| NEW_YORK = b""" |
| VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAABcAAADrAAAABAAAABCeph5wn7rrYKCGAHCh |
| ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e |
| S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0 |
| YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg |
| yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db |
| wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW |
| 8uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b |
| YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g |
| BGD9cAVQ4GEGQN9yBzDCYgeNGXMJEKRjCa2U9ArwhmQL4IV1DNmi5Q3AZ3YOuYTmD6mD9xCZZucR |
| iWX4EnlI6BNpR/kUWSrpFUkp+RY5DOoXKQv6GCIpaxkI7fsaAgtsGvIKfBvh7Wwc0ex8HcHPbR6x |
| zn0fobFtIHYA/SGBk20iVeL+I2qv7iQ1xP4lSpHuJhWm/ycqc+8n/sOAKQpV8CnepYAq6jfxK76H |
| gSzTVHItnmmCLrM2cy9+S4MwkxhzMWdoBDJy+nQzR0oENFLcdTUnLAU2Mr51NwcOBjgb2vY45vAG |
| Ofu89jrG0gY72572PK/uhj27gPY+j9CGP5ti9kBvsoZBhH92Qk+UhkNkYXZEL3aHRURDd0XzqQdH |
| LV/3R9OLB0kNQfdJs20HSu0j90uciYdM1kB3TXxrh062IndPXE2HUJYEd1E8L4dSdeZ3UxwRh1RV |
| yHdU+/OHVjWqd1blEAdYHsb3WMTyB1n+qPdapNQHW96K91yEtgddvmz3XmSYB1+eTvdgTbSHYYdr |
| d2ItlodjZ013ZA14h2VHL3dl7VqHZycRd2fNPIdpBvN3aa0eh2rm1XdrljsHbM/x9212HQdur9P3 |
| b1X/B3CPtfdxNeEHcm+X93MVwwd0T3n3dP7fh3Y4lnd23sGHeBh4d3i+o4d5+Fp3ep6Fh3vYPHd8 |
| fmeHfbged35eSYd/mAB3AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB |
| AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB |
| AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA |
| AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB |
| AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU |
| AEVQVAAEslgAAAAAAQWk7AEAAAACB4YfggAAAAMJZ1MDAAAABAtIhoQAAAAFDSsLhQAAAAYPDD8G |
| AAAABxDtcocAAAAIEs6mCAAAAAkVn8qJAAAACheA/goAAAALGWIxiwAAAAwdJeoMAAAADSHa5Q0A |
| AAAOJZ6djgAAAA8nf9EPAAAAECpQ9ZAAAAARLDIpEQAAABIuE1ySAAAAEzDnJBMAAAAUM7hIlAAA |
| ABU2jBAVAAAAFkO3G5YAAAAXAAAAAQAAAAE= |
| """ |
| |
| TZICAL_EST5EDT = """ |
| BEGIN:VTIMEZONE |
| TZID:US-Eastern |
| LAST-MODIFIED:19870101T000000Z |
| TZURL:http://zones.stds_r_us.net/tz/US-Eastern |
| BEGIN:STANDARD |
| DTSTART:19671029T020000 |
| RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 |
| TZOFFSETFROM:-0400 |
| TZOFFSETTO:-0500 |
| TZNAME:EST |
| END:STANDARD |
| BEGIN:DAYLIGHT |
| DTSTART:19870405T020000 |
| RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 |
| TZOFFSETFROM:-0500 |
| TZOFFSETTO:-0400 |
| TZNAME:EDT |
| END:DAYLIGHT |
| END:VTIMEZONE |
| """ |
| |
| TZICAL_PST8PDT = """ |
| BEGIN:VTIMEZONE |
| TZID:US-Pacific |
| LAST-MODIFIED:19870101T000000Z |
| BEGIN:STANDARD |
| DTSTART:19671029T020000 |
| RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 |
| TZOFFSETFROM:-0700 |
| TZOFFSETTO:-0800 |
| TZNAME:PST |
| END:STANDARD |
| BEGIN:DAYLIGHT |
| DTSTART:19870405T020000 |
| RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 |
| TZOFFSETFROM:-0800 |
| TZOFFSETTO:-0700 |
| TZNAME:PDT |
| END:DAYLIGHT |
| END:VTIMEZONE |
| """ |
| |
| EST_TUPLE = ('EST', timedelta(hours=-5), timedelta(hours=0)) |
| EDT_TUPLE = ('EDT', timedelta(hours=-4), timedelta(hours=1)) |
| |
| SUPPORTS_SUB_MINUTE_OFFSETS = sys.version_info >= (3, 6) |
| |
| |
| ### |
| # Helper functions |
| def get_timezone_tuple(dt): |
| """Retrieve a (tzname, utcoffset, dst) tuple for a given DST""" |
| return dt.tzname(), dt.utcoffset(), dt.dst() |
| |
| |
| ### |
| # Mix-ins |
| class context_passthrough(object): |
| def __init__(*args, **kwargs): |
| pass |
| |
| def __enter__(*args, **kwargs): |
| pass |
| |
| def __exit__(*args, **kwargs): |
| pass |
| |
| |
| class TzFoldMixin(object): |
| """ Mix-in class for testing ambiguous times """ |
| def gettz(self, tzname): |
| raise NotImplementedError |
| |
| def _get_tzname(self, tzname): |
| return tzname |
| |
| def _gettz_context(self, tzname): |
| return context_passthrough() |
| |
| def testFoldPositiveUTCOffset(self): |
| # Test that we can resolve ambiguous times |
| tzname = self._get_tzname('Australia/Sydney') |
| |
| with self._gettz_context(tzname): |
| SYD = self.gettz(tzname) |
| |
| t0_u = datetime(2012, 3, 31, 15, 30, tzinfo=tz.tzutc()) # AEST |
| t1_u = datetime(2012, 3, 31, 16, 30, tzinfo=tz.tzutc()) # AEDT |
| |
| t0_syd0 = t0_u.astimezone(SYD) |
| t1_syd1 = t1_u.astimezone(SYD) |
| |
| self.assertEqual(t0_syd0.replace(tzinfo=None), |
| datetime(2012, 4, 1, 2, 30)) |
| |
| self.assertEqual(t1_syd1.replace(tzinfo=None), |
| datetime(2012, 4, 1, 2, 30)) |
| |
| self.assertEqual(t0_syd0.utcoffset(), timedelta(hours=11)) |
| self.assertEqual(t1_syd1.utcoffset(), timedelta(hours=10)) |
| |
| def testGapPositiveUTCOffset(self): |
| # Test that we don't have a problem around gaps. |
| tzname = self._get_tzname('Australia/Sydney') |
| |
| with self._gettz_context(tzname): |
| SYD = self.gettz(tzname) |
| |
| t0_u = datetime(2012, 10, 6, 15, 30, tzinfo=tz.tzutc()) # AEST |
| t1_u = datetime(2012, 10, 6, 16, 30, tzinfo=tz.tzutc()) # AEDT |
| |
| t0 = t0_u.astimezone(SYD) |
| t1 = t1_u.astimezone(SYD) |
| |
| self.assertEqual(t0.replace(tzinfo=None), |
| datetime(2012, 10, 7, 1, 30)) |
| |
| self.assertEqual(t1.replace(tzinfo=None), |
| datetime(2012, 10, 7, 3, 30)) |
| |
| self.assertEqual(t0.utcoffset(), timedelta(hours=10)) |
| self.assertEqual(t1.utcoffset(), timedelta(hours=11)) |
| |
| def testFoldNegativeUTCOffset(self): |
| # Test that we can resolve ambiguous times |
| tzname = self._get_tzname('America/Toronto') |
| |
| with self._gettz_context(tzname): |
| TOR = self.gettz(tzname) |
| |
| t0_u = datetime(2011, 11, 6, 5, 30, tzinfo=tz.tzutc()) |
| t1_u = datetime(2011, 11, 6, 6, 30, tzinfo=tz.tzutc()) |
| |
| t0_tor = t0_u.astimezone(TOR) |
| t1_tor = t1_u.astimezone(TOR) |
| |
| self.assertEqual(t0_tor.replace(tzinfo=None), |
| datetime(2011, 11, 6, 1, 30)) |
| |
| self.assertEqual(t1_tor.replace(tzinfo=None), |
| datetime(2011, 11, 6, 1, 30)) |
| |
| self.assertNotEqual(t0_tor.tzname(), t1_tor.tzname()) |
| self.assertEqual(t0_tor.utcoffset(), timedelta(hours=-4.0)) |
| self.assertEqual(t1_tor.utcoffset(), timedelta(hours=-5.0)) |
| |
| def testGapNegativeUTCOffset(self): |
| # Test that we don't have a problem around gaps. |
| tzname = self._get_tzname('America/Toronto') |
| |
| with self._gettz_context(tzname): |
| TOR = self.gettz(tzname) |
| |
| t0_u = datetime(2011, 3, 13, 6, 30, tzinfo=tz.tzutc()) |
| t1_u = datetime(2011, 3, 13, 7, 30, tzinfo=tz.tzutc()) |
| |
| t0 = t0_u.astimezone(TOR) |
| t1 = t1_u.astimezone(TOR) |
| |
| self.assertEqual(t0.replace(tzinfo=None), |
| datetime(2011, 3, 13, 1, 30)) |
| |
| self.assertEqual(t1.replace(tzinfo=None), |
| datetime(2011, 3, 13, 3, 30)) |
| |
| self.assertNotEqual(t0, t1) |
| self.assertEqual(t0.utcoffset(), timedelta(hours=-5.0)) |
| self.assertEqual(t1.utcoffset(), timedelta(hours=-4.0)) |
| |
| def testFoldLondon(self): |
| tzname = self._get_tzname('Europe/London') |
| |
| with self._gettz_context(tzname): |
| LON = self.gettz(tzname) |
| UTC = tz.tzutc() |
| |
| t0_u = datetime(2013, 10, 27, 0, 30, tzinfo=UTC) # BST |
| t1_u = datetime(2013, 10, 27, 1, 30, tzinfo=UTC) # GMT |
| |
| t0 = t0_u.astimezone(LON) |
| t1 = t1_u.astimezone(LON) |
| |
| self.assertEqual(t0.replace(tzinfo=None), |
| datetime(2013, 10, 27, 1, 30)) |
| |
| self.assertEqual(t1.replace(tzinfo=None), |
| datetime(2013, 10, 27, 1, 30)) |
| |
| self.assertEqual(t0.utcoffset(), timedelta(hours=1)) |
| self.assertEqual(t1.utcoffset(), timedelta(hours=0)) |
| |
| def testFoldIndependence(self): |
| tzname = self._get_tzname('America/New_York') |
| |
| with self._gettz_context(tzname): |
| NYC = self.gettz(tzname) |
| UTC = tz.tzutc() |
| hour = timedelta(hours=1) |
| |
| # Firmly 2015-11-01 0:30 EDT-4 |
| pre_dst = datetime(2015, 11, 1, 0, 30, tzinfo=NYC) |
| |
| # Ambiguous between 2015-11-01 1:30 EDT-4 and 2015-11-01 1:30 EST-5 |
| in_dst = pre_dst + hour |
| in_dst_tzname_0 = in_dst.tzname() # Stash the tzname - EDT |
| |
| # Doing the arithmetic in UTC creates a date that is unambiguously |
| # 2015-11-01 1:30 EDT-5 |
| in_dst_via_utc = (pre_dst.astimezone(UTC) + 2*hour).astimezone(NYC) |
| |
| # Make sure the dates are actually ambiguous |
| self.assertEqual(in_dst, in_dst_via_utc) |
| |
| # Make sure we got the right folding behavior |
| self.assertNotEqual(in_dst_via_utc.tzname(), in_dst_tzname_0) |
| |
| # Now check to make sure in_dst's tzname hasn't changed |
| self.assertEqual(in_dst_tzname_0, in_dst.tzname()) |
| |
| def testInZoneFoldEquality(self): |
| # Two datetimes in the same zone are considered to be equal if their |
| # wall times are equal, even if they have different absolute times. |
| |
| tzname = self._get_tzname('America/New_York') |
| |
| with self._gettz_context(tzname): |
| NYC = self.gettz(tzname) |
| UTC = tz.tzutc() |
| |
| dt0 = datetime(2011, 11, 6, 1, 30, tzinfo=NYC) |
| dt1 = tz.enfold(dt0, fold=1) |
| |
| # Make sure these actually represent different times |
| self.assertNotEqual(dt0.astimezone(UTC), dt1.astimezone(UTC)) |
| |
| # Test that they compare equal |
| self.assertEqual(dt0, dt1) |
| |
| def _test_ambiguous_time(self, dt, tzid, ambiguous): |
| # This is a test to check that the individual is_ambiguous values |
| # on the _tzinfo subclasses work. |
| tzname = self._get_tzname(tzid) |
| |
| with self._gettz_context(tzname): |
| tzi = self.gettz(tzname) |
| |
| self.assertEqual(tz.datetime_ambiguous(dt, tz=tzi), ambiguous) |
| |
| def testAmbiguousNegativeUTCOffset(self): |
| self._test_ambiguous_time(datetime(2015, 11, 1, 1, 30), |
| 'America/New_York', True) |
| |
| def testAmbiguousPositiveUTCOffset(self): |
| self._test_ambiguous_time(datetime(2012, 4, 1, 2, 30), |
| 'Australia/Sydney', True) |
| |
| def testUnambiguousNegativeUTCOffset(self): |
| self._test_ambiguous_time(datetime(2015, 11, 1, 2, 30), |
| 'America/New_York', False) |
| |
| def testUnambiguousPositiveUTCOffset(self): |
| self._test_ambiguous_time(datetime(2012, 4, 1, 3, 30), |
| 'Australia/Sydney', False) |
| |
| def testUnambiguousGapNegativeUTCOffset(self): |
| # Imaginary time |
| self._test_ambiguous_time(datetime(2011, 3, 13, 2, 30), |
| 'America/New_York', False) |
| |
| def testUnambiguousGapPositiveUTCOffset(self): |
| # Imaginary time |
| self._test_ambiguous_time(datetime(2012, 10, 7, 2, 30), |
| 'Australia/Sydney', False) |
| |
| def _test_imaginary_time(self, dt, tzid, exists): |
| tzname = self._get_tzname(tzid) |
| with self._gettz_context(tzname): |
| tzi = self.gettz(tzname) |
| |
| self.assertEqual(tz.datetime_exists(dt, tz=tzi), exists) |
| |
| def testImaginaryNegativeUTCOffset(self): |
| self._test_imaginary_time(datetime(2011, 3, 13, 2, 30), |
| 'America/New_York', False) |
| |
| def testNotImaginaryNegativeUTCOffset(self): |
| self._test_imaginary_time(datetime(2011, 3, 13, 1, 30), |
| 'America/New_York', True) |
| |
| def testImaginaryPositiveUTCOffset(self): |
| self._test_imaginary_time(datetime(2012, 10, 7, 2, 30), |
| 'Australia/Sydney', False) |
| |
| def testNotImaginaryPositiveUTCOffset(self): |
| self._test_imaginary_time(datetime(2012, 10, 7, 1, 30), |
| 'Australia/Sydney', True) |
| |
| def testNotImaginaryFoldNegativeUTCOffset(self): |
| self._test_imaginary_time(datetime(2015, 11, 1, 1, 30), |
| 'America/New_York', True) |
| |
| def testNotImaginaryFoldPositiveUTCOffset(self): |
| self._test_imaginary_time(datetime(2012, 4, 1, 3, 30), |
| 'Australia/Sydney', True) |
| |
| @unittest.skip("Known failure in Python 3.6.") |
| def testEqualAmbiguousComparison(self): |
| tzname = self._get_tzname('Australia/Sydney') |
| |
| with self._gettz_context(tzname): |
| SYD0 = self.gettz(tzname) |
| SYD1 = self.gettz(tzname) |
| |
| t0_u = datetime(2012, 3, 31, 14, 30, tzinfo=tz.tzutc()) # AEST |
| |
| t0_syd0 = t0_u.astimezone(SYD0) |
| t0_syd1 = t0_u.astimezone(SYD1) |
| |
| # This is considered an "inter-zone comparison" because it's an |
| # ambiguous datetime. |
| self.assertEqual(t0_syd0, t0_syd1) |
| |
| |
| class TzWinFoldMixin(object): |
| def get_args(self, tzname): |
| return (tzname, ) |
| |
| class context(object): |
| def __init__(*args, **kwargs): |
| pass |
| |
| def __enter__(*args, **kwargs): |
| pass |
| |
| def __exit__(*args, **kwargs): |
| pass |
| |
| def get_utc_transitions(self, tzi, year, gap): |
| dston, dstoff = tzi.transitions(year) |
| if gap: |
| t_n = dston - timedelta(minutes=30) |
| |
| t0_u = t_n.replace(tzinfo=tzi).astimezone(tz.tzutc()) |
| t1_u = t0_u + timedelta(hours=1) |
| else: |
| # Get 1 hour before the first ambiguous date |
| t_n = dstoff - timedelta(minutes=30) |
| |
| t0_u = t_n.replace(tzinfo=tzi).astimezone(tz.tzutc()) |
| t_n += timedelta(hours=1) # Naive ambiguous date |
| t0_u = t0_u + timedelta(hours=1) # First ambiguous date |
| t1_u = t0_u + timedelta(hours=1) # Second ambiguous date |
| |
| return t_n, t0_u, t1_u |
| |
| def testFoldPositiveUTCOffset(self): |
| # Test that we can resolve ambiguous times |
| tzname = 'AUS Eastern Standard Time' |
| args = self.get_args(tzname) |
| |
| with self.context(tzname): |
| # Calling fromutc() alters the tzfile object |
| SYD = self.tzclass(*args) |
| |
| # Get the transition time in UTC from the object, because |
| # Windows doesn't store historical info |
| t_n, t0_u, t1_u = self.get_utc_transitions(SYD, 2012, False) |
| |
| # Using fresh tzfiles |
| t0_syd = t0_u.astimezone(SYD) |
| t1_syd = t1_u.astimezone(SYD) |
| |
| self.assertEqual(t0_syd.replace(tzinfo=None), t_n) |
| |
| self.assertEqual(t1_syd.replace(tzinfo=None), t_n) |
| |
| self.assertEqual(t0_syd.utcoffset(), timedelta(hours=11)) |
| self.assertEqual(t1_syd.utcoffset(), timedelta(hours=10)) |
| self.assertNotEqual(t0_syd.tzname(), t1_syd.tzname()) |
| |
| def testGapPositiveUTCOffset(self): |
| # Test that we don't have a problem around gaps. |
| tzname = 'AUS Eastern Standard Time' |
| args = self.get_args(tzname) |
| |
| with self.context(tzname): |
| SYD = self.tzclass(*args) |
| |
| t_n, t0_u, t1_u = self.get_utc_transitions(SYD, 2012, True) |
| |
| t0 = t0_u.astimezone(SYD) |
| t1 = t1_u.astimezone(SYD) |
| |
| self.assertEqual(t0.replace(tzinfo=None), t_n) |
| |
| self.assertEqual(t1.replace(tzinfo=None), t_n + timedelta(hours=2)) |
| |
| self.assertEqual(t0.utcoffset(), timedelta(hours=10)) |
| self.assertEqual(t1.utcoffset(), timedelta(hours=11)) |
| |
| def testFoldNegativeUTCOffset(self): |
| # Test that we can resolve ambiguous times |
| tzname = 'Eastern Standard Time' |
| args = self.get_args(tzname) |
| |
| with self.context(tzname): |
| TOR = self.tzclass(*args) |
| |
| t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, False) |
| |
| t0_tor = t0_u.astimezone(TOR) |
| t1_tor = t1_u.astimezone(TOR) |
| |
| self.assertEqual(t0_tor.replace(tzinfo=None), t_n) |
| self.assertEqual(t1_tor.replace(tzinfo=None), t_n) |
| |
| self.assertNotEqual(t0_tor.tzname(), t1_tor.tzname()) |
| self.assertEqual(t0_tor.utcoffset(), timedelta(hours=-4.0)) |
| self.assertEqual(t1_tor.utcoffset(), timedelta(hours=-5.0)) |
| |
| def testGapNegativeUTCOffset(self): |
| # Test that we don't have a problem around gaps. |
| tzname = 'Eastern Standard Time' |
| args = self.get_args(tzname) |
| |
| with self.context(tzname): |
| TOR = self.tzclass(*args) |
| |
| t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, True) |
| |
| t0 = t0_u.astimezone(TOR) |
| t1 = t1_u.astimezone(TOR) |
| |
| self.assertEqual(t0.replace(tzinfo=None), |
| t_n) |
| |
| self.assertEqual(t1.replace(tzinfo=None), |
| t_n + timedelta(hours=2)) |
| |
| self.assertNotEqual(t0.tzname(), t1.tzname()) |
| self.assertEqual(t0.utcoffset(), timedelta(hours=-5.0)) |
| self.assertEqual(t1.utcoffset(), timedelta(hours=-4.0)) |
| |
| def testFoldIndependence(self): |
| tzname = 'Eastern Standard Time' |
| args = self.get_args(tzname) |
| |
| with self.context(tzname): |
| NYC = self.tzclass(*args) |
| UTC = tz.tzutc() |
| hour = timedelta(hours=1) |
| |
| # Firmly 2015-11-01 0:30 EDT-4 |
| t_n, t0_u, t1_u = self.get_utc_transitions(NYC, 2015, False) |
| |
| pre_dst = (t_n - hour).replace(tzinfo=NYC) |
| |
| # Currently, there's no way around the fact that this resolves to an |
| # ambiguous date, which defaults to EST. I'm not hard-coding in the |
| # answer, though, because the preferred behavior would be that this |
| # results in a time on the EDT side. |
| |
| # Ambiguous between 2015-11-01 1:30 EDT-4 and 2015-11-01 1:30 EST-5 |
| in_dst = pre_dst + hour |
| in_dst_tzname_0 = in_dst.tzname() # Stash the tzname - EDT |
| |
| # Doing the arithmetic in UTC creates a date that is unambiguously |
| # 2015-11-01 1:30 EDT-5 |
| in_dst_via_utc = (pre_dst.astimezone(UTC) + 2*hour).astimezone(NYC) |
| |
| # Make sure we got the right folding behavior |
| self.assertNotEqual(in_dst_via_utc.tzname(), in_dst_tzname_0) |
| |
| # Now check to make sure in_dst's tzname hasn't changed |
| self.assertEqual(in_dst_tzname_0, in_dst.tzname()) |
| |
| def testInZoneFoldEquality(self): |
| # Two datetimes in the same zone are considered to be equal if their |
| # wall times are equal, even if they have different absolute times. |
| tzname = 'Eastern Standard Time' |
| args = self.get_args(tzname) |
| |
| with self.context(tzname): |
| NYC = self.tzclass(*args) |
| UTC = tz.tzutc() |
| |
| t_n, t0_u, t1_u = self.get_utc_transitions(NYC, 2011, False) |
| |
| dt0 = t_n.replace(tzinfo=NYC) |
| dt1 = tz.enfold(dt0, fold=1) |
| |
| # Make sure these actually represent different times |
| self.assertNotEqual(dt0.astimezone(UTC), dt1.astimezone(UTC)) |
| |
| # Test that they compare equal |
| self.assertEqual(dt0, dt1) |
| |
| ### |
| # Test Cases |
| class TzUTCTest(unittest.TestCase): |
| def testSingleton(self): |
| UTC_0 = tz.tzutc() |
| UTC_1 = tz.tzutc() |
| |
| self.assertIs(UTC_0, UTC_1) |
| |
| def testOffset(self): |
| ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc()) |
| |
| self.assertEqual(ct.utcoffset(), timedelta(seconds=0)) |
| |
| def testDst(self): |
| ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc()) |
| |
| self.assertEqual(ct.dst(), timedelta(seconds=0)) |
| |
| def testTzName(self): |
| ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc()) |
| self.assertEqual(ct.tzname(), 'UTC') |
| |
| def testEquality(self): |
| UTC0 = tz.tzutc() |
| UTC1 = tz.tzutc() |
| |
| self.assertEqual(UTC0, UTC1) |
| |
| def testInequality(self): |
| UTC = tz.tzutc() |
| UTCp4 = tz.tzoffset('UTC+4', 14400) |
| |
| self.assertNotEqual(UTC, UTCp4) |
| |
| def testInequalityInteger(self): |
| self.assertFalse(tz.tzutc() == 7) |
| self.assertNotEqual(tz.tzutc(), 7) |
| |
| def testInequalityUnsupported(self): |
| self.assertEqual(tz.tzutc(), ComparesEqual) |
| |
| def testRepr(self): |
| UTC = tz.tzutc() |
| self.assertEqual(repr(UTC), 'tzutc()') |
| |
| def testTimeOnlyUTC(self): |
| # https://github.com/dateutil/dateutil/issues/132 |
| # tzutc doesn't care |
| tz_utc = tz.tzutc() |
| self.assertEqual(dt_time(13, 20, tzinfo=tz_utc).utcoffset(), |
| timedelta(0)) |
| |
| def testAmbiguity(self): |
| # Pick an arbitrary datetime, this should always return False. |
| dt = datetime(2011, 9, 1, 2, 30, tzinfo=tz.tzutc()) |
| |
| self.assertFalse(tz.datetime_ambiguous(dt)) |
| |
| |
| @pytest.mark.tzoffset |
| class TzOffsetTest(unittest.TestCase): |
| def testTimedeltaOffset(self): |
| est = tz.tzoffset('EST', timedelta(hours=-5)) |
| est_s = tz.tzoffset('EST', -18000) |
| |
| self.assertEqual(est, est_s) |
| |
| def testTzNameNone(self): |
| gmt5 = tz.tzoffset(None, -18000) # -5:00 |
| self.assertIs(datetime(2003, 10, 26, 0, 0, tzinfo=gmt5).tzname(), |
| None) |
| |
| def testTimeOnlyOffset(self): |
| # tzoffset doesn't care |
| tz_offset = tz.tzoffset('+3', 3600) |
| self.assertEqual(dt_time(13, 20, tzinfo=tz_offset).utcoffset(), |
| timedelta(seconds=3600)) |
| |
| def testTzOffsetRepr(self): |
| tname = 'EST' |
| tzo = tz.tzoffset(tname, -5 * 3600) |
| self.assertEqual(repr(tzo), "tzoffset(" + repr(tname) + ", -18000)") |
| |
| def testEquality(self): |
| utc = tz.tzoffset('UTC', 0) |
| gmt = tz.tzoffset('GMT', 0) |
| |
| self.assertEqual(utc, gmt) |
| |
| def testUTCEquality(self): |
| utc = tz.tzutc() |
| o_utc = tz.tzoffset('UTC', 0) |
| |
| self.assertEqual(utc, o_utc) |
| self.assertEqual(o_utc, utc) |
| |
| def testInequalityInvalid(self): |
| tzo = tz.tzoffset('-3', -3 * 3600) |
| self.assertFalse(tzo == -3) |
| self.assertNotEqual(tzo, -3) |
| |
| def testInequalityUnsupported(self): |
| tzo = tz.tzoffset('-5', -5 * 3600) |
| |
| self.assertTrue(tzo == ComparesEqual) |
| self.assertFalse(tzo != ComparesEqual) |
| self.assertEqual(tzo, ComparesEqual) |
| |
| def testAmbiguity(self): |
| # Pick an arbitrary datetime, this should always return False. |
| dt = datetime(2011, 9, 1, 2, 30, tzinfo=tz.tzoffset("EST", -5 * 3600)) |
| |
| self.assertFalse(tz.datetime_ambiguous(dt)) |
| |
| def testTzOffsetInstance(self): |
| tz1 = tz.tzoffset.instance('EST', timedelta(hours=-5)) |
| tz2 = tz.tzoffset.instance('EST', timedelta(hours=-5)) |
| |
| assert tz1 is not tz2 |
| |
| def testTzOffsetSingletonDifferent(self): |
| tz1 = tz.tzoffset('EST', timedelta(hours=-5)) |
| tz2 = tz.tzoffset('EST', -18000) |
| |
| assert tz1 is tz2 |
| |
| |
| @pytest.mark.smoke |
| @pytest.mark.tzoffset |
| def test_tzoffset_weakref(): |
| UTC1 = tz.tzoffset('UTC', 0) |
| UTC_ref = weakref.ref(tz.tzoffset('UTC', 0)) |
| UTC1 is UTC_ref() |
| del UTC1 |
| gc.collect() |
| |
| assert UTC_ref() is not None # Should be in the strong cache |
| assert UTC_ref() is tz.tzoffset('UTC', 0) |
| |
| # Fill the strong cache with other items |
| for offset in range(5,15): |
| tz.tzoffset('RandomZone', offset) |
| |
| gc.collect() |
| assert UTC_ref() is None |
| assert UTC_ref() is not tz.tzoffset('UTC', 0) |
| |
| |
| @pytest.mark.tzoffset |
| @pytest.mark.parametrize('args', [ |
| ('UTC', 0), |
| ('EST', -18000), |
| ('EST', timedelta(hours=-5)), |
| (None, timedelta(hours=3)), |
| ]) |
| def test_tzoffset_singleton(args): |
| tz1 = tz.tzoffset(*args) |
| tz2 = tz.tzoffset(*args) |
| |
| assert tz1 is tz2 |
| |
| |
| @pytest.mark.tzoffset |
| @pytest.mark.skipif(not SUPPORTS_SUB_MINUTE_OFFSETS, |
| reason='Sub-minute offsets not supported') |
| def test_tzoffset_sub_minute(): |
| delta = timedelta(hours=12, seconds=30) |
| test_datetime = datetime(2000, 1, 1, tzinfo=tz.tzoffset(None, delta)) |
| assert test_datetime.utcoffset() == delta |
| |
| |
| @pytest.mark.tzoffset |
| @pytest.mark.skipif(SUPPORTS_SUB_MINUTE_OFFSETS, |
| reason='Sub-minute offsets supported') |
| def test_tzoffset_sub_minute_rounding(): |
| delta = timedelta(hours=12, seconds=30) |
| test_date = datetime(2000, 1, 1, tzinfo=tz.tzoffset(None, delta)) |
| assert test_date.utcoffset() == timedelta(hours=12, minutes=1) |
| |
| |
| @pytest.mark.tzlocal |
| class TzLocalTest(unittest.TestCase): |
| def testEquality(self): |
| tz1 = tz.tzlocal() |
| tz2 = tz.tzlocal() |
| |
| # Explicitly calling == and != here to ensure the operators work |
| self.assertTrue(tz1 == tz2) |
| self.assertFalse(tz1 != tz2) |
| |
| def testInequalityFixedOffset(self): |
| tzl = tz.tzlocal() |
| tzos = tz.tzoffset('LST', tzl._std_offset.total_seconds()) |
| tzod = tz.tzoffset('LDT', tzl._std_offset.total_seconds()) |
| |
| self.assertFalse(tzl == tzos) |
| self.assertFalse(tzl == tzod) |
| self.assertTrue(tzl != tzos) |
| self.assertTrue(tzl != tzod) |
| |
| def testInequalityInvalid(self): |
| tzl = tz.tzlocal() |
| |
| self.assertTrue(tzl != 1) |
| self.assertFalse(tzl == 1) |
| |
| # TODO: Use some sort of universal local mocking so that it's clear |
| # that we're expecting tzlocal to *not* be Pacific/Kiritimati |
| LINT = tz.gettz('Pacific/Kiritimati') |
| self.assertTrue(tzl != LINT) |
| self.assertFalse(tzl == LINT) |
| |
| def testInequalityUnsupported(self): |
| tzl = tz.tzlocal() |
| |
| self.assertTrue(tzl == ComparesEqual) |
| self.assertFalse(tzl != ComparesEqual) |
| |
| def testRepr(self): |
| tzl = tz.tzlocal() |
| |
| self.assertEqual(repr(tzl), 'tzlocal()') |
| |
| |
| @pytest.mark.parametrize('args,kwargs', [ |
| (('EST', -18000), {}), |
| (('EST', timedelta(hours=-5)), {}), |
| (('EST',), {'offset': -18000}), |
| (('EST',), {'offset': timedelta(hours=-5)}), |
| (tuple(), {'name': 'EST', 'offset': -18000}) |
| ]) |
| def test_tzoffset_is(args, kwargs): |
| tz_ref = tz.tzoffset('EST', -18000) |
| assert tz.tzoffset(*args, **kwargs) is tz_ref |
| |
| |
| def test_tzoffset_is_not(): |
| assert tz.tzoffset('EDT', -14400) is not tz.tzoffset('EST', -18000) |
| |
| |
| @pytest.mark.tzlocal |
| @unittest.skipIf(IS_WIN, "requires Unix") |
| @unittest.skipUnless(TZEnvContext.tz_change_allowed(), |
| TZEnvContext.tz_change_disallowed_message()) |
| class TzLocalNixTest(unittest.TestCase, TzFoldMixin): |
| # This is a set of tests for `tzlocal()` on *nix systems |
| |
| # POSIX string indicating change to summer time on the 2nd Sunday in March |
| # at 2AM, and ending the 1st Sunday in November at 2AM. (valid >= 2007) |
| TZ_EST = 'EST+5EDT,M3.2.0/2,M11.1.0/2' |
| |
| # POSIX string for AEST/AEDT (valid >= 2008) |
| TZ_AEST = 'AEST-10AEDT,M10.1.0/2,M4.1.0/3' |
| |
| # POSIX string for BST/GMT |
| TZ_LON = 'GMT0BST,M3.5.0,M10.5.0' |
| |
| # POSIX string for UTC |
| UTC = 'UTC' |
| |
| def gettz(self, tzname): |
| # Actual time zone changes are handled by the _gettz_context function |
| return tz.tzlocal() |
| |
| def _gettz_context(self, tzname): |
| tzname_map = {'Australia/Sydney': self.TZ_AEST, |
| 'America/Toronto': self.TZ_EST, |
| 'America/New_York': self.TZ_EST, |
| 'Europe/London': self.TZ_LON} |
| |
| return TZEnvContext(tzname_map.get(tzname, tzname)) |
| |
| def _testTzFunc(self, tzval, func, std_val, dst_val): |
| """ |
| This generates tests about how the behavior of a function ``func`` |
| changes between STD and DST (e.g. utcoffset, tzname, dst). |
| |
| It assume that DST starts the 2nd Sunday in March and ends the 1st |
| Sunday in November |
| """ |
| with TZEnvContext(tzval): |
| dt1 = datetime(2015, 2, 1, 12, 0, tzinfo=tz.tzlocal()) # STD |
| dt2 = datetime(2015, 5, 1, 12, 0, tzinfo=tz.tzlocal()) # DST |
| |
| self.assertEqual(func(dt1), std_val) |
| self.assertEqual(func(dt2), dst_val) |
| |
| def _testTzName(self, tzval, std_name, dst_name): |
| func = datetime.tzname |
| |
| self._testTzFunc(tzval, func, std_name, dst_name) |
| |
| def testTzNameDST(self): |
| # Test tzname in a zone with DST |
| self._testTzName(self.TZ_EST, 'EST', 'EDT') |
| |
| def testTzNameUTC(self): |
| # Test tzname in a zone without DST |
| self._testTzName(self.UTC, 'UTC', 'UTC') |
| |
| def _testOffset(self, tzval, std_off, dst_off): |
| func = datetime.utcoffset |
| |
| self._testTzFunc(tzval, func, std_off, dst_off) |
| |
| def testOffsetDST(self): |
| self._testOffset(self.TZ_EST, timedelta(hours=-5), timedelta(hours=-4)) |
| |
| def testOffsetUTC(self): |
| self._testOffset(self.UTC, timedelta(0), timedelta(0)) |
| |
| def _testDST(self, tzval, dst_dst): |
| func = datetime.dst |
| std_dst = timedelta(0) |
| |
| self._testTzFunc(tzval, func, std_dst, dst_dst) |
| |
| def testDSTDST(self): |
| self._testDST(self.TZ_EST, timedelta(hours=1)) |
| |
| def testDSTUTC(self): |
| self._testDST(self.UTC, timedelta(0)) |
| |
| def testTimeOnlyOffsetLocalUTC(self): |
| with TZEnvContext(self.UTC): |
| self.assertEqual(dt_time(13, 20, tzinfo=tz.tzlocal()).utcoffset(), |
| timedelta(0)) |
| |
| def testTimeOnlyOffsetLocalDST(self): |
| with TZEnvContext(self.TZ_EST): |
| self.assertIs(dt_time(13, 20, tzinfo=tz.tzlocal()).utcoffset(), |
| None) |
| |
| def testTimeOnlyDSTLocalUTC(self): |
| with TZEnvContext(self.UTC): |
| self.assertEqual(dt_time(13, 20, tzinfo=tz.tzlocal()).dst(), |
| timedelta(0)) |
| |
| def testTimeOnlyDSTLocalDST(self): |
| with TZEnvContext(self.TZ_EST): |
| self.assertIs(dt_time(13, 20, tzinfo=tz.tzlocal()).dst(), |
| None) |
| |
| def testUTCEquality(self): |
| with TZEnvContext(self.UTC): |
| assert tz.tzlocal() == tz.tzutc() |
| |
| |
| # TODO: Maybe a better hack than this? |
| def mark_tzlocal_nix(f): |
| marks = [ |
| pytest.mark.tzlocal, |
| pytest.mark.skipif(IS_WIN, reason='requires Unix'), |
| pytest.mark.skipif(not TZEnvContext.tz_change_allowed, |
| reason=TZEnvContext.tz_change_disallowed_message()) |
| ] |
| |
| for mark in reversed(marks): |
| f = mark(f) |
| |
| return f |
| |
| |
| @mark_tzlocal_nix |
| @pytest.mark.parametrize('tzvar', ['UTC', 'GMT0', 'UTC0']) |
| def test_tzlocal_utc_equal(tzvar): |
| with TZEnvContext(tzvar): |
| assert tz.tzlocal() == tz.UTC |
| |
| |
| @mark_tzlocal_nix |
| @pytest.mark.parametrize('tzvar', [ |
| 'Europe/London', 'America/New_York', |
| 'GMT0BST', 'EST5EDT']) |
| def test_tzlocal_utc_unequal(tzvar): |
| with TZEnvContext(tzvar): |
| assert tz.tzlocal() != tz.UTC |
| |
| |
| @mark_tzlocal_nix |
| def test_tzlocal_local_time_trim_colon(): |
| with TZEnvContext(':/etc/localtime'): |
| assert tz.gettz() is not None |
| |
| |
| @mark_tzlocal_nix |
| @pytest.mark.parametrize('tzvar, tzoff', [ |
| ('EST5', tz.tzoffset('EST', -18000)), |
| ('GMT', tz.tzoffset('GMT', 0)), |
| ('YAKT-9', tz.tzoffset('YAKT', timedelta(hours=9))), |
| ('JST-9', tz.tzoffset('JST', timedelta(hours=9))), |
| ]) |
| def test_tzlocal_offset_equal(tzvar, tzoff): |
| with TZEnvContext(tzvar): |
| # Including both to test both __eq__ and __ne__ |
| assert tz.tzlocal() == tzoff |
| assert not (tz.tzlocal() != tzoff) |
| |
| |
| @mark_tzlocal_nix |
| @pytest.mark.parametrize('tzvar, tzoff', [ |
| ('EST5EDT', tz.tzoffset('EST', -18000)), |
| ('GMT0BST', tz.tzoffset('GMT', 0)), |
| ('EST5', tz.tzoffset('EST', -14400)), |
| ('YAKT-9', tz.tzoffset('JST', timedelta(hours=9))), |
| ('JST-9', tz.tzoffset('YAKT', timedelta(hours=9))), |
| ]) |
| def test_tzlocal_offset_unequal(tzvar, tzoff): |
| with TZEnvContext(tzvar): |
| # Including both to test both __eq__ and __ne__ |
| assert tz.tzlocal() != tzoff |
| assert not (tz.tzlocal() == tzoff) |
| |
| |
| @pytest.mark.gettz |
| class GettzTest(unittest.TestCase, TzFoldMixin): |
| gettz = staticmethod(tz.gettz) |
| |
| def testGettz(self): |
| # bug 892569 |
| str(self.gettz('UTC')) |
| |
| def testGetTzEquality(self): |
| self.assertEqual(self.gettz('UTC'), self.gettz('UTC')) |
| |
| def testTimeOnlyGettz(self): |
| # gettz returns None |
| tz_get = self.gettz('Europe/Minsk') |
| self.assertIs(dt_time(13, 20, tzinfo=tz_get).utcoffset(), None) |
| |
| def testTimeOnlyGettzDST(self): |
| # gettz returns None |
| tz_get = self.gettz('Europe/Minsk') |
| self.assertIs(dt_time(13, 20, tzinfo=tz_get).dst(), None) |
| |
| def testTimeOnlyGettzTzName(self): |
| tz_get = self.gettz('Europe/Minsk') |
| self.assertIs(dt_time(13, 20, tzinfo=tz_get).tzname(), None) |
| |
| def testTimeOnlyFormatZ(self): |
| tz_get = self.gettz('Europe/Minsk') |
| t = dt_time(13, 20, tzinfo=tz_get) |
| |
| self.assertEqual(t.strftime('%H%M%Z'), '1320') |
| |
| def testPortugalDST(self): |
| # In 1996, Portugal changed from CET to WET |
| PORTUGAL = self.gettz('Portugal') |
| |
| t_cet = datetime(1996, 3, 31, 1, 59, tzinfo=PORTUGAL) |
| |
| self.assertEqual(t_cet.tzname(), 'CET') |
| self.assertEqual(t_cet.utcoffset(), timedelta(hours=1)) |
| self.assertEqual(t_cet.dst(), timedelta(0)) |
| |
| t_west = datetime(1996, 3, 31, 2, 1, tzinfo=PORTUGAL) |
| |
| self.assertEqual(t_west.tzname(), 'WEST') |
| self.assertEqual(t_west.utcoffset(), timedelta(hours=1)) |
| self.assertEqual(t_west.dst(), timedelta(hours=1)) |
| |
| def testGettzCacheTzFile(self): |
| NYC1 = tz.gettz('America/New_York') |
| NYC2 = tz.gettz('America/New_York') |
| |
| assert NYC1 is NYC2 |
| |
| def testGettzCacheTzLocal(self): |
| local1 = tz.gettz() |
| local2 = tz.gettz() |
| |
| assert local1 is not local2 |
| |
| |
| @pytest.mark.gettz |
| @pytest.mark.parametrize('badzone', [ |
| 'Fake.Region/Abcdefghijklmnop', # Violates several tz project name rules |
| ]) |
| def test_gettz_badzone(badzone): |
| # Make sure passing a bad TZ string to gettz returns None (GH #800) |
| tzi = tz.gettz(badzone) |
| assert tzi is None |
| |
| |
| @pytest.mark.gettz |
| def test_gettz_badzone_unicode(): |
| # Make sure a unicode string can be passed to TZ (GH #802) |
| # When fixed, combine this with test_gettz_badzone |
| tzi = tz.gettz('🐼') |
| assert tzi is None |
| |
| |
| @pytest.mark.gettz |
| @pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached') |
| def test_gettz_cache_clear(): |
| NYC1 = tz.gettz('America/New_York') |
| tz.gettz.cache_clear() |
| |
| NYC2 = tz.gettz('America/New_York') |
| |
| assert NYC1 is not NYC2 |
| |
| @pytest.mark.gettz |
| @pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached') |
| def test_gettz_set_cache_size(): |
| tz.gettz.cache_clear() |
| tz.gettz.set_cache_size(3) |
| |
| MONACO_ref = weakref.ref(tz.gettz('Europe/Monaco')) |
| EASTER_ref = weakref.ref(tz.gettz('Pacific/Easter')) |
| CURRIE_ref = weakref.ref(tz.gettz('Australia/Currie')) |
| |
| gc.collect() |
| |
| assert MONACO_ref() is not None |
| assert EASTER_ref() is not None |
| assert CURRIE_ref() is not None |
| |
| tz.gettz.set_cache_size(2) |
| gc.collect() |
| |
| assert MONACO_ref() is None |
| |
| @pytest.mark.xfail(IS_WIN, reason="Windows does not use system zoneinfo") |
| @pytest.mark.smoke |
| @pytest.mark.gettz |
| def test_gettz_weakref(): |
| tz.gettz.cache_clear() |
| tz.gettz.set_cache_size(2) |
| NYC1 = tz.gettz('America/New_York') |
| NYC_ref = weakref.ref(tz.gettz('America/New_York')) |
| |
| assert NYC1 is NYC_ref() |
| |
| del NYC1 |
| gc.collect() |
| |
| assert NYC_ref() is not None # Should still be in the strong cache |
| assert tz.gettz('America/New_York') is NYC_ref() |
| |
| # Populate strong cache with other timezones |
| tz.gettz('Europe/Monaco') |
| tz.gettz('Pacific/Easter') |
| tz.gettz('Australia/Currie') |
| |
| gc.collect() |
| assert NYC_ref() is None # Should have been pushed out |
| assert tz.gettz('America/New_York') is not NYC_ref() |
| |
| class ZoneInfoGettzTest(GettzTest, WarningTestMixin): |
| def gettz(self, name): |
| zoneinfo_file = zoneinfo.get_zonefile_instance() |
| return zoneinfo_file.get(name) |
| |
| def testZoneInfoFileStart1(self): |
| tz = self.gettz("EST5EDT") |
| self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname(), "EST", |
| MISSING_TARBALL) |
| self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname(), "EDT") |
| |
| def testZoneInfoFileEnd1(self): |
| tzc = self.gettz("EST5EDT") |
| self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(), |
| "EDT", MISSING_TARBALL) |
| |
| end_est = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tzc), fold=1) |
| self.assertEqual(end_est.tzname(), "EST") |
| |
| def testZoneInfoOffsetSignal(self): |
| utc = self.gettz("UTC") |
| nyc = self.gettz("America/New_York") |
| self.assertNotEqual(utc, None, MISSING_TARBALL) |
| self.assertNotEqual(nyc, None) |
| t0 = datetime(2007, 11, 4, 0, 30, tzinfo=nyc) |
| t1 = t0.astimezone(utc) |
| t2 = t1.astimezone(nyc) |
| self.assertEqual(t0, t2) |
| self.assertEqual(nyc.dst(t0), timedelta(hours=1)) |
| |
| def testZoneInfoCopy(self): |
| # copy.copy() called on a ZoneInfo file was returning the same instance |
| CHI = self.gettz('America/Chicago') |
| CHI_COPY = copy.copy(CHI) |
| |
| self.assertIsNot(CHI, CHI_COPY) |
| self.assertEqual(CHI, CHI_COPY) |
| |
| def testZoneInfoDeepCopy(self): |
| CHI = self.gettz('America/Chicago') |
| CHI_COPY = copy.deepcopy(CHI) |
| |
| self.assertIsNot(CHI, CHI_COPY) |
| self.assertEqual(CHI, CHI_COPY) |
| |
| def testZoneInfoInstanceCaching(self): |
| zif_0 = zoneinfo.get_zonefile_instance() |
| zif_1 = zoneinfo.get_zonefile_instance() |
| |
| self.assertIs(zif_0, zif_1) |
| |
| def testZoneInfoNewInstance(self): |
| zif_0 = zoneinfo.get_zonefile_instance() |
| zif_1 = zoneinfo.get_zonefile_instance(new_instance=True) |
| zif_2 = zoneinfo.get_zonefile_instance() |
| |
| self.assertIsNot(zif_0, zif_1) |
| self.assertIs(zif_1, zif_2) |
| |
| def testZoneInfoDeprecated(self): |
| with self.assertWarns(DeprecationWarning): |
| zoneinfo.gettz('US/Eastern') |
| |
| def testZoneInfoMetadataDeprecated(self): |
| with self.assertWarns(DeprecationWarning): |
| zoneinfo.gettz_db_metadata() |
| |
| |
| class TZRangeTest(unittest.TestCase, TzFoldMixin): |
| TZ_EST = tz.tzrange('EST', timedelta(hours=-5), |
| 'EDT', timedelta(hours=-4), |
| start=relativedelta(month=3, day=1, hour=2, |
| weekday=SU(+2)), |
| end=relativedelta(month=11, day=1, hour=1, |
| weekday=SU(+1))) |
| |
| TZ_AEST = tz.tzrange('AEST', timedelta(hours=10), |
| 'AEDT', timedelta(hours=11), |
| start=relativedelta(month=10, day=1, hour=2, |
| weekday=SU(+1)), |
| end=relativedelta(month=4, day=1, hour=2, |
| weekday=SU(+1))) |
| |
| TZ_LON = tz.tzrange('GMT', timedelta(hours=0), |
| 'BST', timedelta(hours=1), |
| start=relativedelta(month=3, day=31, weekday=SU(-1), |
| hours=2), |
| end=relativedelta(month=10, day=31, weekday=SU(-1), |
| hours=1)) |
| # POSIX string for UTC |
| UTC = 'UTC' |
| |
| def gettz(self, tzname): |
| tzname_map = {'Australia/Sydney': self.TZ_AEST, |
| 'America/Toronto': self.TZ_EST, |
| 'America/New_York': self.TZ_EST, |
| 'Europe/London': self.TZ_LON} |
| |
| return tzname_map[tzname] |
| |
| def testRangeCmp1(self): |
| self.assertEqual(tz.tzstr("EST5EDT"), |
| tz.tzrange("EST", -18000, "EDT", -14400, |
| relativedelta(hours=+2, |
| month=4, day=1, |
| weekday=SU(+1)), |
| relativedelta(hours=+1, |
| month=10, day=31, |
| weekday=SU(-1)))) |
| |
| def testRangeCmp2(self): |
| self.assertEqual(tz.tzstr("EST5EDT"), |
| tz.tzrange("EST", -18000, "EDT")) |
| |
| def testRangeOffsets(self): |
| TZR = tz.tzrange('EST', -18000, 'EDT', -14400, |
| start=relativedelta(hours=2, month=4, day=1, |
| weekday=SU(+2)), |
| end=relativedelta(hours=1, month=10, day=31, |
| weekday=SU(-1))) |
| |
| dt_std = datetime(2014, 4, 11, 12, 0, tzinfo=TZR) # STD |
| dt_dst = datetime(2016, 4, 11, 12, 0, tzinfo=TZR) # DST |
| |
| dst_zero = timedelta(0) |
| dst_hour = timedelta(hours=1) |
| |
| std_offset = timedelta(hours=-5) |
| dst_offset = timedelta(hours=-4) |
| |
| # Check dst() |
| self.assertEqual(dt_std.dst(), dst_zero) |
| self.assertEqual(dt_dst.dst(), dst_hour) |
| |
| # Check utcoffset() |
| self.assertEqual(dt_std.utcoffset(), std_offset) |
| self.assertEqual(dt_dst.utcoffset(), dst_offset) |
| |
| # Check tzname |
| self.assertEqual(dt_std.tzname(), 'EST') |
| self.assertEqual(dt_dst.tzname(), 'EDT') |
| |
| def testTimeOnlyRangeFixed(self): |
| # This is a fixed-offset zone, so tzrange allows this |
| tz_range = tz.tzrange('dflt', stdoffset=timedelta(hours=-3)) |
| self.assertEqual(dt_time(13, 20, tzinfo=tz_range).utcoffset(), |
| timedelta(hours=-3)) |
| |
| def testTimeOnlyRange(self): |
| # tzrange returns None because this zone has DST |
| tz_range = tz.tzrange('EST', timedelta(hours=-5), |
| 'EDT', timedelta(hours=-4)) |
| self.assertIs(dt_time(13, 20, tzinfo=tz_range).utcoffset(), None) |
| |
| def testBrokenIsDstHandling(self): |
| # tzrange._isdst() was using a date() rather than a datetime(). |
| # Issue reported by Lennart Regebro. |
| dt = datetime(2007, 8, 6, 4, 10, tzinfo=tz.tzutc()) |
| self.assertEqual(dt.astimezone(tz=tz.gettz("GMT+2")), |
| datetime(2007, 8, 6, 6, 10, tzinfo=tz.tzstr("GMT+2"))) |
| |
| def testRangeTimeDelta(self): |
| # Test that tzrange can be specified with a timedelta instead of an int. |
| EST5EDT_td = tz.tzrange('EST', timedelta(hours=-5), |
| 'EDT', timedelta(hours=-4)) |
| |
| EST5EDT_sec = tz.tzrange('EST', -18000, |
| 'EDT', -14400) |
| |
| self.assertEqual(EST5EDT_td, EST5EDT_sec) |
| |
| def testRangeEquality(self): |
| TZR1 = tz.tzrange('EST', -18000, 'EDT', -14400) |
| |
| # Standard abbreviation different |
| TZR2 = tz.tzrange('ET', -18000, 'EDT', -14400) |
| self.assertNotEqual(TZR1, TZR2) |
| |
| # DST abbreviation different |
| TZR3 = tz.tzrange('EST', -18000, 'EMT', -14400) |
| self.assertNotEqual(TZR1, TZR3) |
| |
| # STD offset different |
| TZR4 = tz.tzrange('EST', -14000, 'EDT', -14400) |
| self.assertNotEqual(TZR1, TZR4) |
| |
| # DST offset different |
| TZR5 = tz.tzrange('EST', -18000, 'EDT', -18000) |
| self.assertNotEqual(TZR1, TZR5) |
| |
| # Start delta different |
| TZR6 = tz.tzrange('EST', -18000, 'EDT', -14400, |
| start=relativedelta(hours=+1, month=3, |
| day=1, weekday=SU(+2))) |
| self.assertNotEqual(TZR1, TZR6) |
| |
| # End delta different |
| TZR7 = tz.tzrange('EST', -18000, 'EDT', -14400, |
| end=relativedelta(hours=+1, month=11, |
| day=1, weekday=SU(+2))) |
| self.assertNotEqual(TZR1, TZR7) |
| |
| def testRangeInequalityUnsupported(self): |
| TZR = tz.tzrange('EST', -18000, 'EDT', -14400) |
| |
| self.assertFalse(TZR == 4) |
| self.assertTrue(TZR == ComparesEqual) |
| self.assertFalse(TZR != ComparesEqual) |
| |
| |
| @pytest.mark.tzstr |
| class TZStrTest(unittest.TestCase, TzFoldMixin): |
| # POSIX string indicating change to summer time on the 2nd Sunday in March |
| # at 2AM, and ending the 1st Sunday in November at 2AM. (valid >= 2007) |
| TZ_EST = 'EST+5EDT,M3.2.0/2,M11.1.0/2' |
| |
| # POSIX string for AEST/AEDT (valid >= 2008) |
| TZ_AEST = 'AEST-10AEDT,M10.1.0/2,M4.1.0/3' |
| |
| # POSIX string for GMT/BST |
| TZ_LON = 'GMT0BST,M3.5.0,M10.5.0' |
| |
| def gettz(self, tzname): |
| # Actual time zone changes are handled by the _gettz_context function |
| tzname_map = {'Australia/Sydney': self.TZ_AEST, |
| 'America/Toronto': self.TZ_EST, |
| 'America/New_York': self.TZ_EST, |
| 'Europe/London': self.TZ_LON} |
| |
| return tz.tzstr(tzname_map[tzname]) |
| |
| def testStrStr(self): |
| # Test that tz.tzstr() won't throw an error if given a str instead |
| # of a unicode literal. |
| self.assertEqual(datetime(2003, 4, 6, 1, 59, |
| tzinfo=tz.tzstr(str("EST5EDT"))).tzname(), "EST") |
| self.assertEqual(datetime(2003, 4, 6, 2, 00, |
| tzinfo=tz.tzstr(str("EST5EDT"))).tzname(), "EDT") |
| |
| def testStrInequality(self): |
| TZS1 = tz.tzstr('EST5EDT4') |
| |
| # Standard abbreviation different |
| TZS2 = tz.tzstr('ET5EDT4') |
| self.assertNotEqual(TZS1, TZS2) |
| |
| # DST abbreviation different |
| TZS3 = tz.tzstr('EST5EMT') |
| self.assertNotEqual(TZS1, TZS3) |
| |
| # STD offset different |
| TZS4 = tz.tzstr('EST4EDT4') |
| self.assertNotEqual(TZS1, TZS4) |
| |
| # DST offset different |
| TZS5 = tz.tzstr('EST5EDT3') |
| self.assertNotEqual(TZS1, TZS5) |
| |
| def testStrInequalityStartEnd(self): |
| TZS1 = tz.tzstr('EST5EDT4') |
| |
| # Start delta different |
| TZS2 = tz.tzstr('EST5EDT4,M4.2.0/02:00:00,M10-5-0/02:00') |
| self.assertNotEqual(TZS1, TZS2) |
| |
| # End delta different |
| TZS3 = tz.tzstr('EST5EDT4,M4.2.0/02:00:00,M11-5-0/02:00') |
| self.assertNotEqual(TZS1, TZS3) |
| |
| def testPosixOffset(self): |
| TZ1 = tz.tzstr('UTC-3') |
| self.assertEqual(datetime(2015, 1, 1, tzinfo=TZ1).utcoffset(), |
| timedelta(hours=-3)) |
| |
| TZ2 = tz.tzstr('UTC-3', posix_offset=True) |
| self.assertEqual(datetime(2015, 1, 1, tzinfo=TZ2).utcoffset(), |
| timedelta(hours=+3)) |
| |
| def testStrInequalityUnsupported(self): |
| TZS = tz.tzstr('EST5EDT') |
| |
| self.assertFalse(TZS == 4) |
| self.assertTrue(TZS == ComparesEqual) |
| self.assertFalse(TZS != ComparesEqual) |
| |
| def testTzStrRepr(self): |
| TZS1 = tz.tzstr('EST5EDT4') |
| TZS2 = tz.tzstr('EST') |
| |
| self.assertEqual(repr(TZS1), "tzstr(" + repr('EST5EDT4') + ")") |
| self.assertEqual(repr(TZS2), "tzstr(" + repr('EST') + ")") |
| |
| def testTzStrFailure(self): |
| with self.assertRaises(ValueError): |
| tz.tzstr('InvalidString;439999') |
| |
| def testTzStrSingleton(self): |
| tz1 = tz.tzstr('EST5EDT') |
| tz2 = tz.tzstr('CST4CST') |
| tz3 = tz.tzstr('EST5EDT') |
| |
| self.assertIsNot(tz1, tz2) |
| self.assertIs(tz1, tz3) |
| |
| def testTzStrSingletonPosix(self): |
| tz_t1 = tz.tzstr('GMT+3', posix_offset=True) |
| tz_f1 = tz.tzstr('GMT+3', posix_offset=False) |
| |
| tz_t2 = tz.tzstr('GMT+3', posix_offset=True) |
| tz_f2 = tz.tzstr('GMT+3', posix_offset=False) |
| |
| self.assertIs(tz_t1, tz_t2) |
| self.assertIsNot(tz_t1, tz_f1) |
| |
| self.assertIs(tz_f1, tz_f2) |
| |
| def testTzStrInstance(self): |
| tz1 = tz.tzstr('EST5EDT') |
| tz2 = tz.tzstr.instance('EST5EDT') |
| tz3 = tz.tzstr.instance('EST5EDT') |
| |
| assert tz1 is not tz2 |
| assert tz2 is not tz3 |
| |
| # Ensure that these still are all the same zone |
| assert tz1 == tz2 == tz3 |
| |
| |
| @pytest.mark.smoke |
| @pytest.mark.tzstr |
| def test_tzstr_weakref(): |
| tz_t1 = tz.tzstr('EST5EDT') |
| tz_t2_ref = weakref.ref(tz.tzstr('EST5EDT')) |
| assert tz_t1 is tz_t2_ref() |
| |
| del tz_t1 |
| gc.collect() |
| |
| assert tz_t2_ref() is not None |
| assert tz.tzstr('EST5EDT') is tz_t2_ref() |
| |
| for offset in range(5,15): |
| tz.tzstr('GMT+{}'.format(offset)) |
| gc.collect() |
| |
| assert tz_t2_ref() is None |
| assert tz.tzstr('EST5EDT') is not tz_t2_ref() |
| |
| |
| @pytest.mark.tzstr |
| @pytest.mark.parametrize('tz_str,expected', [ |
| # From https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html |
| ('', tz.tzrange(None)), # TODO: Should change this so tz.tzrange('') works |
| ('EST+5EDT,M3.2.0/2,M11.1.0/12', |
| tz.tzrange('EST', -18000, 'EDT', -14400, |
| start=relativedelta(month=3, day=1, weekday=SU(2), hours=2), |
| end=relativedelta(month=11, day=1, weekday=SU(1), hours=11))), |
| ('WART4WARST,J1/0,J365/25', # This is DST all year, Western Argentina Summer Time |
| tz.tzrange('WART', timedelta(hours=-4), 'WARST', |
| start=relativedelta(month=1, day=1, hours=0), |
| end=relativedelta(month=12, day=31, days=1))), |
| ('IST-2IDT,M3.4.4/26,M10.5.0', # Israel Standard / Daylight Time |
| tz.tzrange('IST', timedelta(hours=2), 'IDT', |
| start=relativedelta(month=3, day=1, weekday=TH(4), days=1, hours=2), |
| end=relativedelta(month=10, day=31, weekday=SU(-1), hours=1))), |
| ('WGT3WGST,M3.5.0/2,M10.5.0/1', |
| tz.tzrange('WGT', timedelta(hours=-3), 'WGST', |
| start=relativedelta(month=3, day=31, weekday=SU(-1), hours=2), |
| end=relativedelta(month=10, day=31, weekday=SU(-1), hours=0))), |
| |
| # Different offset specifications |
| ('WGT0300WGST', |
| tz.tzrange('WGT', timedelta(hours=-3), 'WGST')), |
| ('WGT03:00WGST', |
| tz.tzrange('WGT', timedelta(hours=-3), 'WGST')), |
| ('AEST-1100AEDT', |
| tz.tzrange('AEST', timedelta(hours=11), 'AEDT')), |
| ('AEST-11:00AEDT', |
| tz.tzrange('AEST', timedelta(hours=11), 'AEDT')), |
| |
| # Different time formats |
| ('EST5EDT,M3.2.0/4:00,M11.1.0/3:00', |
| tz.tzrange('EST', timedelta(hours=-5), 'EDT', |
| start=relativedelta(month=3, day=1, weekday=SU(2), hours=4), |
| end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))), |
| ('EST5EDT,M3.2.0/04:00,M11.1.0/03:00', |
| tz.tzrange('EST', timedelta(hours=-5), 'EDT', |
| start=relativedelta(month=3, day=1, weekday=SU(2), hours=4), |
| end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))), |
| ('EST5EDT,M3.2.0/0400,M11.1.0/0300', |
| tz.tzrange('EST', timedelta(hours=-5), 'EDT', |
| start=relativedelta(month=3, day=1, weekday=SU(2), hours=4), |
| end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))), |
| ]) |
| def test_valid_GNU_tzstr(tz_str, expected): |
| tzi = tz.tzstr(tz_str) |
| |
| assert tzi == expected |
| |
| |
| @pytest.mark.tzstr |
| @pytest.mark.parametrize('tz_str, expected', [ |
| ('EST5EDT,5,4,0,7200,11,3,0,7200', |
| tz.tzrange('EST', timedelta(hours=-5), 'EDT', |
| start=relativedelta(month=5, day=1, weekday=SU(+4), hours=+2), |
| end=relativedelta(month=11, day=1, weekday=SU(+3), hours=+1))), |
| ('EST5EDT,5,-4,0,7200,11,3,0,7200', |
| tz.tzrange('EST', timedelta(hours=-5), 'EDT', |
| start=relativedelta(hours=+2, month=5, day=31, weekday=SU(-4)), |
| end=relativedelta(hours=+1, month=11, day=1, weekday=SU(+3)))), |
| ('EST5EDT,5,4,0,7200,11,-3,0,7200', |
| tz.tzrange('EST', timedelta(hours=-5), 'EDT', |
| start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), |
| end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), |
| ('EST5EDT,5,4,0,7200,11,-3,0,7200,3600', |
| tz.tzrange('EST', timedelta(hours=-5), 'EDT', |
| start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), |
| end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), |
| ('EST5EDT,5,4,0,7200,11,-3,0,7200,3600', |
| tz.tzrange('EST', timedelta(hours=-5), 'EDT', |
| start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), |
| end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), |
| ('EST5EDT,5,4,0,7200,11,-3,0,7200,-3600', |
| tz.tzrange('EST', timedelta(hours=-5), 'EDT', timedelta(hours=-6), |
| start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), |
| end=relativedelta(hours=+3, month=11, day=31, weekday=SU(-3)))), |
| ('EST5EDT,5,4,0,7200,11,-3,0,7200,+7200', |
| tz.tzrange('EST', timedelta(hours=-5), 'EDT', timedelta(hours=-3), |
| start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), |
| end=relativedelta(hours=0, month=11, day=31, weekday=SU(-3)))), |
| ('EST5EDT,5,4,0,7200,11,-3,0,7200,+3600', |
| tz.tzrange('EST', timedelta(hours=-5), 'EDT', |
| start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)), |
| end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))), |
| ]) |
| def test_valid_dateutil_format(tz_str, expected): |
| # This tests the dateutil-specific format that is used widely in the tests |
| # and examples. It is unclear where this format originated from. |
| with pytest.warns(tz.DeprecatedTzFormatWarning): |
| tzi = tz.tzstr.instance(tz_str) |
| |
| assert tzi == expected |
| |
| |
| @pytest.mark.tzstr |
| @pytest.mark.parametrize('tz_str', [ |
| 'hdfiughdfuig,dfughdfuigpu87ñ::', |
| ',dfughdfuigpu87ñ::', |
| '-1:WART4WARST,J1,J365/25', |
| 'WART4WARST,J1,J365/-25', |
| 'IST-2IDT,M3.4.-1/26,M10.5.0', |
| 'IST-2IDT,M3,2000,1/26,M10,5,0' |
| ]) |
| def test_invalid_GNU_tzstr(tz_str): |
| with pytest.raises(ValueError): |
| tz.tzstr(tz_str) |
| |
| |
| # Different representations of the same default rule set |
| DEFAULT_TZSTR_RULES_EQUIV_2003 = [ |
| 'EST5EDT', |
| 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00', |
| 'EST5EDT4,95/02:00:00,298/02:00', |
| 'EST5EDT4,J96/02:00:00,J299/02:00', |
| 'EST5EDT4,J96/02:00:00,J299/02' |
| ] |
| |
| |
| @pytest.mark.tzstr |
| @pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003) |
| def test_tzstr_default_start(tz_str): |
| tzi = tz.tzstr(tz_str) |
| dt_std = datetime(2003, 4, 6, 1, 59, tzinfo=tzi) |
| dt_dst = datetime(2003, 4, 6, 2, 00, tzinfo=tzi) |
| |
| assert get_timezone_tuple(dt_std) == EST_TUPLE |
| assert get_timezone_tuple(dt_dst) == EDT_TUPLE |
| |
| |
| @pytest.mark.tzstr |
| @pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003) |
| def test_tzstr_default_end(tz_str): |
| tzi = tz.tzstr(tz_str) |
| dt_dst = datetime(2003, 10, 26, 0, 59, tzinfo=tzi) |
| dt_dst_ambig = datetime(2003, 10, 26, 1, 00, tzinfo=tzi) |
| dt_std_ambig = tz.enfold(dt_dst_ambig, fold=1) |
| dt_std = datetime(2003, 10, 26, 2, 00, tzinfo=tzi) |
| |
| assert get_timezone_tuple(dt_dst) == EDT_TUPLE |
| assert get_timezone_tuple(dt_dst_ambig) == EDT_TUPLE |
| assert get_timezone_tuple(dt_std_ambig) == EST_TUPLE |
| assert get_timezone_tuple(dt_std) == EST_TUPLE |
| |
| |
| @pytest.mark.tzstr |
| @pytest.mark.parametrize('tzstr_1', ['EST5EDT', |
| 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00']) |
| @pytest.mark.parametrize('tzstr_2', ['EST5EDT', |
| 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00']) |
| def test_tzstr_default_cmp(tzstr_1, tzstr_2): |
| tz1 = tz.tzstr(tzstr_1) |
| tz2 = tz.tzstr(tzstr_2) |
| |
| assert tz1 == tz2 |
| |
| class TZICalTest(unittest.TestCase, TzFoldMixin): |
| def _gettz_str_tuple(self, tzname): |
| TZ_EST = ( |
| 'BEGIN:VTIMEZONE', |
| 'TZID:US-Eastern', |
| 'BEGIN:STANDARD', |
| 'DTSTART:19971029T020000', |
| 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=11', |
| 'TZOFFSETFROM:-0400', |
| 'TZOFFSETTO:-0500', |
| 'TZNAME:EST', |
| 'END:STANDARD', |
| 'BEGIN:DAYLIGHT', |
| 'DTSTART:19980301T020000', |
| 'RRULE:FREQ=YEARLY;BYDAY=+2SU;BYMONTH=03', |
| 'TZOFFSETFROM:-0500', |
| 'TZOFFSETTO:-0400', |
| 'TZNAME:EDT', |
| 'END:DAYLIGHT', |
| 'END:VTIMEZONE' |
| ) |
| |
| TZ_PST = ( |
| 'BEGIN:VTIMEZONE', |
| 'TZID:US-Pacific', |
| 'BEGIN:STANDARD', |
| 'DTSTART:19971029T020000', |
| 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=11', |
| 'TZOFFSETFROM:-0700', |
| 'TZOFFSETTO:-0800', |
| 'TZNAME:PST', |
| 'END:STANDARD', |
| 'BEGIN:DAYLIGHT', |
| 'DTSTART:19980301T020000', |
| 'RRULE:FREQ=YEARLY;BYDAY=+2SU;BYMONTH=03', |
| 'TZOFFSETFROM:-0800', |
| 'TZOFFSETTO:-0700', |
| 'TZNAME:PDT', |
| 'END:DAYLIGHT', |
| 'END:VTIMEZONE' |
| ) |
| |
| TZ_AEST = ( |
| 'BEGIN:VTIMEZONE', |
| 'TZID:Australia-Sydney', |
| 'BEGIN:STANDARD', |
| 'DTSTART:19980301T030000', |
| 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=04', |
| 'TZOFFSETFROM:+1100', |
| 'TZOFFSETTO:+1000', |
| 'TZNAME:AEST', |
| 'END:STANDARD', |
| 'BEGIN:DAYLIGHT', |
| 'DTSTART:19971029T020000', |
| 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=10', |
| 'TZOFFSETFROM:+1000', |
| 'TZOFFSETTO:+1100', |
| 'TZNAME:AEDT', |
| 'END:DAYLIGHT', |
| 'END:VTIMEZONE' |
| ) |
| |
| TZ_LON = ( |
| 'BEGIN:VTIMEZONE', |
| 'TZID:Europe-London', |
| 'BEGIN:STANDARD', |
| 'DTSTART:19810301T030000', |
| 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10;BYHOUR=02', |
| 'TZOFFSETFROM:+0100', |
| 'TZOFFSETTO:+0000', |
| 'TZNAME:GMT', |
| 'END:STANDARD', |
| 'BEGIN:DAYLIGHT', |
| 'DTSTART:19961001T030000', |
| 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=03;BYHOUR=01', |
| 'TZOFFSETFROM:+0000', |
| 'TZOFFSETTO:+0100', |
| 'TZNAME:BST', |
| 'END:DAYLIGHT', |
| 'END:VTIMEZONE' |
| ) |
| |
| tzname_map = {'Australia/Sydney': TZ_AEST, |
| 'America/Toronto': TZ_EST, |
| 'America/New_York': TZ_EST, |
| 'America/Los_Angeles': TZ_PST, |
| 'Europe/London': TZ_LON} |
| |
| return tzname_map[tzname] |
| |
| def _gettz_str(self, tzname): |
| return '\n'.join(self._gettz_str_tuple(tzname)) |
| |
| def _tzstr_dtstart_with_params(self, tzname, param_str): |
| # Adds parameters to the DTSTART values of a given tzstr |
| tz_str_tuple = self._gettz_str_tuple(tzname) |
| |
| out_tz = [] |
| for line in tz_str_tuple: |
| if line.startswith('DTSTART'): |
| name, value = line.split(':', 1) |
| line = name + ';' + param_str + ':' + value |
| |
| out_tz.append(line) |
| |
| return '\n'.join(out_tz) |
| |
| def gettz(self, tzname): |
| tz_str = self._gettz_str(tzname) |
| |
| tzc = tz.tzical(StringIO(tz_str)).get() |
| |
| return tzc |
| |
| def testRepr(self): |
| instr = StringIO(TZICAL_PST8PDT) |
| instr.name = 'StringIO(PST8PDT)' |
| tzc = tz.tzical(instr) |
| |
| self.assertEqual(repr(tzc), "tzical(" + repr(instr.name) + ")") |
| |
| # Test performance |
| def _test_us_zone(self, tzc, func, values, start): |
| if start: |
| dt1 = datetime(2003, 3, 9, 1, 59) |
| dt2 = datetime(2003, 3, 9, 2, 00) |
| fold = [0, 0] |
| else: |
| dt1 = datetime(2003, 11, 2, 0, 59) |
| dt2 = datetime(2003, 11, 2, 1, 00) |
| fold = [0, 1] |
| |
| dts = (tz.enfold(dt.replace(tzinfo=tzc), fold=f) |
| for dt, f in zip((dt1, dt2), fold)) |
| |
| for value, dt in zip(values, dts): |
| self.assertEqual(func(dt), value) |
| |
| def _test_multi_zones(self, tzstrs, tzids, func, values, start): |
| tzic = tz.tzical(StringIO('\n'.join(tzstrs))) |
| for tzid, vals in zip(tzids, values): |
| tzc = tzic.get(tzid) |
| |
| self._test_us_zone(tzc, func, vals, start) |
| |
| def _prepare_EST(self): |
| tz_str = self._gettz_str('America/New_York') |
| return tz.tzical(StringIO(tz_str)).get() |
| |
| def _testEST(self, start, test_type, tzc=None): |
| if tzc is None: |
| tzc = self._prepare_EST() |
| |
| argdict = { |
| 'name': (datetime.tzname, ('EST', 'EDT')), |
| 'offset': (datetime.utcoffset, (timedelta(hours=-5), |
| timedelta(hours=-4))), |
| 'dst': (datetime.dst, (timedelta(hours=0), |
| timedelta(hours=1))) |
| } |
| |
| func, values = argdict[test_type] |
| |
| if not start: |
| values = reversed(values) |
| |
| self._test_us_zone(tzc, func, values, start=start) |
| |
| def testESTStartName(self): |
| self._testEST(start=True, test_type='name') |
| |
| def testESTEndName(self): |
| self._testEST(start=False, test_type='name') |
| |
| def testESTStartOffset(self): |
| self._testEST(start=True, test_type='offset') |
| |
| def testESTEndOffset(self): |
| self._testEST(start=False, test_type='offset') |
| |
| def testESTStartDST(self): |
| self._testEST(start=True, test_type='dst') |
| |
| def testESTEndDST(self): |
| self._testEST(start=False, test_type='dst') |
| |
| def testESTValueDatetime(self): |
| # Violating one-test-per-test rule because we're not set up to do |
| # parameterized tests and the manual proliferation is getting a bit |
| # out of hand. |
| tz_str = self._tzstr_dtstart_with_params('America/New_York', |
| 'VALUE=DATE-TIME') |
| |
| tzc = tz.tzical(StringIO(tz_str)).get() |
| |
| for start in (True, False): |
| for test_type in ('name', 'offset', 'dst'): |
| self._testEST(start=start, test_type=test_type, tzc=tzc) |
| |
| def _testMultizone(self, start, test_type): |
| tzstrs = (self._gettz_str('America/New_York'), |
| self._gettz_str('America/Los_Angeles')) |
| tzids = ('US-Eastern', 'US-Pacific') |
| |
| argdict = { |
| 'name': (datetime.tzname, (('EST', 'EDT'), |
| ('PST', 'PDT'))), |
| 'offset': (datetime.utcoffset, ((timedelta(hours=-5), |
| timedelta(hours=-4)), |
| (timedelta(hours=-8), |
| timedelta(hours=-7)))), |
| 'dst': (datetime.dst, ((timedelta(hours=0), |
| timedelta(hours=1)), |
| (timedelta(hours=0), |
| timedelta(hours=1)))) |
| } |
| |
| func, values = argdict[test_type] |
| |
| if not start: |
| values = map(reversed, values) |
| |
| self._test_multi_zones(tzstrs, tzids, func, values, start) |
| |
| def testMultiZoneStartName(self): |
| self._testMultizone(start=True, test_type='name') |
| |
| def testMultiZoneEndName(self): |
| self._testMultizone(start=False, test_type='name') |
| |
| def testMultiZoneStartOffset(self): |
| self._testMultizone(start=True, test_type='offset') |
| |
| def testMultiZoneEndOffset(self): |
| self._testMultizone(start=False, test_type='offset') |
| |
| def testMultiZoneStartDST(self): |
| self._testMultizone(start=True, test_type='dst') |
| |
| def testMultiZoneEndDST(self): |
| self._testMultizone(start=False, test_type='dst') |
| |
| def testMultiZoneKeys(self): |
| est_str = self._gettz_str('America/New_York') |
| pst_str = self._gettz_str('America/Los_Angeles') |
| tzic = tz.tzical(StringIO('\n'.join((est_str, pst_str)))) |
| |
| # Sort keys because they are in a random order, being dictionary keys |
| keys = sorted(tzic.keys()) |
| |
| self.assertEqual(keys, ['US-Eastern', 'US-Pacific']) |
| |
| # Test error conditions |
| def testEmptyString(self): |
| with self.assertRaises(ValueError): |
| tz.tzical(StringIO("")) |
| |
| def testMultiZoneGet(self): |
| tzic = tz.tzical(StringIO(TZICAL_EST5EDT + TZICAL_PST8PDT)) |
| |
| with self.assertRaises(ValueError): |
| tzic.get() |
| |
| def testDtstartDate(self): |
| tz_str = self._tzstr_dtstart_with_params('America/New_York', |
| 'VALUE=DATE') |
| with self.assertRaises(ValueError): |
| tz.tzical(StringIO(tz_str)) |
| |
| def testDtstartTzid(self): |
| tz_str = self._tzstr_dtstart_with_params('America/New_York', |
| 'TZID=UTC') |
| with self.assertRaises(ValueError): |
| tz.tzical(StringIO(tz_str)) |
| |
| def testDtstartBadParam(self): |
| tz_str = self._tzstr_dtstart_with_params('America/New_York', |
| 'FOO=BAR') |
| with self.assertRaises(ValueError): |
| tz.tzical(StringIO(tz_str)) |
| |
| # Test Parsing |
| def testGap(self): |
| tzic = tz.tzical(StringIO('\n'.join((TZICAL_EST5EDT, TZICAL_PST8PDT)))) |
| |
| keys = sorted(tzic.keys()) |
| self.assertEqual(keys, ['US-Eastern', 'US-Pacific']) |
| |
| |
| class TZTest(unittest.TestCase): |
| def testFileStart1(self): |
| tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) |
| self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tzc).tzname(), "EST") |
| self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tzc).tzname(), "EDT") |
| |
| def testFileEnd1(self): |
| tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) |
| self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(), |
| "EDT") |
| end_est = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tzc)) |
| self.assertEqual(end_est.tzname(), "EST") |
| |
| def testFileLastTransition(self): |
| # After the last transition, it goes to standard time in perpetuity |
| tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) |
| self.assertEqual(datetime(2037, 10, 25, 0, 59, tzinfo=tzc).tzname(), |
| "EDT") |
| |
| last_date = tz.enfold(datetime(2037, 10, 25, 1, 00, tzinfo=tzc), fold=1) |
| self.assertEqual(last_date.tzname(), |
| "EST") |
| |
| self.assertEqual(datetime(2038, 5, 25, 12, 0, tzinfo=tzc).tzname(), |
| "EST") |
| |
| def testInvalidFile(self): |
| # Should throw a ValueError if an invalid file is passed |
| with self.assertRaises(ValueError): |
| tz.tzfile(BytesIO(b'BadFile')) |
| |
| def testFilestreamWithNameRepr(self): |
| # If fileobj is a filestream with a "name" attribute this name should |
| # be reflected in the tz object's repr |
| fileobj = BytesIO(base64.b64decode(TZFILE_EST5EDT)) |
| fileobj.name = 'foo' |
| tzc = tz.tzfile(fileobj) |
| self.assertEqual(repr(tzc), 'tzfile(' + repr('foo') + ')') |
| |
| def testLeapCountDecodesProperly(self): |
| # This timezone has leapcnt, and failed to decode until |
| # Eugene Oden notified about the issue. |
| |
| # As leap information is currently unused (and unstored) by tzfile() we |
| # can only indirectly test this: Take advantage of tzfile() not closing |
| # the input file if handed in as an opened file and assert that the |
| # full file content has been read by tzfile(). Note: For this test to |
| # work NEW_YORK must be in TZif version 1 format i.e. no more data |
| # after TZif v1 header + data has been read |
| fileobj = BytesIO(base64.b64decode(NEW_YORK)) |
| tz.tzfile(fileobj) |
| # we expect no remaining file content now, i.e. zero-length; if there's |
| # still data we haven't read the file format correctly |
| remaining_tzfile_content = fileobj.read() |
| self.assertEqual(len(remaining_tzfile_content), 0) |
| |
| def testIsStd(self): |
| # NEW_YORK tzfile contains this isstd information: |
| isstd_expected = (0, 0, 0, 1) |
| tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK))) |
| # gather the actual information as parsed by the tzfile class |
| isstd = [] |
| for ttinfo in tzc._ttinfo_list: |
| # ttinfo objects contain boolean values |
| isstd.append(int(ttinfo.isstd)) |
| # ttinfo list may contain more entries than isstd file content |
| isstd = tuple(isstd[:len(isstd_expected)]) |
| self.assertEqual( |
| isstd_expected, isstd, |
| "isstd UTC/local indicators parsed: %s != tzfile contents: %s" |
| % (isstd, isstd_expected)) |
| |
| def testGMTHasNoDaylight(self): |
| # tz.tzstr("GMT+2") improperly considered daylight saving time. |
| # Issue reported by Lennart Regebro. |
| dt = datetime(2007, 8, 6, 4, 10) |
| self.assertEqual(tz.gettz("GMT+2").dst(dt), timedelta(0)) |
| |
| def testGMTOffset(self): |
| # GMT and UTC offsets have inverted signal when compared to the |
| # usual TZ variable handling. |
| dt = datetime(2007, 8, 6, 4, 10, tzinfo=tz.tzutc()) |
| self.assertEqual(dt.astimezone(tz=tz.tzstr("GMT+2")), |
| datetime(2007, 8, 6, 6, 10, tzinfo=tz.tzstr("GMT+2"))) |
| self.assertEqual(dt.astimezone(tz=tz.gettz("UTC-2")), |
| datetime(2007, 8, 6, 2, 10, tzinfo=tz.tzstr("UTC-2"))) |
| |
| @unittest.skipIf(IS_WIN, "requires Unix") |
| @unittest.skipUnless(TZEnvContext.tz_change_allowed(), |
| TZEnvContext.tz_change_disallowed_message()) |
| def testTZSetDoesntCorrupt(self): |
| # if we start in non-UTC then tzset UTC make sure parse doesn't get |
| # confused |
| with TZEnvContext('UTC'): |
| # this should parse to UTC timezone not the original timezone |
| dt = parse('2014-07-20T12:34:56+00:00') |
| self.assertEqual(str(dt), '2014-07-20 12:34:56+00:00') |
| |
| |
| @pytest.mark.tzfile |
| @pytest.mark.skipif(not SUPPORTS_SUB_MINUTE_OFFSETS, |
| reason='Sub-minute offsets not supported') |
| def test_tzfile_sub_minute_offset(): |
| # If user running python 3.6 or newer, exact offset is used |
| tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI))) |
| offset = timedelta(hours=1, minutes=39, seconds=52) |
| assert datetime(1900, 1, 1, 0, 0, tzinfo=tzc).utcoffset() == offset |
| |
| |
| @pytest.mark.tzfile |
| @pytest.mark.skipif(SUPPORTS_SUB_MINUTE_OFFSETS, |
| reason='Sub-minute offsets supported.') |
| def test_sub_minute_rounding_tzfile(): |
| # This timezone has an offset of 5992 seconds in 1900-01-01. |
| # For python version pre-3.6, this will be rounded |
| tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI))) |
| offset = timedelta(hours=1, minutes=40) |
| assert datetime(1900, 1, 1, 0, 0, tzinfo=tzc).utcoffset() == offset |
| |
| |
| @pytest.mark.tzfile |
| def test_samoa_transition(): |
| # utcoffset() was erroneously returning +14:00 an hour early (GH #812) |
| APIA = tz.gettz('Pacific/Apia') |
| dt = datetime(2011, 12, 29, 23, 59, tzinfo=APIA) |
| assert dt.utcoffset() == timedelta(hours=-10) |
| |
| # Make sure the transition actually works, too |
| dt_after = (dt.astimezone(tz.UTC) + timedelta(minutes=1)).astimezone(APIA) |
| assert dt_after == datetime(2011, 12, 31, tzinfo=APIA) |
| assert dt_after.utcoffset() == timedelta(hours=14) |
| |
| |
| @unittest.skipUnless(IS_WIN, "Requires Windows") |
| class TzWinTest(unittest.TestCase, TzWinFoldMixin): |
| def setUp(self): |
| self.tzclass = tzwin.tzwin |
| |
| def testTzResLoadName(self): |
| # This may not work right on non-US locales. |
| tzr = tzwin.tzres() |
| self.assertEqual(tzr.load_name(112), "Eastern Standard Time") |
| |
| def testTzResNameFromString(self): |
| tzr = tzwin.tzres() |
| self.assertEqual(tzr.name_from_string('@tzres.dll,-221'), |
| 'Alaskan Daylight Time') |
| |
| self.assertEqual(tzr.name_from_string('Samoa Daylight Time'), |
| 'Samoa Daylight Time') |
| |
| with self.assertRaises(ValueError): |
| tzr.name_from_string('@tzres.dll,100') |
| |
| def testIsdstZoneWithNoDaylightSaving(self): |
| tz = tzwin.tzwin("UTC") |
| dt = parse("2013-03-06 19:08:15") |
| self.assertFalse(tz._isdst(dt)) |
| |
| def testOffset(self): |
| tz = tzwin.tzwin("Cape Verde Standard Time") |
| self.assertEqual(tz.utcoffset(datetime(1995, 5, 21, 12, 9, 13)), |
| timedelta(-1, 82800)) |
| |
| def testTzwinName(self): |
| # https://github.com/dateutil/dateutil/issues/143 |
| tw = tz.tzwin('Eastern Standard Time') |
| |
| # Cover the transitions for at least two years. |
| ESTs = 'Eastern Standard Time' |
| EDTs = 'Eastern Daylight Time' |
| transition_dates = [(datetime(2015, 3, 8, 0, 59), ESTs), |
| (datetime(2015, 3, 8, 3, 1), EDTs), |
| (datetime(2015, 11, 1, 0, 59), EDTs), |
| (datetime(2015, 11, 1, 3, 1), ESTs), |
| (datetime(2016, 3, 13, 0, 59), ESTs), |
| (datetime(2016, 3, 13, 3, 1), EDTs), |
| (datetime(2016, 11, 6, 0, 59), EDTs), |
| (datetime(2016, 11, 6, 3, 1), ESTs)] |
| |
| for t_date, expected in transition_dates: |
| self.assertEqual(t_date.replace(tzinfo=tw).tzname(), expected) |
| |
| def testTzwinRepr(self): |
| tw = tz.tzwin('Yakutsk Standard Time') |
| self.assertEqual(repr(tw), 'tzwin(' + |
| repr('Yakutsk Standard Time') + ')') |
| |
| def testTzWinEquality(self): |
| # https://github.com/dateutil/dateutil/issues/151 |
| tzwin_names = ('Eastern Standard Time', |
| 'West Pacific Standard Time', |
| 'Yakutsk Standard Time', |
| 'Iran Standard Time', |
| 'UTC') |
| |
| for tzwin_name in tzwin_names: |
| # Get two different instances to compare |
| tw1 = tz.tzwin(tzwin_name) |
| tw2 = tz.tzwin(tzwin_name) |
| |
| self.assertEqual(tw1, tw2) |
| |
| def testTzWinInequality(self): |
| # https://github.com/dateutil/dateutil/issues/151 |
| # Note these last two currently differ only in their name. |
| tzwin_names = (('Eastern Standard Time', 'Yakutsk Standard Time'), |
| ('Greenwich Standard Time', 'GMT Standard Time'), |
| ('GMT Standard Time', 'UTC'), |
| ('E. South America Standard Time', |
| 'Argentina Standard Time')) |
| |
| for tzwn1, tzwn2 in tzwin_names: |
| # Get two different instances to compare |
| tw1 = tz.tzwin(tzwn1) |
| tw2 = tz.tzwin(tzwn2) |
| |
| self.assertNotEqual(tw1, tw2) |
| |
| def testTzWinEqualityInvalid(self): |
| # Compare to objects that do not implement comparison with this |
| # (should default to False) |
| UTC = tz.tzutc() |
| EST = tz.tzwin('Eastern Standard Time') |
| |
| self.assertFalse(EST == UTC) |
| self.assertFalse(EST == 1) |
| self.assertFalse(UTC == EST) |
| |
| self.assertTrue(EST != UTC) |
| self.assertTrue(EST != 1) |
| |
| def testTzWinInequalityUnsupported(self): |
| # Compare it to an object that is promiscuous about equality, but for |
| # which tzwin does not implement an equality operator. |
| EST = tz.tzwin('Eastern Standard Time') |
| self.assertTrue(EST == ComparesEqual) |
| self.assertFalse(EST != ComparesEqual) |
| |
| def testTzwinTimeOnlyDST(self): |
| # For zones with DST, .dst() should return None |
| tw_est = tz.tzwin('Eastern Standard Time') |
| self.assertIs(dt_time(14, 10, tzinfo=tw_est).dst(), None) |
| |
| # This zone has no DST, so .dst() can return 0 |
| tw_sast = tz.tzwin('South Africa Standard Time') |
| self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).dst(), |
| timedelta(0)) |
| |
| def testTzwinTimeOnlyUTCOffset(self): |
| # For zones with DST, .utcoffset() should return None |
| tw_est = tz.tzwin('Eastern Standard Time') |
| self.assertIs(dt_time(14, 10, tzinfo=tw_est).utcoffset(), None) |
| |
| # This zone has no DST, so .utcoffset() returns standard offset |
| tw_sast = tz.tzwin('South Africa Standard Time') |
| self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).utcoffset(), |
| timedelta(hours=2)) |
| |
| def testTzwinTimeOnlyTZName(self): |
| # For zones with DST, the name defaults to standard time |
| tw_est = tz.tzwin('Eastern Standard Time') |
| self.assertEqual(dt_time(14, 10, tzinfo=tw_est).tzname(), |
| 'Eastern Standard Time') |
| |
| # For zones with no DST, this should work normally. |
| tw_sast = tz.tzwin('South Africa Standard Time') |
| self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).tzname(), |
| 'South Africa Standard Time') |
| |
| |
| @unittest.skipUnless(IS_WIN, "Requires Windows") |
| @unittest.skipUnless(TZWinContext.tz_change_allowed(), |
| TZWinContext.tz_change_disallowed_message()) |
| class TzWinLocalTest(unittest.TestCase, TzWinFoldMixin): |
| |
| def setUp(self): |
| self.tzclass = tzwin.tzwinlocal |
| self.context = TZWinContext |
| |
| def get_args(self, tzname): |
| return () |
| |
| def testLocal(self): |
| # Not sure how to pin a local time zone, so for now we're just going |
| # to run this and make sure it doesn't raise an error |
| # See Github Issue #135: https://github.com/dateutil/dateutil/issues/135 |
| datetime.now(tzwin.tzwinlocal()) |
| |
| def testTzwinLocalUTCOffset(self): |
| with TZWinContext('Eastern Standard Time'): |
| tzwl = tzwin.tzwinlocal() |
| self.assertEqual(datetime(2014, 3, 11, tzinfo=tzwl).utcoffset(), |
| timedelta(hours=-4)) |
| |
| def testTzwinLocalName(self): |
| # https://github.com/dateutil/dateutil/issues/143 |
| ESTs = 'Eastern Standard Time' |
| EDTs = 'Eastern Daylight Time' |
| transition_dates = [(datetime(2015, 3, 8, 0, 59), ESTs), |
| (datetime(2015, 3, 8, 3, 1), EDTs), |
| (datetime(2015, 11, 1, 0, 59), EDTs), |
| (datetime(2015, 11, 1, 3, 1), ESTs), |
| (datetime(2016, 3, 13, 0, 59), ESTs), |
| (datetime(2016, 3, 13, 3, 1), EDTs), |
| (datetime(2016, 11, 6, 0, 59), EDTs), |
| (datetime(2016, 11, 6, 3, 1), ESTs)] |
| |
| with TZWinContext('Eastern Standard Time'): |
| tw = tz.tzwinlocal() |
| |
| for t_date, expected in transition_dates: |
| self.assertEqual(t_date.replace(tzinfo=tw).tzname(), expected) |
| |
| def testTzWinLocalRepr(self): |
| tw = tz.tzwinlocal() |
| self.assertEqual(repr(tw), 'tzwinlocal()') |
| |
| def testTzwinLocalRepr(self): |
| # https://github.com/dateutil/dateutil/issues/143 |
| with TZWinContext('Eastern Standard Time'): |
| tw = tz.tzwinlocal() |
| |
| self.assertEqual(str(tw), 'tzwinlocal(' + |
| repr('Eastern Standard Time') + ')') |
| |
| with TZWinContext('Pacific Standard Time'): |
| tw = tz.tzwinlocal() |
| |
| self.assertEqual(str(tw), 'tzwinlocal(' + |
| repr('Pacific Standard Time') + ')') |
| |
| def testTzwinLocalEquality(self): |
| tw_est = tz.tzwin('Eastern Standard Time') |
| tw_pst = tz.tzwin('Pacific Standard Time') |
| |
| with TZWinContext('Eastern Standard Time'): |
| twl1 = tz.tzwinlocal() |
| twl2 = tz.tzwinlocal() |
| |
| self.assertEqual(twl1, twl2) |
| self.assertEqual(twl1, tw_est) |
| self.assertNotEqual(twl1, tw_pst) |
| |
| with TZWinContext('Pacific Standard Time'): |
| twl1 = tz.tzwinlocal() |
| twl2 = tz.tzwinlocal() |
| tw = tz.tzwin('Pacific Standard Time') |
| |
| self.assertEqual(twl1, twl2) |
| self.assertEqual(twl1, tw) |
| self.assertEqual(twl1, tw_pst) |
| self.assertNotEqual(twl1, tw_est) |
| |
| def testTzwinLocalTimeOnlyDST(self): |
| # For zones with DST, .dst() should return None |
| with TZWinContext('Eastern Standard Time'): |
| twl = tz.tzwinlocal() |
| self.assertIs(dt_time(14, 10, tzinfo=twl).dst(), None) |
| |
| # This zone has no DST, so .dst() can return 0 |
| with TZWinContext('South Africa Standard Time'): |
| twl = tz.tzwinlocal() |
| self.assertEqual(dt_time(14, 10, tzinfo=twl).dst(), timedelta(0)) |
| |
| def testTzwinLocalTimeOnlyUTCOffset(self): |
| # For zones with DST, .utcoffset() should return None |
| with TZWinContext('Eastern Standard Time'): |
| twl = tz.tzwinlocal() |
| self.assertIs(dt_time(14, 10, tzinfo=twl).utcoffset(), None) |
| |
| # This zone has no DST, so .utcoffset() returns standard offset |
| with TZWinContext('South Africa Standard Time'): |
| twl = tz.tzwinlocal() |
| self.assertEqual(dt_time(14, 10, tzinfo=twl).utcoffset(), |
| timedelta(hours=2)) |
| |
| def testTzwinLocalTimeOnlyTZName(self): |
| # For zones with DST, the name defaults to standard time |
| with TZWinContext('Eastern Standard Time'): |
| twl = tz.tzwinlocal() |
| self.assertEqual(dt_time(14, 10, tzinfo=twl).tzname(), |
| 'Eastern Standard Time') |
| |
| # For zones with no DST, this should work normally. |
| with TZWinContext('South Africa Standard Time'): |
| twl = tz.tzwinlocal() |
| self.assertEqual(dt_time(14, 10, tzinfo=twl).tzname(), |
| 'South Africa Standard Time') |
| |
| |
| class TzPickleTest(PicklableMixin, unittest.TestCase): |
| _asfile = False |
| |
| def setUp(self): |
| self.assertPicklable = partial(self.assertPicklable, |
| asfile=self._asfile) |
| |
| def testPickleTzUTC(self): |
| self.assertPicklable(tz.tzutc(), singleton=True) |
| |
| def testPickleTzOffsetZero(self): |
| self.assertPicklable(tz.tzoffset('UTC', 0), singleton=True) |
| |
| def testPickleTzOffsetPos(self): |
| self.assertPicklable(tz.tzoffset('UTC+1', 3600), singleton=True) |
| |
| def testPickleTzOffsetNeg(self): |
| self.assertPicklable(tz.tzoffset('UTC-1', -3600), singleton=True) |
| |
| @pytest.mark.tzlocal |
| def testPickleTzLocal(self): |
| self.assertPicklable(tz.tzlocal()) |
| |
| def testPickleTzFileEST5EDT(self): |
| tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT))) |
| self.assertPicklable(tzc) |
| |
| def testPickleTzFileEurope_Helsinki(self): |
| tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI))) |
| self.assertPicklable(tzc) |
| |
| def testPickleTzFileNew_York(self): |
| tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK))) |
| self.assertPicklable(tzc) |
| |
| @unittest.skip("Known failure") |
| def testPickleTzICal(self): |
| tzc = tz.tzical(StringIO(TZICAL_EST5EDT)).get() |
| self.assertPicklable(tzc) |
| |
| def testPickleTzGettz(self): |
| self.assertPicklable(tz.gettz('America/New_York')) |
| |
| def testPickleZoneFileGettz(self): |
| zoneinfo_file = zoneinfo.get_zonefile_instance() |
| tzi = zoneinfo_file.get('America/New_York') |
| self.assertIsNot(tzi, None) |
| self.assertPicklable(tzi) |
| |
| |
| class TzPickleFileTest(TzPickleTest): |
| """ Run all the TzPickleTest tests, using a temporary file """ |
| _asfile = True |
| |
| |
| class DatetimeAmbiguousTest(unittest.TestCase): |
| """ Test the datetime_exists / datetime_ambiguous functions """ |
| |
| def testNoTzSpecified(self): |
| with self.assertRaises(ValueError): |
| tz.datetime_ambiguous(datetime(2016, 4, 1, 2, 9)) |
| |
| def _get_no_support_tzinfo_class(self, dt_start, dt_end, dst_only=False): |
| # Generates a class of tzinfo with no support for is_ambiguous |
| # where dates between dt_start and dt_end are ambiguous. |
| |
| class FoldingTzInfo(tzinfo): |
| def utcoffset(self, dt): |
| if not dst_only: |
| dt_n = dt.replace(tzinfo=None) |
| |
| if dt_start <= dt_n < dt_end and getattr(dt_n, 'fold', 0): |
| return timedelta(hours=-1) |
| |
| return timedelta(hours=0) |
| |
| def dst(self, dt): |
| dt_n = dt.replace(tzinfo=None) |
| |
| if dt_start <= dt_n < dt_end and getattr(dt_n, 'fold', 0): |
| return timedelta(hours=1) |
| else: |
| return timedelta(0) |
| |
| return FoldingTzInfo |
| |
| def _get_no_support_tzinfo(self, dt_start, dt_end, dst_only=False): |
| return self._get_no_support_tzinfo_class(dt_start, dt_end, dst_only)() |
| |
| def testNoSupportAmbiguityFoldNaive(self): |
| dt_start = datetime(2018, 9, 1, 1, 0) |
| dt_end = datetime(2018, 9, 1, 2, 0) |
| |
| tzi = self._get_no_support_tzinfo(dt_start, dt_end) |
| |
| self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), |
| tz=tzi)) |
| |
| def testNoSupportAmbiguityFoldAware(self): |
| dt_start = datetime(2018, 9, 1, 1, 0) |
| dt_end = datetime(2018, 9, 1, 2, 0) |
| |
| tzi = self._get_no_support_tzinfo(dt_start, dt_end) |
| |
| self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30, |
| tzinfo=tzi))) |
| |
| def testNoSupportAmbiguityUnambiguousNaive(self): |
| dt_start = datetime(2018, 9, 1, 1, 0) |
| dt_end = datetime(2018, 9, 1, 2, 0) |
| |
| tzi = self._get_no_support_tzinfo(dt_start, dt_end) |
| |
| self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), |
| tz=tzi)) |
| |
| def testNoSupportAmbiguityUnambiguousAware(self): |
| dt_start = datetime(2018, 9, 1, 1, 0) |
| dt_end = datetime(2018, 9, 1, 2, 0) |
| |
| tzi = self._get_no_support_tzinfo(dt_start, dt_end) |
| |
| self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30, |
| tzinfo=tzi))) |
| |
| def testNoSupportAmbiguityFoldDSTOnly(self): |
| dt_start = datetime(2018, 9, 1, 1, 0) |
| dt_end = datetime(2018, 9, 1, 2, 0) |
| |
| tzi = self._get_no_support_tzinfo(dt_start, dt_end, dst_only=True) |
| |
| self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), |
| tz=tzi)) |
| |
| def testNoSupportAmbiguityUnambiguousDSTOnly(self): |
| dt_start = datetime(2018, 9, 1, 1, 0) |
| dt_end = datetime(2018, 9, 1, 2, 0) |
| |
| tzi = self._get_no_support_tzinfo(dt_start, dt_end, dst_only=True) |
| |
| self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), |
| tz=tzi)) |
| |
| def testSupportAmbiguityFoldNaive(self): |
| tzi = tz.gettz('US/Eastern') |
| |
| dt = datetime(2011, 11, 6, 1, 30) |
| |
| self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi)) |
| |
| def testSupportAmbiguityFoldAware(self): |
| tzi = tz.gettz('US/Eastern') |
| |
| dt = datetime(2011, 11, 6, 1, 30, tzinfo=tzi) |
| |
| self.assertTrue(tz.datetime_ambiguous(dt)) |
| |
| def testSupportAmbiguityUnambiguousAware(self): |
| tzi = tz.gettz('US/Eastern') |
| |
| dt = datetime(2011, 11, 6, 4, 30) |
| |
| self.assertFalse(tz.datetime_ambiguous(dt, tz=tzi)) |
| |
| def testSupportAmbiguityUnambiguousNaive(self): |
| tzi = tz.gettz('US/Eastern') |
| |
| dt = datetime(2011, 11, 6, 4, 30, tzinfo=tzi) |
| |
| self.assertFalse(tz.datetime_ambiguous(dt)) |
| |
| def _get_ambig_error_tzinfo(self, dt_start, dt_end, dst_only=False): |
| cTzInfo = self._get_no_support_tzinfo_class(dt_start, dt_end, dst_only) |
| |
| # Takes the wrong number of arguments and raises an error anyway. |
| class FoldTzInfoRaises(cTzInfo): |
| def is_ambiguous(self, dt, other_arg): |
| raise NotImplementedError('This is not implemented') |
| |
| return FoldTzInfoRaises() |
| |
| def testIncompatibleAmbiguityFoldNaive(self): |
| dt_start = datetime(2018, 9, 1, 1, 0) |
| dt_end = datetime(2018, 9, 1, 2, 0) |
| |
| tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) |
| |
| self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), |
| tz=tzi)) |
| |
| def testIncompatibleAmbiguityFoldAware(self): |
| dt_start = datetime(2018, 9, 1, 1, 0) |
| dt_end = datetime(2018, 9, 1, 2, 0) |
| |
| tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) |
| |
| self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30, |
| tzinfo=tzi))) |
| |
| def testIncompatibleAmbiguityUnambiguousNaive(self): |
| dt_start = datetime(2018, 9, 1, 1, 0) |
| dt_end = datetime(2018, 9, 1, 2, 0) |
| |
| tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) |
| |
| self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), |
| tz=tzi)) |
| |
| def testIncompatibleAmbiguityUnambiguousAware(self): |
| dt_start = datetime(2018, 9, 1, 1, 0) |
| dt_end = datetime(2018, 9, 1, 2, 0) |
| |
| tzi = self._get_ambig_error_tzinfo(dt_start, dt_end) |
| |
| self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30, |
| tzinfo=tzi))) |
| |
| def testIncompatibleAmbiguityFoldDSTOnly(self): |
| dt_start = datetime(2018, 9, 1, 1, 0) |
| dt_end = datetime(2018, 9, 1, 2, 0) |
| |
| tzi = self._get_ambig_error_tzinfo(dt_start, dt_end, dst_only=True) |
| |
| self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30), |
| tz=tzi)) |
| |
| def testIncompatibleAmbiguityUnambiguousDSTOnly(self): |
| dt_start = datetime(2018, 9, 1, 1, 0) |
| dt_end = datetime(2018, 9, 1, 2, 0) |
| |
| tzi = self._get_ambig_error_tzinfo(dt_start, dt_end, dst_only=True) |
| |
| self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30), |
| tz=tzi)) |
| |
| def testSpecifiedTzOverridesAttached(self): |
| # If a tz is specified, the datetime will be treated as naive. |
| |
| # This is not ambiguous in the local zone |
| dt = datetime(2011, 11, 6, 1, 30, tzinfo=tz.gettz('Australia/Sydney')) |
| |
| self.assertFalse(tz.datetime_ambiguous(dt)) |
| |
| tzi = tz.gettz('US/Eastern') |
| self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi)) |
| |
| |
| class DatetimeExistsTest(unittest.TestCase): |
| def testNoTzSpecified(self): |
| with self.assertRaises(ValueError): |
| tz.datetime_exists(datetime(2016, 4, 1, 2, 9)) |
| |
| def testInGapNaive(self): |
| tzi = tz.gettz('Australia/Sydney') |
| |
| dt = datetime(2012, 10, 7, 2, 30) |
| |
| self.assertFalse(tz.datetime_exists(dt, tz=tzi)) |
| |
| def testInGapAware(self): |
| tzi = tz.gettz('Australia/Sydney') |
| |
| dt = datetime(2012, 10, 7, 2, 30, tzinfo=tzi) |
| |
| self.assertFalse(tz.datetime_exists(dt)) |
| |
| def testExistsNaive(self): |
| tzi = tz.gettz('Australia/Sydney') |
| |
| dt = datetime(2012, 10, 7, 10, 30) |
| |
| self.assertTrue(tz.datetime_exists(dt, tz=tzi)) |
| |
| def testExistsAware(self): |
| tzi = tz.gettz('Australia/Sydney') |
| |
| dt = datetime(2012, 10, 7, 10, 30, tzinfo=tzi) |
| |
| self.assertTrue(tz.datetime_exists(dt)) |
| |
| def testSpecifiedTzOverridesAttached(self): |
| EST = tz.gettz('US/Eastern') |
| AEST = tz.gettz('Australia/Sydney') |
| |
| dt = datetime(2012, 10, 7, 2, 30, tzinfo=EST) # This time exists |
| |
| self.assertFalse(tz.datetime_exists(dt, tz=AEST)) |
| |
| |
| class TestEnfold: |
| def test_enter_fold_default(self): |
| dt = tz.enfold(datetime(2020, 1, 19, 3, 32)) |
| |
| assert dt.fold == 1 |
| |
| def test_enter_fold(self): |
| dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1) |
| |
| assert dt.fold == 1 |
| |
| def test_exit_fold(self): |
| dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=0) |
| |
| # Before Python 3.6, dt.fold won't exist if fold is 0. |
| assert getattr(dt, 'fold', 0) == 0 |
| |
| def test_defold(self): |
| dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1) |
| |
| dt2 = tz.enfold(dt, fold=0) |
| |
| assert getattr(dt2, 'fold', 0) == 0 |
| |
| def test_fold_replace_args(self): |
| # This test can be dropped when Python < 3.6 is dropped, since it |
| # is mainly to cover the `replace` method on _DatetimeWithFold |
| dt = tz.enfold(datetime(1950, 1, 2, 12, 30, 15, 8), fold=1) |
| |
| dt2 = dt.replace(1952, 2, 3, 13, 31, 16, 9) |
| assert dt2 == tz.enfold(datetime(1952, 2, 3, 13, 31, 16, 9), fold=1) |
| assert dt2.fold == 1 |
| |
| def test_fold_replace_exception_duplicate_args(self): |
| dt = tz.enfold(datetime(1999, 1, 3), fold=1) |
| |
| with pytest.raises(TypeError): |
| dt.replace(1950, year=2000) |
| |
| |
| @pytest.mark.tz_resolve_imaginary |
| class ImaginaryDateTest(unittest.TestCase): |
| def testCanberraForward(self): |
| tzi = tz.gettz('Australia/Canberra') |
| dt = datetime(2018, 10, 7, 2, 30, tzinfo=tzi) |
| dt_act = tz.resolve_imaginary(dt) |
| dt_exp = datetime(2018, 10, 7, 3, 30, tzinfo=tzi) |
| self.assertEqual(dt_act, dt_exp) |
| |
| def testLondonForward(self): |
| tzi = tz.gettz('Europe/London') |
| dt = datetime(2018, 3, 25, 1, 30, tzinfo=tzi) |
| dt_act = tz.resolve_imaginary(dt) |
| dt_exp = datetime(2018, 3, 25, 2, 30, tzinfo=tzi) |
| self.assertEqual(dt_act, dt_exp) |
| |
| def testKeivForward(self): |
| tzi = tz.gettz('Europe/Kiev') |
| dt = datetime(2018, 3, 25, 3, 30, tzinfo=tzi) |
| dt_act = tz.resolve_imaginary(dt) |
| dt_exp = datetime(2018, 3, 25, 4, 30, tzinfo=tzi) |
| self.assertEqual(dt_act, dt_exp) |
| |
| |
| @pytest.mark.tz_resolve_imaginary |
| @pytest.mark.parametrize('dt', [ |
| datetime(2017, 11, 5, 1, 30, tzinfo=tz.gettz('America/New_York')), |
| datetime(2018, 10, 28, 1, 30, tzinfo=tz.gettz('Europe/London')), |
| datetime(2017, 4, 2, 2, 30, tzinfo=tz.gettz('Australia/Sydney')), |
| ]) |
| def test_resolve_imaginary_ambiguous(dt): |
| assert tz.resolve_imaginary(dt) is dt |
| |
| dt_f = tz.enfold(dt) |
| assert dt is not dt_f |
| assert tz.resolve_imaginary(dt_f) is dt_f |
| |
| |
| @pytest.mark.tz_resolve_imaginary |
| @pytest.mark.parametrize('dt', [ |
| datetime(2017, 6, 2, 12, 30, tzinfo=tz.gettz('America/New_York')), |
| datetime(2018, 4, 2, 9, 30, tzinfo=tz.gettz('Europe/London')), |
| datetime(2017, 2, 2, 16, 30, tzinfo=tz.gettz('Australia/Sydney')), |
| datetime(2017, 12, 2, 12, 30, tzinfo=tz.gettz('America/New_York')), |
| datetime(2018, 12, 2, 9, 30, tzinfo=tz.gettz('Europe/London')), |
| datetime(2017, 6, 2, 16, 30, tzinfo=tz.gettz('Australia/Sydney')), |
| datetime(2025, 9, 25, 1, 17, tzinfo=tz.tzutc()), |
| datetime(2025, 9, 25, 1, 17, tzinfo=tz.tzoffset('EST', -18000)), |
| datetime(2019, 3, 4, tzinfo=None) |
| ]) |
| def test_resolve_imaginary_existing(dt): |
| assert tz.resolve_imaginary(dt) is dt |
| |
| |
| def __get_kiritimati_resolve_imaginary_test(): |
| # In the 2018d release of the IANA database, the Kiritimati "imaginary day" |
| # data was corrected, so if the system zoneinfo is older than 2018d, the |
| # Kiritimati test will fail. |
| |
| tzi = tz.gettz('Pacific/Kiritimati') |
| new_version = False |
| if not tz.datetime_exists(datetime(1995, 1, 1, 12, 30), tzi): |
| zif = zoneinfo.get_zonefile_instance() |
| if zif.metadata is not None: |
| new_version = zif.metadata['tzversion'] >= '2018d' |
| |
| if new_version: |
| tzi = zif.get('Pacific/Kiritimati') |
| else: |
| new_version = True |
| |
| if new_version: |
| dates = (datetime(1994, 12, 31, 12, 30), datetime(1995, 1, 1, 12, 30)) |
| else: |
| dates = (datetime(1995, 1, 1, 12, 30), datetime(1995, 1, 2, 12, 30)) |
| |
| return (tzi, ) + dates |
| |
| |
| resolve_imaginary_tests = [ |
| (tz.gettz('Europe/London'), |
| datetime(2018, 3, 25, 1, 30), datetime(2018, 3, 25, 2, 30)), |
| (tz.gettz('America/New_York'), |
| datetime(2017, 3, 12, 2, 30), datetime(2017, 3, 12, 3, 30)), |
| (tz.gettz('Australia/Sydney'), |
| datetime(2014, 10, 5, 2, 0), datetime(2014, 10, 5, 3, 0)), |
| __get_kiritimati_resolve_imaginary_test(), |
| ] |
| |
| |
| if SUPPORTS_SUB_MINUTE_OFFSETS: |
| resolve_imaginary_tests.append( |
| (tz.gettz('Africa/Monrovia'), |
| datetime(1972, 1, 7, 0, 30), datetime(1972, 1, 7, 1, 14, 30))) |
| |
| |
| @pytest.mark.tz_resolve_imaginary |
| @pytest.mark.parametrize('tzi, dt, dt_exp', resolve_imaginary_tests) |
| def test_resolve_imaginary(tzi, dt, dt_exp): |
| dt = dt.replace(tzinfo=tzi) |
| dt_exp = dt_exp.replace(tzinfo=tzi) |
| |
| dt_r = tz.resolve_imaginary(dt) |
| assert dt_r == dt_exp |
| assert dt_r.tzname() == dt_exp.tzname() |
| assert dt_r.utcoffset() == dt_exp.utcoffset() |