# -*- coding: ascii -*-

import doctest
import sys
import os
import os.path
import unittest
try:
    import cPickle as pickle
except ImportError:
    import pickle
from datetime import (
    datetime,
    timedelta
)
import warnings

if __name__ == '__main__':
    # Only munge path if invoked as a script. Testrunners should have setup
    # the paths already
    sys.path.insert(0, os.path.abspath(os.path.join(os.pardir, os.pardir)))

import pytz  # noqa
from pytz import reference  # noqa
from pytz.tzfile import _byte_string  # noqa
from pytz.tzinfo import DstTzInfo, StaticTzInfo  # noqa

# I test for expected version to ensure the correct version of pytz is
# actually being tested.
EXPECTED_VERSION = '2023.3'
EXPECTED_OLSON_VERSION = '2023c'

fmt = '%Y-%m-%d %H:%M:%S %Z%z'

NOTIME = timedelta(0)

# GMT is a tzinfo.StaticTzInfo--the class we primarily want to test--while
# UTC is reference implementation.  They both have the same timezone meaning.
UTC = pytz.timezone('UTC')
GMT = pytz.timezone('GMT')
assert isinstance(GMT, StaticTzInfo), 'GMT is no longer a StaticTzInfo'


def prettydt(dt):
    """datetime as a string using a known format.

    We don't use strftime as it doesn't handle years earlier than 1900
    per http://bugs.python.org/issue1777412
    """
    if dt.utcoffset() >= timedelta(0):
        offset = '+%s' % (dt.utcoffset(),)
    else:
        offset = '-%s' % (-1 * dt.utcoffset(),)
    return '%04d-%02d-%02d %02d:%02d:%02d %s %s' % (
        dt.year, dt.month, dt.day,
        dt.hour, dt.minute, dt.second,
        dt.tzname(), offset)


if sys.version_info[0] > 2:
    # Python 3.x doesn't have unicode(), making writing code
    # for Python 2.3 and Python 3.x a pain.
    unicode = str


class BasicTest(unittest.TestCase):

    def testVersion(self):
        # Ensuring the correct version of pytz has been loaded
        self.assertEqual(
            EXPECTED_VERSION, pytz.__version__,
            'Incorrect pytz version loaded. Import path is stuffed '
            'or this test needs updating. (Wanted %s, got %s)'
            % (EXPECTED_VERSION, pytz.__version__)
        )

        self.assertEqual(
            EXPECTED_OLSON_VERSION, pytz.OLSON_VERSION,
            'Incorrect pytz version loaded. Import path is stuffed '
            'or this test needs updating. (Wanted %s, got %s)'
            % (EXPECTED_OLSON_VERSION, pytz.OLSON_VERSION)
        )

    def testGMT(self):
        now = datetime.now(tz=GMT)
        self.assertEqual(now.utcoffset(), NOTIME)
        self.assertEqual(now.dst(), NOTIME)
        self.assertEqual(now.timetuple(), now.utctimetuple())
        self.assertEqual(now, now.replace(tzinfo=UTC))

    def testReferenceUTC(self):
        now = datetime.now(tz=UTC)
        self.assertEqual(now.utcoffset(), NOTIME)
        self.assertEqual(now.dst(), NOTIME)
        self.assertEqual(now.timetuple(), now.utctimetuple())

    def testUnknownOffsets(self):
        # This tzinfo behavior is required to make
        # datetime.time.{utcoffset, dst, tzname} work as documented.

        dst_tz = pytz.timezone('US/Eastern')

        # This information is not known when we don't have a date,
        # so return None per API.
        self.assertIsNone(dst_tz.utcoffset(None))
        self.assertIsNone(dst_tz.dst(None))
        # We don't know the abbreviation, but this is still a valid
        # tzname per the Python documentation.
        self.assertEqual(dst_tz.tzname(None), 'US/Eastern')

    def clearCache(self):
        pytz._tzinfo_cache.clear()

    def testUnicodeTimezone(self):
        # We need to ensure that cold lookups work for both Unicode
        # and traditional strings, and that the desired singleton is
        # returned.
        self.clearCache()
        eastern = pytz.timezone(unicode('US/Eastern'))
        self.assertIs(eastern, pytz.timezone('US/Eastern'))

        self.clearCache()
        eastern = pytz.timezone('US/Eastern')
        self.assertIs(eastern, pytz.timezone(unicode('US/Eastern')))

    def testStaticTzInfo(self):
        # Ensure that static timezones are correctly detected,
        # per lp:1602807
        static = pytz.timezone('Etc/GMT-4')
        self.assertIsInstance(static, StaticTzInfo)


class PicklingTest(unittest.TestCase):

    def _roundtrip_tzinfo(self, tz):
        p = pickle.dumps(tz)
        unpickled_tz = pickle.loads(p)
        self.assertIs(tz, unpickled_tz, '%s did not roundtrip' % tz.zone)

    def _roundtrip_datetime(self, dt):
        # Ensure that the tzinfo attached to a datetime instance
        # is identical to the one returned. This is important for
        # DST timezones, as some state is stored in the tzinfo.
        tz = dt.tzinfo
        p = pickle.dumps(dt)
        unpickled_dt = pickle.loads(p)
        unpickled_tz = unpickled_dt.tzinfo
        self.assertIs(tz, unpickled_tz, '%s did not roundtrip' % tz.zone)

    def testDst(self):
        tz = pytz.timezone('Europe/Amsterdam')
        dt = datetime(2004, 2, 1, 0, 0, 0)

        for localized_tz in tz._tzinfos.values():
            self._roundtrip_tzinfo(localized_tz)
            self._roundtrip_datetime(dt.replace(tzinfo=localized_tz))

    def testRoundtrip(self):
        for zone in pytz.all_timezones:
            tz = pytz.timezone(zone)
            self._roundtrip_tzinfo(tz)

    def testDatabaseFixes(self):
        # Hack the pickle to make it refer to a timezone abbreviation
        # that does not match anything. The unpickler should be able
        # to repair this case
        tz = pytz.timezone('Australia/Melbourne')
        p = pickle.dumps(tz)
        tzname = tz._tzname
        hacked_p = p.replace(
            _byte_string(tzname),
            _byte_string('?' * len(tzname))
        )
        self.assertNotEqual(p, hacked_p)
        unpickled_tz = pickle.loads(hacked_p)
        self.assertIs(tz, unpickled_tz)

        # Simulate a database correction. In this case, the incorrect
        # data will continue to be used.
        p = pickle.dumps(tz)
        new_utcoffset = tz._utcoffset.seconds + 42

        # Python 3 introduced a new pickle protocol where numbers are stored in
        # hexadecimal representation. Here we extract the pickle
        # representation of the number for the current Python version.
        #
        # Test protocol 3 on Python 3 and protocol 0 on Python 2.
        if sys.version_info >= (3,):
            protocol = 3
        else:
            protocol = 0
        old_pickle_pattern = pickle.dumps(tz._utcoffset.seconds, protocol)[3:-1]
        new_pickle_pattern = pickle.dumps(new_utcoffset, protocol)[3:-1]
        hacked_p = p.replace(old_pickle_pattern, new_pickle_pattern)

        self.assertNotEqual(p, hacked_p)
        unpickled_tz = pickle.loads(hacked_p)
        self.assertEqual(unpickled_tz._utcoffset.seconds, new_utcoffset)
        self.assertIsNot(tz, unpickled_tz)

    def testOldPickles(self):
        # Ensure that applications serializing pytz instances as pickles
        # have no troubles upgrading to a new pytz release. These pickles
        # where created with pytz2006j
        east1 = pickle.loads(
            _byte_string(
                "cpytz\n_p\np1\n(S'US/Eastern'\np2\nI-18000\n"
                "I0\nS'EST'\np3\ntRp4\n."
            )
        )
        east2 = pytz.timezone('US/Eastern').localize(
            datetime(2006, 1, 1)).tzinfo
        self.assertIs(east1, east2)

        # Confirm changes in name munging between 2006j and 2007c cause
        # no problems.
        pap1 = pickle.loads(_byte_string(
            "cpytz\n_p\np1\n(S'America/Port_minus_au_minus_Prince'"
            "\np2\nI-17340\nI0\nS'PPMT'\np3\ntRp4\n."))
        pap2 = pytz.timezone('America/Port-au-Prince').localize(
            datetime(1910, 1, 1)).tzinfo
        self.assertIs(pap1, pap2)

        gmt1 = pickle.loads(_byte_string(
            "cpytz\n_p\np1\n(S'Etc/GMT_plus_10'\np2\ntRp3\n."))
        gmt2 = pytz.timezone('Etc/GMT+10')
        self.assertIs(gmt1, gmt2)


class USEasternDSTStartTestCase(unittest.TestCase):
    tzinfo = pytz.timezone('US/Eastern')

    # 24 hours before DST changeover
    transition_time = datetime(2002, 4, 7, 7, 0, 0, tzinfo=UTC)

    # Increase for 'flexible' DST transitions due to 1 minute granularity
    # of Python's datetime library
    instant = timedelta(seconds=1)

    # before transition
    before = {
        'tzname': 'EST',
        'utcoffset': timedelta(hours=-5),
        'dst': timedelta(hours=0),
    }

    # after transition
    after = {
        'tzname': 'EDT',
        'utcoffset': timedelta(hours=-4),
        'dst': timedelta(hours=1),
    }

    def _test_tzname(self, utc_dt, wanted):
        tzname = wanted['tzname']
        dt = utc_dt.astimezone(self.tzinfo)
        self.assertEqual(
            dt.tzname(), tzname,
            'Expected %s as tzname for %s. Got %s' % (
                tzname, str(utc_dt), dt.tzname()
            )
        )

    def _test_utcoffset(self, utc_dt, wanted):
        utcoffset = wanted['utcoffset']
        dt = utc_dt.astimezone(self.tzinfo)
        self.assertEqual(
            dt.utcoffset(), wanted['utcoffset'],
            'Expected %s as utcoffset for %s. Got %s' % (
                utcoffset, utc_dt, dt.utcoffset()
            )
        )

    def _test_dst(self, utc_dt, wanted):
        dst = wanted['dst']
        dt = utc_dt.astimezone(self.tzinfo)
        self.assertEqual(
            dt.dst(), dst,
            'Expected %s as dst for %s. Got %s' % (dst, utc_dt, dt.dst())
        )

    def test_arithmetic(self):
        utc_dt = self.transition_time

        for days in range(-420, 720, 20):
            delta = timedelta(days=days)

            # Make sure we can get back where we started
            dt = utc_dt.astimezone(self.tzinfo)
            dt2 = dt + delta
            dt2 = dt2 - delta
            self.assertEqual(dt, dt2)

            # Make sure arithmetic crossing DST boundaries ends
            # up in the correct timezone after normalization
            utc_plus_delta = (utc_dt + delta).astimezone(self.tzinfo)
            local_plus_delta = self.tzinfo.normalize(dt + delta)
            self.assertEqual(
                prettydt(utc_plus_delta), prettydt(local_plus_delta),
                'Incorrect result for delta==%d days.  Wanted %r. Got %r' % (
                    days, prettydt(utc_plus_delta), prettydt(local_plus_delta),
                )
            )

    def _test_all(self, utc_dt, wanted):
        self._test_utcoffset(utc_dt, wanted)
        self._test_tzname(utc_dt, wanted)
        self._test_dst(utc_dt, wanted)

    def testDayBefore(self):
        self._test_all(
            self.transition_time - timedelta(days=1), self.before
        )

    def testTwoHoursBefore(self):
        self._test_all(
            self.transition_time - timedelta(hours=2), self.before
        )

    def testHourBefore(self):
        self._test_all(
            self.transition_time - timedelta(hours=1), self.before
        )

    def testInstantBefore(self):
        self._test_all(
            self.transition_time - self.instant, self.before
        )

    def testTransition(self):
        self._test_all(
            self.transition_time, self.after
        )

    def testInstantAfter(self):
        self._test_all(
            self.transition_time + self.instant, self.after
        )

    def testHourAfter(self):
        self._test_all(
            self.transition_time + timedelta(hours=1), self.after
        )

    def testTwoHoursAfter(self):
        self._test_all(
            self.transition_time + timedelta(hours=1), self.after
        )

    def testDayAfter(self):
        self._test_all(
            self.transition_time + timedelta(days=1), self.after
        )


class USEasternDSTEndTestCase(USEasternDSTStartTestCase):
    tzinfo = pytz.timezone('US/Eastern')
    transition_time = datetime(2002, 10, 27, 6, 0, 0, tzinfo=UTC)
    before = {
        'tzname': 'EDT',
        'utcoffset': timedelta(hours=-4),
        'dst': timedelta(hours=1),
    }
    after = {
        'tzname': 'EST',
        'utcoffset': timedelta(hours=-5),
        'dst': timedelta(hours=0),
    }


class USEasternEPTStartTestCase(USEasternDSTStartTestCase):
    transition_time = datetime(1945, 8, 14, 23, 0, 0, tzinfo=UTC)
    before = {
        'tzname': 'EWT',
        'utcoffset': timedelta(hours=-4),
        'dst': timedelta(hours=1),
    }
    after = {
        'tzname': 'EPT',
        'utcoffset': timedelta(hours=-4),
        'dst': timedelta(hours=1),
    }


class USEasternEPTEndTestCase(USEasternDSTStartTestCase):
    transition_time = datetime(1945, 9, 30, 6, 0, 0, tzinfo=UTC)
    before = {
        'tzname': 'EPT',
        'utcoffset': timedelta(hours=-4),
        'dst': timedelta(hours=1),
    }
    after = {
        'tzname': 'EST',
        'utcoffset': timedelta(hours=-5),
        'dst': timedelta(hours=0),
    }


class WarsawWMTEndTestCase(USEasternDSTStartTestCase):
    # In 1915, Warsaw changed from Warsaw to Central European time.
    # This involved the clocks being set backwards, causing a end-of-DST
    # like situation without DST being involved.
    tzinfo = pytz.timezone('Europe/Warsaw')
    transition_time = datetime(1915, 8, 4, 22, 36, 0, tzinfo=UTC)
    before = {
        'tzname': 'WMT',
        'utcoffset': timedelta(hours=1, minutes=24),
        'dst': timedelta(0),
    }
    after = {
        'tzname': 'CET',
        'utcoffset': timedelta(hours=1),
        'dst': timedelta(0),
    }


class VilniusWMTEndTestCase(USEasternDSTStartTestCase):
    # At the end of 1916, Vilnius changed timezones putting its clock
    # forward by 11 minutes 35 seconds. Neither timezone was in DST mode.
    tzinfo = pytz.timezone('Europe/Vilnius')
    instant = timedelta(seconds=31)
    transition_time = datetime(1916, 12, 31, 22, 36, 00, tzinfo=UTC)
    before = {
        'tzname': 'WMT',
        'utcoffset': timedelta(hours=1, minutes=24),
        'dst': timedelta(0),
    }
    after = {
        'tzname': 'KMT',
        'utcoffset': timedelta(hours=1, minutes=36),  # Really 1:35:36
        'dst': timedelta(0),
    }


class VilniusCESTStartTestCase(USEasternDSTStartTestCase):
    # In 1941, Vilnius changed from MSG to CEST, switching to summer
    # time while simultaneously reducing its UTC offset by two hours,
    # causing the clocks to go backwards for this summer time
    # switchover.
    tzinfo = pytz.timezone('Europe/Vilnius')
    transition_time = datetime(1941, 6, 23, 21, 00, 00, tzinfo=UTC)
    before = {
        'tzname': 'MSK',
        'utcoffset': timedelta(hours=3),
        'dst': timedelta(0),
    }
    after = {
        'tzname': 'CEST',
        'utcoffset': timedelta(hours=2),
        'dst': timedelta(hours=1),
    }


class LondonHistoryStartTestCase(USEasternDSTStartTestCase):
    # The first known timezone transition in London was in 1847 when
    # clocks where synchronized to GMT. However, we currently only
    # understand v1 format tzfile(5) files which does handle years
    # this far in the past, so our earliest known transition is in
    # 1916.
    tzinfo = pytz.timezone('Europe/London')
    # transition_time = datetime(1847, 12, 1, 1, 15, 00, tzinfo=UTC)
    # before = {
    #     'tzname': 'LMT',
    #     'utcoffset': timedelta(minutes=-75),
    #     'dst': timedelta(0),
    #     }
    # after = {
    #     'tzname': 'GMT',
    #     'utcoffset': timedelta(0),
    #     'dst': timedelta(0),
    #     }
    transition_time = datetime(1916, 5, 21, 2, 00, 00, tzinfo=UTC)
    before = {
        'tzname': 'GMT',
        'utcoffset': timedelta(0),
        'dst': timedelta(0),
    }
    after = {
        'tzname': 'BST',
        'utcoffset': timedelta(hours=1),
        'dst': timedelta(hours=1),
    }


class LondonHistoryEndTestCase(USEasternDSTStartTestCase):
    # Timezone switchovers are projected into the future, even
    # though no official statements exist or could be believed even
    # if they did exist. We currently only check the last known
    # transition in 2037, as we are still using v1 format tzfile(5)
    # files.
    tzinfo = pytz.timezone('Europe/London')
    # transition_time = datetime(2499, 10, 25, 1, 0, 0, tzinfo=UTC)
    transition_time = datetime(2037, 10, 25, 1, 0, 0, tzinfo=UTC)
    before = {
        'tzname': 'BST',
        'utcoffset': timedelta(hours=1),
        'dst': timedelta(hours=1),
    }
    after = {
        'tzname': 'GMT',
        'utcoffset': timedelta(0),
        'dst': timedelta(0),
    }


class NoumeaHistoryStartTestCase(USEasternDSTStartTestCase):
    # Noumea adopted a whole hour offset in 1912. Previously
    # it was 11 hours, 5 minutes and 48 seconds off UTC. However,
    # due to limitations of the Python datetime library, we need
    # to round that to 11 hours 6 minutes.
    tzinfo = pytz.timezone('Pacific/Noumea')
    transition_time = datetime(1912, 1, 12, 12, 54, 12, tzinfo=UTC)
    before = {
        'tzname': 'LMT',
        'utcoffset': timedelta(hours=11, minutes=6),
        'dst': timedelta(0),
    }
    after = {
        'tzname': '+11',  # pre-2017a, NCT
        'utcoffset': timedelta(hours=11),
        'dst': timedelta(0),
    }


class NoumeaDSTEndTestCase(USEasternDSTStartTestCase):
    # Noumea dropped DST in 1997.
    tzinfo = pytz.timezone('Pacific/Noumea')
    transition_time = datetime(1997, 3, 1, 15, 00, 00, tzinfo=UTC)
    before = {
        'tzname': '+12',  # pre-2017a, NCST
        'utcoffset': timedelta(hours=12),
        'dst': timedelta(hours=1),
    }
    after = {
        'tzname': '+11',  # pre-2017a, NCT
        'utcoffset': timedelta(hours=11),
        'dst': timedelta(0),
    }


class NoumeaNoMoreDSTTestCase(NoumeaDSTEndTestCase):
    # Noumea dropped DST in 1997. Here we test that it stops occurring.
    transition_time = (
        NoumeaDSTEndTestCase.transition_time + timedelta(days=365 * 10))
    before = NoumeaDSTEndTestCase.after
    after = NoumeaDSTEndTestCase.after


class TahitiTestCase(USEasternDSTStartTestCase):
    # Tahiti has had a single transition in its history.
    tzinfo = pytz.timezone('Pacific/Tahiti')
    transition_time = datetime(1912, 10, 1, 9, 58, 16, tzinfo=UTC)
    before = {
        'tzname': 'LMT',
        'utcoffset': timedelta(hours=-9, minutes=-58),
        'dst': timedelta(0),
    }
    after = {
        'tzname': '-10',  # pre-2017a, TAHT
        'utcoffset': timedelta(hours=-10),
        'dst': timedelta(0),
    }


class SamoaInternationalDateLineChange(USEasternDSTStartTestCase):
    # At the end of 2011, Samoa will switch from being east of the
    # international dateline to the west. There will be no Dec 30th
    # 2011 and it will switch from UTC-10 to UTC+14.
    tzinfo = pytz.timezone('Pacific/Apia')
    transition_time = datetime(2011, 12, 30, 10, 0, 0, tzinfo=UTC)
    before = {
        'tzname': '-10',  # pre-2017a, SDT
        'utcoffset': timedelta(hours=-10),
        'dst': timedelta(hours=1),
    }
    after = {
        'tzname': '+14',  # pre-2017a, WSDT
        'utcoffset': timedelta(hours=14),
        'dst': timedelta(hours=1),
    }


class ReferenceUSEasternDSTStartTestCase(USEasternDSTStartTestCase):
    tzinfo = reference.Eastern

    def test_arithmetic(self):
        # Reference implementation cannot handle this
        pass


class ReferenceUSEasternDSTEndTestCase(USEasternDSTEndTestCase):
    tzinfo = reference.Eastern

    def testHourBefore(self):
        # Python's datetime library has a bug, where the hour before
        # a daylight saving transition is one hour out. For example,
        # at the end of US/Eastern daylight saving time, 01:00 EST
        # occurs twice (once at 05:00 UTC and once at 06:00 UTC),
        # whereas the first should actually be 01:00 EDT.
        # Note that this bug is by design - by accepting this ambiguity
        # for one hour one hour per year, an is_dst flag on datetime.time
        # became unnecessary.
        self._test_all(self.transition_time - timedelta(hours=1), self.after)

    def testInstantBefore(self):
        self._test_all(self.transition_time - timedelta(seconds=1), self.after)

    def test_arithmetic(self):
        # Reference implementation cannot handle this
        pass


class LocalTestCase(unittest.TestCase):
    def testLocalize(self):
        loc_tz = pytz.timezone('US/Eastern')

        # End of DST ambiguity check
        loc_time = loc_tz.localize(datetime(1918, 10, 27, 1, 59, 59), is_dst=1)
        self.assertEqual(loc_time.strftime('%Z%z'), 'EDT-0400')

        loc_time = loc_tz.localize(datetime(1918, 10, 27, 1, 59, 59), is_dst=0)
        self.assertEqual(loc_time.strftime('%Z%z'), 'EST-0500')

        self.assertRaises(
            pytz.AmbiguousTimeError,
            loc_tz.localize, datetime(1918, 10, 27, 1, 59, 59), is_dst=None
        )

        # Start of DST non-existent times
        loc_time = loc_tz.localize(datetime(1918, 3, 31, 2, 0, 0), is_dst=0)
        self.assertEqual(loc_time.strftime('%Z%z'), 'EST-0500')

        loc_time = loc_tz.localize(datetime(1918, 3, 31, 2, 0, 0), is_dst=1)
        self.assertEqual(loc_time.strftime('%Z%z'), 'EDT-0400')

        self.assertRaises(
            pytz.NonExistentTimeError,
            loc_tz.localize, datetime(1918, 3, 31, 2, 0, 0), is_dst=None
        )

        # Weird changes - war time and peace time both is_dst==True

        loc_time = loc_tz.localize(datetime(1942, 2, 9, 3, 0, 0))
        self.assertEqual(loc_time.strftime('%Z%z'), 'EWT-0400')

        loc_time = loc_tz.localize(datetime(1945, 8, 14, 19, 0, 0))
        self.assertEqual(loc_time.strftime('%Z%z'), 'EPT-0400')

        loc_time = loc_tz.localize(datetime(1945, 9, 30, 1, 0, 0), is_dst=1)
        self.assertEqual(loc_time.strftime('%Z%z'), 'EPT-0400')

        loc_time = loc_tz.localize(datetime(1945, 9, 30, 1, 0, 0), is_dst=0)
        self.assertEqual(loc_time.strftime('%Z%z'), 'EST-0500')

        # Weird changes - ambiguous time (end-of-DST like) but is_dst==False
        for zonename, ambiguous_naive, expected in [
                ('Europe/Warsaw', datetime(1915, 8, 4, 23, 59, 59),
                 ['1915-08-04 23:59:59 WMT+0124',
                  '1915-08-04 23:59:59 CET+0100']),
                ('Europe/Moscow', datetime(2014, 10, 26, 1, 30),
                 ['2014-10-26 01:30:00 MSK+0400',
                  '2014-10-26 01:30:00 MSK+0300'])]:
            loc_tz = pytz.timezone(zonename)
            self.assertRaises(
                pytz.AmbiguousTimeError,
                loc_tz.localize, ambiguous_naive, is_dst=None
            )
            # Also test non-boolean is_dst in the weird case
            for dst in [True, timedelta(1), False, timedelta(0)]:
                loc_time = loc_tz.localize(ambiguous_naive, is_dst=dst)
                self.assertEqual(loc_time.strftime(fmt), expected[not dst])

    def testNormalize(self):
        tz = pytz.timezone('US/Eastern')
        dt = datetime(2004, 4, 4, 7, 0, 0, tzinfo=UTC).astimezone(tz)
        dt2 = dt - timedelta(minutes=10)
        self.assertEqual(
            dt2.strftime('%Y-%m-%d %H:%M:%S %Z%z'),
            '2004-04-04 02:50:00 EDT-0400'
        )

        dt2 = tz.normalize(dt2)
        self.assertEqual(
            dt2.strftime('%Y-%m-%d %H:%M:%S %Z%z'),
            '2004-04-04 01:50:00 EST-0500'
        )

    def no_testCreateLocaltime(self):
        # It would be nice if this worked, but it doesn't.
        tz = pytz.timezone('Europe/Amsterdam')
        dt = datetime(2004, 10, 31, 2, 0, 0, tzinfo=tz)
        self.assertEqual(
            dt.strftime(fmt),
            '2004-10-31 02:00:00 CET+0100'
        )


class CommonTimezonesTestCase(unittest.TestCase):
    def test_bratislava(self):
        # Bratislava is the default timezone for Slovakia, but our
        # heuristics where not adding it to common_timezones. Ideally,
        # common_timezones should be populated from zone.tab at runtime,
        # but I'm hesitant to pay the startup cost as loading the list
        # on demand whilst remaining backwards compatible seems
        # difficult.
        self.assertIn('Europe/Bratislava', pytz.common_timezones)
        self.assertIn('Europe/Bratislava', pytz.common_timezones_set)

    def test_us_eastern(self):
        self.assertIn('US/Eastern', pytz.common_timezones)
        self.assertIn('US/Eastern', pytz.common_timezones_set)

    def test_belfast(self):
        self.assertIn('Europe/Belfast', pytz.all_timezones_set)
        self.assertNotIn('Europe/Belfast', pytz.common_timezones)
        self.assertNotIn('Europe/Belfast', pytz.common_timezones_set)


class ZoneCaseInsensitivityTestCase(unittest.TestCase):
    def test_lower_case_timezone_constructor_arg(self):
        for tz in pytz.all_timezones_set:
            from_lower = pytz.timezone(tz.lower())
            from_passed = pytz.timezone(tz)
            self.assertEqual(from_lower,
                             from_passed,
                             "arg '%s' and arg '%s' produce different "
                             "timezone objects" % (
                                 from_lower, from_passed))


class BaseTzInfoTestCase:
    '''Ensure UTC, StaticTzInfo and DstTzInfo work consistently.

    These tests are run for each type of tzinfo.
    '''
    tz = None  # override
    tz_class = None  # override

    def test_expectedclass(self):
        self.assertIsInstance(self.tz, self.tz_class)

    def test_fromutc(self):
        # naive datetime.
        dt1 = datetime(2011, 10, 31)

        # localized datetime, same timezone.
        dt2 = self.tz.localize(dt1)

        # Both should give the same results. Note that the standard
        # Python tzinfo.fromutc() only supports the second.
        for dt in [dt1, dt2]:
            loc_dt = self.tz.fromutc(dt)
            loc_dt2 = pytz.utc.localize(dt1).astimezone(self.tz)
            self.assertEqual(loc_dt, loc_dt2)

        # localized datetime, different timezone.
        new_tz = pytz.timezone('Europe/Paris')
        self.assertIsNot(self.tz, new_tz)
        dt3 = new_tz.localize(dt1)
        self.assertRaises(ValueError, self.tz.fromutc, dt3)

    def test_normalize(self):
        other_tz = pytz.timezone('Europe/Paris')
        self.assertIsNot(self.tz, other_tz)

        dt = datetime(2012, 3, 26, 12, 0)
        other_dt = other_tz.localize(dt)

        local_dt = self.tz.normalize(other_dt)

        self.assertIsNot(local_dt.tzinfo, other_dt.tzinfo)
        self.assertNotEqual(
            local_dt.replace(tzinfo=None), other_dt.replace(tzinfo=None))

    def test_astimezone(self):
        other_tz = pytz.timezone('Europe/Paris')
        self.assertIsNot(self.tz, other_tz)

        dt = datetime(2012, 3, 26, 12, 0)
        other_dt = other_tz.localize(dt)

        local_dt = other_dt.astimezone(self.tz)

        self.assertIsNot(local_dt.tzinfo, other_dt.tzinfo)
        self.assertNotEqual(
            local_dt.replace(tzinfo=None), other_dt.replace(tzinfo=None))


class OptimizedUTCTestCase(unittest.TestCase, BaseTzInfoTestCase):
    tz = pytz.utc
    tz_class = tz.__class__


class LegacyUTCTestCase(unittest.TestCase, BaseTzInfoTestCase):
    # Deprecated timezone, but useful for comparison tests.
    tz = pytz.timezone('Etc/UTC')
    tz_class = StaticTzInfo


class StaticTzInfoTestCase(unittest.TestCase, BaseTzInfoTestCase):
    tz = pytz.timezone('GMT')
    tz_class = StaticTzInfo


class DstTzInfoTestCase(unittest.TestCase, BaseTzInfoTestCase):
    tz = pytz.timezone('Australia/Melbourne')
    tz_class = DstTzInfo


def test_suite():
    suite = unittest.TestSuite()
    suite.addTest(doctest.DocTestSuite('pytz'))
    suite.addTest(doctest.DocTestSuite('pytz.tzinfo'))
    suite.addTest(doctest.DocTestSuite('pytz.exceptions'))
    import test_tzinfo
    suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(test_tzinfo))
    return suite


if __name__ == '__main__':
    warnings.simplefilter("error")  # Warnings should be fatal in tests.
    unittest.main(defaultTest='test_suite')
