コード例 #1
0
    def test_compare_fuzzy(self):
        match = ZoneMatch({
            'startDateTime': DateTuple(2000, 1, 1, 0, 'w'),
            'untilDateTime': DateTuple(2001, 1, 1, 0, 'w')
        })

        transition = Transition(
            {'transitionTime': DateTuple(1999, 11, 1, 0, 'w')})
        self.assertEqual(-1,
                         _compare_transition_to_match_fuzzy(transition, match))

        transition = Transition(
            {'transitionTime': DateTuple(1999, 12, 1, 0, 'w')})
        self.assertEqual(1,
                         _compare_transition_to_match_fuzzy(transition, match))

        transition = Transition(
            {'transitionTime': DateTuple(2000, 1, 1, 0, 'w')})
        self.assertEqual(1,
                         _compare_transition_to_match_fuzzy(transition, match))

        transition = Transition(
            {'transitionTime': DateTuple(2001, 1, 1, 0, 'w')})
        self.assertEqual(1,
                         _compare_transition_to_match_fuzzy(transition, match))

        transition = Transition(
            {'transitionTime': DateTuple(2001, 2, 1, 0, 'w')})
        self.assertEqual(1,
                         _compare_transition_to_match_fuzzy(transition, match))

        transition = Transition(
            {'transitionTime': DateTuple(2001, 3, 1, 0, 'w')})
        self.assertEqual(2,
                         _compare_transition_to_match_fuzzy(transition, match))
コード例 #2
0
    def test_Petersburg(self):
        """America/Indianapolis/Petersbug moved from central to eastern time in
        1977, then switched back in 2006, then switched back again in 2007.
        """
        zone_specifier = ZoneSpecifier(
            zonedb.zone_infos.ZONE_INFO_America_Indiana_Petersburg,
            viewing_months=14)
        zone_specifier.init_for_year(2006)

        matches = zone_specifier.matches
        self.assertEqual(2, len(matches))

        self.assertEqual(DateTuple(2005, 12, 1, 0, 'w'),
                         matches[0].startDateTime)
        self.assertEqual(DateTuple(2006, 4, 2, 2 * 3600, 'w'),
                         matches[0].untilDateTime)
        self.assertEqual('-', matches[0].zoneEra.policyName)

        self.assertEqual(DateTuple(2006, 4, 2, 2 * 3600, 'w'),
                         matches[1].startDateTime)
        self.assertEqual(DateTuple(2007, 2, 1, 0, 'w'),
                         matches[1].untilDateTime)
        self.assertEqual('US', matches[1].zoneEra.policyName)

        transitions = zone_specifier.transitions
        self.assertEqual(3, len(transitions))

        self.assertEqual(DateTuple(2005, 12, 1, 0, 'w'),
                         transitions[0].startDateTime)
        self.assertEqual(DateTuple(2006, 4, 2, 2 * 3600, 'w'),
                         transitions[0].untilDateTime)
        self.assertEqual(-5 * 3600, transitions[0].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[0].deltaSeconds)

        self.assertEqual(DateTuple(2006, 4, 2, 2 * 3600, 'w'),
                         transitions[1].startDateTime)
        self.assertEqual(DateTuple(2006, 10, 29, 2 * 3600, 'w'),
                         transitions[1].untilDateTime)
        self.assertEqual(-6 * 3600, transitions[1].offsetSeconds)
        self.assertEqual(1 * 3600, transitions[1].deltaSeconds)

        self.assertEqual(DateTuple(2006, 10, 29, 1 * 3600, 'w'),
                         transitions[2].startDateTime)
        self.assertEqual(DateTuple(2007, 2, 1, 0, 'w'),
                         transitions[2].untilDateTime)
        self.assertEqual(-6 * 3600, transitions[2].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[2].deltaSeconds)
コード例 #3
0
    def test_Macquarie(self):
        """Antarctica/Macquarie changes ZoneEra in 2011 using a 'w' time, but
        the ZoneRule transitions use an 's' time, which happens to coincide with
        the change in ZoneEra. The code must treat those 2 transition times as
        the same point in time.
        """
        zone_specifier = ZoneSpecifier(
            zonedb.zone_infos.ZONE_INFO_Antarctica_Macquarie,
            viewing_months=14)
        zone_specifier.init_for_year(2010)

        matches = zone_specifier.matches
        self.assertEqual(2, len(matches))

        self.assertEqual(DateTuple(2009, 12, 1, 0, 'w'),
                         matches[0].startDateTime)
        self.assertEqual(DateTuple(2010, 4, 4, 3 * 3600, 'w'),
                         matches[0].untilDateTime)
        self.assertEqual('AT', matches[0].zoneEra.policyName)

        self.assertEqual(DateTuple(2010, 4, 4, 3 * 3600, 'w'),
                         matches[1].startDateTime)
        self.assertEqual(DateTuple(2011, 2, 1, 0, 'w'),
                         matches[1].untilDateTime)
        self.assertEqual('-', matches[1].zoneEra.policyName)

        transitions = zone_specifier.transitions
        self.assertEqual(2, len(transitions))

        self.assertEqual(DateTuple(2009, 12, 1, 0, 'w'),
                         transitions[0].startDateTime)
        self.assertEqual(DateTuple(2010, 4, 4, 3 * 3600, 'w'),
                         transitions[0].untilDateTime)
        self.assertEqual(10 * 3600, transitions[0].offsetSeconds)
        self.assertEqual(1 * 3600, transitions[0].deltaSeconds)

        self.assertEqual(DateTuple(2010, 4, 4, 3 * 3600, 'w'),
                         transitions[1].startDateTime)
        self.assertEqual(DateTuple(2011, 2, 1, 0 * 3600, 'w'),
                         transitions[1].untilDateTime)
        self.assertEqual(11 * 3600, transitions[1].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[1].deltaSeconds)
コード例 #4
0
    def test_normalize_date_tuple(self):
        self.assertEqual(
            DateTuple(2000, 2, 1, 0, 'w'),
            ZoneSpecifier._normalize_date_tuple(DateTuple(2000, 2, 1, 0, 'w')))

        self.assertEqual(
            DateTuple(2000, 2, 1, 0, 's'),
            ZoneSpecifier._normalize_date_tuple(
                DateTuple(2000, 1, 31, 24 * 3600, 's')))

        self.assertEqual(
            DateTuple(2000, 2, 29, 23 * 3600, 'u'),
            ZoneSpecifier._normalize_date_tuple(
                DateTuple(2000, 3, 1, -3600, 'u')))
コード例 #5
0
    def test_London(self):
        """Europe/London uses a EU which has a 'u' in the AT field.
        """
        zone_specifier = ZoneSpecifier(
            zonedb.zone_infos.ZONE_INFO_Europe_London, viewing_months=14)
        zone_specifier.init_for_year(2000)

        matches = zone_specifier.matches
        self.assertEqual(1, len(matches))

        self.assertEqual(DateTuple(1999, 12, 1, 0, 'w'),
                         matches[0].startDateTime)
        self.assertEqual(DateTuple(2001, 2, 1, 0, 'w'),
                         matches[0].untilDateTime)
        self.assertEqual('EU', matches[0].zoneEra.policyName)

        transitions = zone_specifier.transitions
        self.assertEqual(3, len(transitions))

        self.assertEqual(DateTuple(1999, 12, 1, 0, 'w'),
                         transitions[0].startDateTime)
        self.assertEqual(DateTuple(2000, 3, 26, 1 * 3600, 'w'),
                         transitions[0].untilDateTime)
        self.assertEqual(0 * 3600, transitions[0].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[0].deltaSeconds)

        self.assertEqual(DateTuple(2000, 3, 26, 2 * 3600, 'w'),
                         transitions[1].startDateTime)
        self.assertEqual(DateTuple(2000, 10, 29, 2 * 3600, 'w'),
                         transitions[1].untilDateTime)
        self.assertEqual(0 * 3600, transitions[1].offsetSeconds)
        self.assertEqual(1 * 3600, transitions[1].deltaSeconds)

        self.assertEqual(DateTuple(2000, 10, 29, 1 * 3600, 'w'),
                         transitions[2].startDateTime)
        self.assertEqual(DateTuple(2001, 2, 1, 0, 'w'),
                         transitions[2].untilDateTime)
        self.assertEqual(0 * 3600, transitions[2].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[2].deltaSeconds)
コード例 #6
0
    def test_Los_Angeles(self):
        """America/Los_Angela uses a simple US rule.
        """
        zone_specifier = ZoneSpecifier(
            zonedb.zone_infos.ZONE_INFO_America_Los_Angeles, viewing_months=14)
        zone_specifier.init_for_year(2000)

        matches = zone_specifier.matches
        self.assertEqual(1, len(matches))

        self.assertEqual(DateTuple(1999, 12, 1, 0, 'w'),
                         matches[0].startDateTime)
        self.assertEqual(DateTuple(2001, 2, 1, 0, 'w'),
                         matches[0].untilDateTime)
        self.assertEqual('US', matches[0].zoneEra.policyName)

        transitions = zone_specifier.transitions
        self.assertEqual(3, len(transitions))

        self.assertEqual(DateTuple(1999, 12, 1, 0, 'w'),
                         transitions[0].startDateTime)
        self.assertEqual(DateTuple(2000, 4, 2, 2 * 3600, 'w'),
                         transitions[0].untilDateTime)
        self.assertEqual(-8 * 3600, transitions[0].offsetSeconds)
        self.assertEqual(0, transitions[0].deltaSeconds)

        self.assertEqual(DateTuple(2000, 4, 2, 3 * 3600, 'w'),
                         transitions[1].startDateTime)
        self.assertEqual(DateTuple(2000, 10, 29, 2 * 3600, 'w'),
                         transitions[1].untilDateTime)
        self.assertEqual(-8 * 3600, transitions[1].offsetSeconds)
        self.assertEqual(1 * 3600, transitions[1].deltaSeconds)

        self.assertEqual(DateTuple(2000, 10, 29, 1 * 3600, 'w'),
                         transitions[2].startDateTime)
        self.assertEqual(DateTuple(2001, 2, 1, 0, 'w'),
                         transitions[2].untilDateTime)
        self.assertEqual(-8 * 3600, transitions[2].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[2].deltaSeconds)
コード例 #7
0
    def test_Dublin(self):
        """Europe/Dublin uses negative DST during Winter.
        """
        zone_specifier = ZoneSpecifier(
            zonedb.zone_infos.ZONE_INFO_Europe_Dublin, viewing_months=14)
        zone_specifier.init_for_year(2000)

        matches = zone_specifier.matches
        self.assertEqual(1, len(matches))

        self.assertEqual(DateTuple(1999, 12, 1, 0, 'w'),
                         matches[0].startDateTime)
        self.assertEqual(DateTuple(2001, 2, 1, 0, 'w'),
                         matches[0].untilDateTime)
        self.assertEqual('Eire', matches[0].zoneEra.policyName)

        transitions = zone_specifier.transitions
        self.assertEqual(3, len(transitions))

        self.assertEqual(DateTuple(1999, 12, 1, 0, 'w'),
                         transitions[0].startDateTime)
        self.assertEqual(DateTuple(2000, 3, 26, 1 * 3600, 'w'),
                         transitions[0].untilDateTime)
        self.assertEqual(1 * 3600, transitions[0].offsetSeconds)
        self.assertEqual(-1 * 3600, transitions[0].deltaSeconds)

        self.assertEqual(DateTuple(2000, 3, 26, 2 * 3600, 'w'),
                         transitions[1].startDateTime)
        self.assertEqual(DateTuple(2000, 10, 29, 2 * 3600, 'w'),
                         transitions[1].untilDateTime)
        self.assertEqual(1 * 3600, transitions[1].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[1].deltaSeconds)

        self.assertEqual(DateTuple(2000, 10, 29, 1 * 3600, 'w'),
                         transitions[2].startDateTime)
        self.assertEqual(DateTuple(2001, 2, 1, 0, 'w'),
                         transitions[2].untilDateTime)
        self.assertEqual(1 * 3600, transitions[2].offsetSeconds)
        self.assertEqual(-1 * 3600, transitions[2].deltaSeconds)
コード例 #8
0
    def test_Kamchatka(self):
        """Asia/Kamchatka uses 's' in the Zone UNTIL and Rule AT fields.
        """
        zone_specifier = ZoneSpecifier(
            zonedb.zone_infos.ZONE_INFO_Asia_Kamchatka, viewing_months=14)
        zone_specifier.init_for_year(2011)

        matches = zone_specifier.matches
        self.assertEqual(2, len(matches))

        self.assertEqual(DateTuple(2010, 12, 1, 0 * 3600, 'w'),
                         matches[0].startDateTime)
        self.assertEqual(DateTuple(2011, 3, 27, 2 * 3600, 's'),
                         matches[0].untilDateTime)
        self.assertEqual('Russia', matches[0].zoneEra.policyName)

        self.assertEqual(DateTuple(2011, 3, 27, 2 * 3600, 's'),
                         matches[1].startDateTime)
        self.assertEqual(DateTuple(2012, 2, 1, 0 * 3600, 'w'),
                         matches[1].untilDateTime)
        self.assertEqual('-', matches[1].zoneEra.policyName)

        transitions = zone_specifier.transitions
        self.assertEqual(2, len(transitions))

        self.assertEqual(DateTuple(2010, 12, 1, 0, 'w'),
                         transitions[0].startDateTime)
        self.assertEqual(DateTuple(2011, 3, 27, 2 * 3600, 'w'),
                         transitions[0].untilDateTime)
        self.assertEqual(11 * 3600, transitions[0].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[0].deltaSeconds)

        self.assertEqual(DateTuple(2011, 3, 27, 3 * 3600, 'w'),
                         transitions[1].startDateTime)
        self.assertEqual(DateTuple(2012, 2, 1, 0 * 3600, 'w'),
                         transitions[1].untilDateTime)
        self.assertEqual(12 * 3600, transitions[1].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[1].deltaSeconds)
コード例 #9
0
    def test_Moscow(self):
        """Europe/Moscow uses 's' in the Zone UNTIL field.
        """
        zone_specifier = ZoneSpecifier(
            zonedb.zone_infos.ZONE_INFO_Europe_Moscow, viewing_months=14)
        zone_specifier.init_for_year(2011)

        matches = zone_specifier.matches
        self.assertEqual(2, len(matches))

        self.assertEqual(DateTuple(2010, 12, 1, 0, 'w'),
                         matches[0].startDateTime)
        self.assertEqual(DateTuple(2011, 3, 27, 2 * 3600, 's'),
                         matches[0].untilDateTime)
        self.assertEqual('Russia', matches[0].zoneEra.policyName)

        self.assertEqual(DateTuple(2011, 3, 27, 2 * 3600, 's'),
                         matches[1].startDateTime)
        self.assertEqual(DateTuple(2012, 2, 1, 0, 'w'),
                         matches[1].untilDateTime)
        self.assertEqual('-', matches[1].zoneEra.policyName)

        transitions = zone_specifier.transitions
        self.assertEqual(2, len(transitions))

        self.assertEqual(DateTuple(2010, 12, 1, 0, 'w'),
                         transitions[0].startDateTime)
        self.assertEqual(DateTuple(2011, 3, 27, 2 * 3600, 'w'),
                         transitions[0].untilDateTime)
        self.assertEqual(3 * 3600, transitions[0].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[0].deltaSeconds)

        self.assertEqual(DateTuple(2011, 3, 27, 3 * 3600, 'w'),
                         transitions[1].startDateTime)
        self.assertEqual(DateTuple(2012, 2, 1, 0 * 3600, 'w'),
                         transitions[1].untilDateTime)
        self.assertEqual(4 * 3600, transitions[1].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[1].deltaSeconds)
コード例 #10
0
    def test_Famagusta(self):
        """Asia/Famagusta uses 'u' in the Zone UNTIL field.
        """
        zone_specifier = ZoneSpecifier(
            zonedb.zone_infos.ZONE_INFO_Asia_Famagusta, viewing_months=14)
        zone_specifier.init_for_year(2017)

        matches = zone_specifier.matches
        self.assertEqual(2, len(matches))

        self.assertEqual(DateTuple(2016, 12, 1, 0, 'w'),
                         matches[0].startDateTime)
        self.assertEqual(DateTuple(2017, 10, 29, 1 * 3600, 'u'),
                         matches[0].untilDateTime)
        self.assertEqual('-', matches[0].zoneEra.policyName)

        self.assertEqual(DateTuple(2017, 10, 29, 1 * 3600, 'u'),
                         matches[1].startDateTime)
        self.assertEqual(DateTuple(2018, 2, 1, 0, 'w'),
                         matches[1].untilDateTime)
        self.assertEqual('EUAsia', matches[1].zoneEra.policyName)

        transitions = zone_specifier.transitions
        self.assertEqual(2, len(transitions))

        self.assertEqual(DateTuple(2016, 12, 1, 0, 'w'),
                         transitions[0].startDateTime)
        self.assertEqual(DateTuple(2017, 10, 29, 4 * 3600, 'w'),
                         transitions[0].untilDateTime)
        self.assertEqual(3 * 3600, transitions[0].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[0].deltaSeconds)

        self.assertEqual(DateTuple(2017, 10, 29, 3 * 3600, 'w'),
                         transitions[1].startDateTime)
        self.assertEqual(DateTuple(2018, 2, 1, 0 * 3600, 'w'),
                         transitions[1].untilDateTime)
        self.assertEqual(2 * 3600, transitions[1].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[1].deltaSeconds)
コード例 #11
0
    def test_Winnipeg(self):
        """America/Winnipeg uses 'Rule Winn' until 2006 which has an 's' suffix
        in the Rule.AT field.
        """
        zone_specifier = ZoneSpecifier(
            zonedb.zone_infos.ZONE_INFO_America_Winnipeg, viewing_months=14)
        zone_specifier.init_for_year(2005)

        matches = zone_specifier.matches
        self.assertEqual(2, len(matches))

        self.assertEqual(DateTuple(2004, 12, 1, 0, 'w'),
                         matches[0].startDateTime)
        self.assertEqual(DateTuple(2006, 1, 1, 0 * 3600, 'w'),
                         matches[0].untilDateTime)
        self.assertEqual('Winn', matches[0].zoneEra.policyName)

        self.assertEqual(DateTuple(2006, 1, 1, 0 * 3600, 'w'),
                         matches[1].startDateTime)
        self.assertEqual(DateTuple(2006, 2, 1, 0 * 3600, 'w'),
                         matches[1].untilDateTime)
        self.assertEqual('Canada', matches[1].zoneEra.policyName)

        transitions = zone_specifier.transitions
        self.assertEqual(4, len(transitions))

        self.assertEqual(DateTuple(2004, 12, 1, 0, 'w'),
                         transitions[0].startDateTime)
        self.assertEqual(DateTuple(2005, 4, 3, 2 * 3600, 'w'),
                         transitions[0].untilDateTime)
        self.assertEqual(-6 * 3600, transitions[0].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[0].deltaSeconds)

        self.assertEqual(DateTuple(2005, 4, 3, 3 * 3600, 'w'),
                         transitions[1].startDateTime)
        self.assertEqual(DateTuple(2005, 10, 30, 3 * 3600, 'w'),
                         transitions[1].untilDateTime)
        self.assertEqual(-6 * 3600, transitions[1].offsetSeconds)
        self.assertEqual(1 * 3600, transitions[1].deltaSeconds)

        self.assertEqual(DateTuple(2005, 10, 30, 2 * 3600, 'w'),
                         transitions[2].startDateTime)
        self.assertEqual(DateTuple(2006, 1, 1, 0, 'w'),
                         transitions[2].untilDateTime)
        self.assertEqual(-6 * 3600, transitions[2].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[2].deltaSeconds)

        self.assertEqual(DateTuple(2006, 1, 1, 0 * 3600, 'w'),
                         transitions[3].startDateTime)
        self.assertEqual(DateTuple(2006, 2, 1, 0, 'w'),
                         transitions[3].untilDateTime)
        self.assertEqual(-6 * 3600, transitions[3].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[3].deltaSeconds)
コード例 #12
0
    def test_Simferopol(self):
        """Asia/Simferopol in 2014 uses a bizarre mixture of 'w' when using EU
        rules (which itself uses 'u' in the UNTIL fields), then uses 's' time to
        switch to Moscow time.
        """
        zone_specifier = ZoneSpecifier(
            zonedb.zone_infos.ZONE_INFO_Europe_Simferopol, viewing_months=14)
        zone_specifier.init_for_year(2014)

        matches = zone_specifier.matches
        self.assertEqual(3, len(matches))

        self.assertEqual(DateTuple(2013, 12, 1, 0 * 3600, 'w'),
                         matches[0].startDateTime)
        self.assertEqual(DateTuple(2014, 3, 30, 2 * 3600, 'w'),
                         matches[0].untilDateTime)
        self.assertEqual('EU', matches[0].zoneEra.policyName)

        self.assertEqual(DateTuple(2014, 3, 30, 2 * 3600, 'w'),
                         matches[1].startDateTime)
        self.assertEqual(DateTuple(2014, 10, 26, 2 * 3600, 's'),
                         matches[1].untilDateTime)
        self.assertEqual('-', matches[1].zoneEra.policyName)

        self.assertEqual(DateTuple(2014, 10, 26, 2 * 3600, 's'),
                         matches[2].startDateTime)
        self.assertEqual(DateTuple(2015, 2, 1, 0 * 3600, 'w'),
                         matches[2].untilDateTime)
        self.assertEqual('-', matches[2].zoneEra.policyName)

        transitions = zone_specifier.transitions
        self.assertEqual(3, len(transitions))

        self.assertEqual(DateTuple(2013, 12, 1, 0, 'w'),
                         transitions[0].startDateTime)
        self.assertEqual(DateTuple(2014, 3, 30, 2 * 3600, 'w'),
                         transitions[0].untilDateTime)
        self.assertEqual(2 * 3600, transitions[0].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[0].deltaSeconds)

        self.assertEqual(DateTuple(2014, 3, 30, 4 * 3600, 'w'),
                         transitions[1].startDateTime)
        self.assertEqual(DateTuple(2014, 10, 26, 2 * 3600, 'w'),
                         transitions[1].untilDateTime)
        self.assertEqual(4 * 3600, transitions[1].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[1].deltaSeconds)

        self.assertEqual(DateTuple(2014, 10, 26, 1 * 3600, 'w'),
                         transitions[2].startDateTime)
        self.assertEqual(DateTuple(2015, 2, 1, 0 * 3600, 'w'),
                         transitions[2].untilDateTime)
        self.assertEqual(3 * 3600, transitions[2].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[2].deltaSeconds)
コード例 #13
0
    def test_Apia(self):
        """Pacific/Apia uses a transition time of 24:00 on Dec 29, 2011,
        going from Thursday 29th December 2011 23:59:59 Hours to Saturday 31st
        December 2011 00:00:00 Hours.
        """
        zone_specifier = ZoneSpecifier(
            zonedb.zone_infos.ZONE_INFO_Pacific_Apia, viewing_months=14)
        zone_specifier.init_for_year(2011)

        matches = zone_specifier.matches
        self.assertEqual(2, len(matches))

        self.assertEqual(DateTuple(2010, 12, 1, 0, 'w'),
                         matches[0].startDateTime)
        self.assertEqual(DateTuple(2011, 12, 29, 24 * 3600, 'w'),
                         matches[0].untilDateTime)
        self.assertEqual('WS', matches[0].zoneEra.policyName)

        self.assertEqual(DateTuple(2011, 12, 29, 24 * 3600, 'w'),
                         matches[1].startDateTime)
        self.assertEqual(DateTuple(2012, 2, 1, 0, 'w'),
                         matches[1].untilDateTime)
        self.assertEqual('WS', matches[1].zoneEra.policyName)

        transitions = zone_specifier.transitions
        self.assertEqual(4, len(transitions))

        self.assertEqual(DateTuple(2010, 12, 1, 0, 'w'),
                         transitions[0].startDateTime)
        self.assertEqual(DateTuple(2011, 4, 2, 4 * 3600, 'w'),
                         transitions[0].untilDateTime)
        self.assertEqual(-11 * 3600, transitions[0].offsetSeconds)
        self.assertEqual(1 * 3600, transitions[0].deltaSeconds)

        self.assertEqual(DateTuple(2011, 4, 2, 3 * 3600, 'w'),
                         transitions[1].startDateTime)
        self.assertEqual(DateTuple(2011, 9, 24, 3 * 3600, 'w'),
                         transitions[1].untilDateTime)
        self.assertEqual(-11 * 3600, transitions[1].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[1].deltaSeconds)

        self.assertEqual(DateTuple(2011, 9, 24, 4 * 3600, 'w'),
                         transitions[2].startDateTime)
        self.assertEqual(DateTuple(2011, 12, 30, 0, 'w'),
                         transitions[2].untilDateTime)
        self.assertEqual(-11 * 3600, transitions[2].offsetSeconds)
        self.assertEqual(1 * 3600, transitions[2].deltaSeconds)

        self.assertEqual(DateTuple(2011, 12, 31, 0 * 3600, 'w'),
                         transitions[3].startDateTime)
        self.assertEqual(DateTuple(2012, 2, 1, 0, 'w'),
                         transitions[3].untilDateTime)
        self.assertEqual(13 * 3600, transitions[3].offsetSeconds)
        self.assertEqual(1 * 3600, transitions[3].deltaSeconds)
コード例 #14
0
    def test_expand_date_tuple(self):
        self.assertEqual(
            (DateTuple(2000, 1, 30, 10800,
                       'w'), DateTuple(2000, 1, 30, 7200,
                                       's'), DateTuple(2000, 1, 30, 0, 'u')),
            ZoneSpecifier._expand_date_tuple(DateTuple(2000, 1, 30, 10800,
                                                       'w'),
                                             offset_seconds=7200,
                                             delta_seconds=3600))

        self.assertEqual(
            (DateTuple(2000, 1, 30, 10800,
                       'w'), DateTuple(2000, 1, 30, 7200,
                                       's'), DateTuple(2000, 1, 30, 0, 'u')),
            ZoneSpecifier._expand_date_tuple(DateTuple(2000, 1, 30, 7200, 's'),
                                             offset_seconds=7200,
                                             delta_seconds=3600))

        self.assertEqual(
            (DateTuple(2000, 1, 30, 10800,
                       'w'), DateTuple(2000, 1, 30, 7200,
                                       's'), DateTuple(2000, 1, 30, 0, 'u')),
            ZoneSpecifier._expand_date_tuple(DateTuple(2000, 1, 30, 0, 'u'),
                                             offset_seconds=7200,
                                             delta_seconds=3600))
コード例 #15
0
    def _create_transition_test_items(self, zone_name, tz, zone_specifier):
        """Create a TestItem for the tz for each zone, for each year from
        start_year to until_year, exclusive. The following test samples are
        created:

        * One test point for each month, on the first of the month.
        * One test point for Dec 31, 23:00 for each year.
        * A test point at the transition from DST to Standard, or vise versa.
        * A test point one second before the transition.

        Each TestData is annotated as:
        * 'a': corrected pre-transition
        * 'b': corrected post-transition
        * 'A': pre-transition
        * 'B': post-transition
        * 'S': a monthly test sample
        * 'Y': end of year test sample

        For [2000, 2038], this generates about 100,000 data points.
        """
        items_map = {}
        for year in range(self.start_year, self.until_year):
            # Skip start_year when viewing months is 36, because it needs data
            # for (start_year - 3), but ZoneSpecifier won't generate data for
            # years that old.
            if self.viewing_months == 36 and year == self_start_year: continue

            # Add samples just before and just after the DST transition.
            zone_specifier.init_for_year(year)
            for transition in zone_specifier.transitions:
                # Some Transitions from ZoneSpecifier are in previous or post
                # years (e.g. viewing_months = [14, 36]), so skip those.
                start = transition.startDateTime
                transition_year = start.y
                if transition_year != year: continue

                # If viewing_months== (13 or 36), don't look at Transitions at
                # the beginning of the year since those have been already added.
                if self.viewing_months in [13, 36]:
                    if start.M == 1 and start.d == 1 and start.ss == 0:
                        continue

                correction = self._get_correction(
                    zone_name, transition.transitionTime)

                epoch_seconds = transition.startEpochSecond

                # Add a test data just before the transition
                test_item = self._create_test_item_from_epoch_seconds(
                    tz, epoch_seconds - 1, correction,
                    'a' if correction else 'A')
                self._add_test_item(items_map, test_item)

                # Add a test data at the transition itself (which will
                # normally be shifted forward or backwards).
                test_item = self._create_test_item_from_epoch_seconds(
                    tz, epoch_seconds, correction, 'b' if correction else 'B')
                self._add_test_item(items_map, test_item)

            # Add one sample test point on the first of each month
            for month in range(1, 13):
                tt = DateTuple(y=year, M=month, d=1, ss=0, f='w')
                correction = self._get_correction(zone_name, tt)
                test_item = self._create_test_item_from_datetime(
                    tz, tt, correction, type='S')
                self._add_test_item(items_map, test_item)

            # Add a sample test point at the end of the year.
            tt = DateTuple(y=year, M=12, d=31, ss=23*3600, f='w')
            correction = self._get_correction(zone_name, tt)
            test_item = self._create_test_item_from_datetime(
                tz, tt, correction, type='Y')
            self._add_test_item(items_map, test_item)

        # Return the TestItems ordered by epoch
        return [items_map[x] for x in sorted(items_map)]
コード例 #16
0
    def test_Istanbul(self):
        """Europe/Istanbul uses an 'hh:mm' offset in the RULES field in 2015.
        """
        zone_specifier = ZoneSpecifier(
            zonedb.zone_infos.ZONE_INFO_Europe_Istanbul, viewing_months=14)
        zone_specifier.init_for_year(2015)

        matches = zone_specifier.matches
        self.assertEqual(3, len(matches))

        self.assertEqual(DateTuple(2014, 12, 1, 0, 'w'),
                         matches[0].startDateTime)
        self.assertEqual(DateTuple(2015, 10, 25, 1 * 3600, 'u'),
                         matches[0].untilDateTime)
        self.assertEqual('EU', matches[0].zoneEra.policyName)

        self.assertEqual(DateTuple(2015, 10, 25, 1 * 3600, 'u'),
                         matches[1].startDateTime)
        self.assertEqual(DateTuple(2015, 11, 8, 1 * 3600, 'u'),
                         matches[1].untilDateTime)
        self.assertEqual(':', matches[1].zoneEra.policyName)

        self.assertEqual(DateTuple(2015, 11, 8, 1 * 3600, 'u'),
                         matches[2].startDateTime)
        self.assertEqual(DateTuple(2016, 2, 1, 0, 'w'),
                         matches[2].untilDateTime)
        self.assertEqual('EU', matches[2].zoneEra.policyName)

        transitions = zone_specifier.transitions
        self.assertEqual(4, len(transitions))

        self.assertEqual(DateTuple(2014, 12, 1, 0, 'w'),
                         transitions[0].startDateTime)
        self.assertEqual(DateTuple(2015, 3, 29, 3 * 3600, 'w'),
                         transitions[0].untilDateTime)
        self.assertEqual(2 * 3600, transitions[0].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[0].deltaSeconds)

        self.assertEqual(DateTuple(2015, 3, 29, 4 * 3600, 'w'),
                         transitions[1].startDateTime)
        self.assertEqual(DateTuple(2015, 10, 25, 4 * 3600, 'w'),
                         transitions[1].untilDateTime)
        self.assertEqual(2 * 3600, transitions[1].offsetSeconds)
        self.assertEqual(1 * 3600, transitions[1].deltaSeconds)

        self.assertEqual(DateTuple(2015, 10, 25, 4 * 3600, 'w'),
                         transitions[2].startDateTime)
        self.assertEqual(DateTuple(2015, 11, 8, 4 * 3600, 'w'),
                         transitions[2].untilDateTime)
        self.assertEqual(2 * 3600, transitions[2].offsetSeconds)
        self.assertEqual(1 * 3600, transitions[2].deltaSeconds)

        self.assertEqual(DateTuple(2015, 11, 8, 3 * 3600, 'w'),
                         transitions[3].startDateTime)
        self.assertEqual(DateTuple(2016, 2, 1, 0, 'w'),
                         transitions[3].untilDateTime)
        self.assertEqual(2 * 3600, transitions[3].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[3].deltaSeconds)
コード例 #17
0
    def test_Moncton(self):
        """America/Moncton transitioned DST at 00:01 through 2006.
        """
        zone_specifier = ZoneSpecifier(
            zonedb.zone_infos.ZONE_INFO_America_Moncton, viewing_months=14)
        zone_specifier.init_for_year(2006)

        matches = zone_specifier.matches
        self.assertEqual(2, len(matches))

        self.assertEqual(DateTuple(2005, 12, 1, 0, 'w'),
                         matches[0].startDateTime)
        self.assertEqual(DateTuple(2007, 1, 1, 0 * 3600, 'w'),
                         matches[0].untilDateTime)
        self.assertEqual('Moncton', matches[0].zoneEra.policyName)

        self.assertEqual(DateTuple(2007, 1, 1, 0 * 3600, 'w'),
                         matches[1].startDateTime)
        self.assertEqual(DateTuple(2007, 2, 1, 0, 'w'),
                         matches[1].untilDateTime)
        self.assertEqual('Canada', matches[1].zoneEra.policyName)

        transitions = zone_specifier.transitions
        self.assertEqual(4, len(transitions))

        self.assertEqual(DateTuple(2005, 12, 1, 0, 'w'),
                         transitions[0].startDateTime)
        self.assertEqual(DateTuple(2006, 4, 2, 0 * 3600 + 60, 'w'),
                         transitions[0].untilDateTime)
        self.assertEqual(-4 * 3600, transitions[0].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[0].deltaSeconds)

        self.assertEqual(DateTuple(2006, 4, 2, 1 * 3600 + 60, 'w'),
                         transitions[1].startDateTime)
        self.assertEqual(DateTuple(2006, 10, 29, 0 * 3600 + 60, 'w'),
                         transitions[1].untilDateTime)
        self.assertEqual(-4 * 3600, transitions[1].offsetSeconds)
        self.assertEqual(1 * 3600, transitions[1].deltaSeconds)

        self.assertEqual(DateTuple(2006, 10, 28, 23 * 3600 + 60, 'w'),
                         transitions[2].startDateTime)
        self.assertEqual(DateTuple(2007, 1, 1, 0, 'w'),
                         transitions[2].untilDateTime)
        self.assertEqual(-4 * 3600, transitions[2].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[2].deltaSeconds)

        self.assertEqual(DateTuple(2007, 1, 1, 0 * 3600, 'w'),
                         transitions[3].startDateTime)
        self.assertEqual(DateTuple(2007, 2, 1, 0, 'w'),
                         transitions[3].untilDateTime)
        self.assertEqual(-4 * 3600, transitions[3].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[3].deltaSeconds)
コード例 #18
0
    def test_Santo_Domingo(self):
        """America/Santo_Domingo uses 2 ZoneEra changes in year 2000.
        """
        zone_specifier = ZoneSpecifier(
            zonedb.zone_infos.ZONE_INFO_America_Santo_Domingo,
            viewing_months=14)
        zone_specifier.init_for_year(2000)

        matches = zone_specifier.matches
        self.assertEqual(3, len(matches))

        self.assertEqual(DateTuple(1999, 12, 1, 0, 'w'),
                         matches[0].startDateTime)
        self.assertEqual(DateTuple(2000, 10, 29, 2 * 3600, 'w'),
                         matches[0].untilDateTime)
        self.assertEqual('-', matches[0].zoneEra.policyName)

        self.assertEqual(DateTuple(2000, 10, 29, 2 * 3600, 'w'),
                         matches[1].startDateTime)
        self.assertEqual(DateTuple(2000, 12, 3, 1 * 3600, 'w'),
                         matches[1].untilDateTime)
        self.assertEqual('US', matches[1].zoneEra.policyName)

        self.assertEqual(DateTuple(2000, 12, 3, 1 * 3600, 'w'),
                         matches[2].startDateTime)
        self.assertEqual(DateTuple(2001, 2, 1, 0, 'w'),
                         matches[2].untilDateTime)
        self.assertEqual('-', matches[2].zoneEra.policyName)

        transitions = zone_specifier.transitions
        self.assertEqual(3, len(transitions))

        self.assertEqual(DateTuple(1999, 12, 1, 0, 'w'),
                         transitions[0].startDateTime)
        self.assertEqual(DateTuple(2000, 10, 29, 2 * 3600, 'w'),
                         transitions[0].untilDateTime)
        self.assertEqual(-4 * 3600, transitions[0].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[0].deltaSeconds)

        self.assertEqual(DateTuple(2000, 10, 29, 1 * 3600, 'w'),
                         transitions[1].startDateTime)
        self.assertEqual(DateTuple(2000, 12, 3, 1 * 3600, 'w'),
                         transitions[1].untilDateTime)
        self.assertEqual(-5 * 3600, transitions[1].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[1].deltaSeconds)

        self.assertEqual(DateTuple(2000, 12, 3, 2 * 3600, 'w'),
                         transitions[2].startDateTime)
        self.assertEqual(DateTuple(2001, 2, 1, 0, 'w'),
                         transitions[2].untilDateTime)
        self.assertEqual(-4 * 3600, transitions[2].offsetSeconds)
        self.assertEqual(0 * 3600, transitions[2].deltaSeconds)
コード例 #19
0
class TestDataGenerator:
    """Generate the validation test data using the Transitions determined by
    ZoneSpecifier and the UTC offsets determined by pytz. This gives us
    stability which we can use to test other versions of ZoneSpecifier.
    """

    # The following zones have transitions which occurs at a time which is not a
    # multiple of 15 minutes, so cannot be represented by the C++ TimeOffset
    # object. In all cases below, the actual transition occurs at 00:01, but the
    # Transformer filter truncated the transition time to the nearest 15-minute
    # towards 00:00. To produce the correct validation_data.cpp data file,
    # for the transitions appearing below, we must shift the actual transition
    # time to 00:01 before calling the timezone object in PyTz.
    #
    # The first value of the 2-tuple is a DateTuple that marks the
    # 'transitionTime' of the current Transition (usually the wall time, using
    # the UTC offset of the *previous* Transition), as determined by the
    # (Python) ZoneSpecifier class. Since the algorithm in ZoneSpecifier already
    # truncates the transition time, this will be the transition time *after*
    # the granularity truncation has been applied. In other words, if the actual
    # transitionTime according to the TZ Database was 00:01, then the first
    # component will be stored as 00:00. (The DateTuple[3] component is 'ss'
    # which is the transitionTime in seconds.)
    #
    # The second value of the 2-tuple is the amount of offset (in seconds) that
    # should be added to the transitionTime to get the corrected transitionTime
    # which should be passed to pytz to determine the expected UTC offset.
    #
    # NOTE: Using a dict {} might make the look up faster than using a
    # sequential scan through the DateTuple.
    CORRECTIONS = {
        'Asia/Gaza': [
            (DateTuple(2010, 3, 27, 0, 'w'), 60),
            (DateTuple(2011, 4, 1, 0, 'w'), 60),
        ],
        'America/Goose_Bay': [
            (DateTuple(2000, 4, 2, 0, 'w'), 60),
            (DateTuple(2000, 10, 29, 0, 'w'), 60),
            (DateTuple(2001, 4, 1, 0, 'w'), 60),
            (DateTuple(2001, 10, 28, 0, 'w'), 60),
            (DateTuple(2002, 4, 7, 0, 'w'), 60),
            (DateTuple(2002, 10, 27, 0, 'w'), 60),
            (DateTuple(2003, 4, 6, 0, 'w'), 60),
            (DateTuple(2003, 10, 26, 0, 'w'), 60),
            (DateTuple(2004, 4, 4, 0, 'w'), 60),
            (DateTuple(2004, 10, 31, 0, 'w'), 60),
            (DateTuple(2005, 4, 3, 0, 'w'), 60),
            (DateTuple(2005, 10, 30, 0, 'w'), 60),
            (DateTuple(2006, 4, 2, 0, 'w'), 60),
            (DateTuple(2006, 10, 29, 0, 'w'), 60),
            (DateTuple(2007, 3, 11, 0, 'w'), 60),
            (DateTuple(2007, 11, 4, 0, 'w'), 60),
            (DateTuple(2008, 3, 9, 0, 'w'), 60),
            (DateTuple(2008, 11, 2, 0, 'w'), 60),
            (DateTuple(2009, 3, 8, 0, 'w'), 60),
            (DateTuple(2009, 11, 1, 0, 'w'), 60),
            (DateTuple(2010, 3, 14, 0, 'w'), 60),
            (DateTuple(2010, 11, 7, 0, 'w'), 60),
            (DateTuple(2011, 3, 13, 0, 'w'), 60),
        ],
        'Asia/Hebron': [
            (DateTuple(2011, 4, 1, 0, 'w'), 60),
        ],
        'America/Moncton': [
            (DateTuple(2000, 4, 2, 0, 'w'), 60),
            (DateTuple(2000, 10, 29, 0, 'w'), 60),
            (DateTuple(2001, 4, 1, 0, 'w'), 60),
            (DateTuple(2001, 10, 28, 0, 'w'), 60),
            (DateTuple(2002, 4, 7, 0, 'w'), 60),
            (DateTuple(2002, 10, 27, 0, 'w'), 60),
            (DateTuple(2003, 4, 6, 0, 'w'), 60),
            (DateTuple(2003, 10, 26, 0, 'w'), 60),
            (DateTuple(2004, 4, 4, 0, 'w'), 60),
            (DateTuple(2004, 10, 31, 0, 'w'), 60),
            (DateTuple(2005, 4, 3, 0, 'w'), 60),
            (DateTuple(2005, 10, 30, 0, 'w'), 60),
            (DateTuple(2006, 4, 2, 0, 'w'), 60),
            (DateTuple(2006, 10, 29, 0, 'w'), 60),
        ],
        'America/St_Johns': [
            (DateTuple(2000, 4, 2, 0, 'w'), 60),
            (DateTuple(2000, 10, 29, 0, 'w'), 60),
            (DateTuple(2001, 4, 1, 0, 'w'), 60),
            (DateTuple(2001, 10, 28, 0, 'w'), 60),
            (DateTuple(2002, 4, 7, 0, 'w'), 60),
            (DateTuple(2002, 10, 27, 0, 'w'), 60),
            (DateTuple(2003, 4, 6, 0, 'w'), 60),
            (DateTuple(2003, 10, 26, 0, 'w'), 60),
            (DateTuple(2004, 4, 4, 0, 'w'), 60),
            (DateTuple(2004, 10, 31, 0, 'w'), 60),
            (DateTuple(2005, 4, 3, 0, 'w'), 60),
            (DateTuple(2005, 10, 30, 0, 'w'), 60),
            (DateTuple(2006, 4, 2, 0, 'w'), 60),
            (DateTuple(2006, 10, 29, 0, 'w'), 60),
            (DateTuple(2007, 3, 11, 0, 'w'), 60),
            (DateTuple(2007, 11, 4, 0, 'w'), 60),
            (DateTuple(2008, 3, 9, 0, 'w'), 60),
            (DateTuple(2008, 11, 2, 0, 'w'), 60),
            (DateTuple(2009, 3, 8, 0, 'w'), 60),
            (DateTuple(2009, 11, 1, 0, 'w'), 60),
            (DateTuple(2010, 3, 14, 0, 'w'), 60),
            (DateTuple(2010, 11, 7, 0, 'w'), 60),
            (DateTuple(2011, 3, 13, 0, 'w'), 60),
        ],
    }

    def __init__(self, zone_infos, zone_policies, granularity, start_year,
        until_year):
        """
        Args:
            zone_infos (dict): {zone_name -> zone_info{} }
            zone_policies (dict): {zone_name ->zone_policy{} }
        """
        self.zone_infos = zone_infos
        self.zone_policies = zone_policies
        self.granularity = granularity

        self.zone_name = ''
        self.viewing_months = 14
        self.start_year = start_year
        self.until_year = until_year

    def create_test_data(self):
        """Create a map of {
            zone_name: [ TestItem() ]
        }
        Return (test_data, num_items).
        """
        test_data = {}
        num_items = 0
        for zone_name, zone_info in sorted(self.zone_infos.items()):
            if self.zone_name != '' and zone_name != self.zone_name:
                continue
            test_items = self._create_test_data_for_zone(
                zone_name, zone_info)
            if test_items:
                test_data[zone_name] = test_items
                num_items += len(test_items)
        return (test_data, num_items)

    def _create_test_data_for_zone(self, zone_name, zone_info):
        """Create the TestItems for a specific zone.
        """
        zone_specifier = ZoneSpecifier(zone_info)
        try:
            tz = pytz.timezone(zone_name)
        except:
            logging.error("Zone '%s' not found in Python pytz package",
                          zone_name)
            return None

        return self._create_transition_test_items(
            zone_name, tz, zone_specifier)

    @staticmethod
    def _add_test_item(items_map, item):
        current = items_map.get(item.epoch)
        if current:
            # If a duplicate TestItem exists for epoch, then check that the
            # data is exactly the same.
            if (current.total_offset != item.total_offset
                    or current.dst_offset != item.dst_offset
                    or current.y != item.y or current.M != item.M
                    or current.d != item.d or current.h != item.h
                    or current.m != item.m or current.s != item.s):
                raise Exception('Item %s does not match item %s' % (current,
                                                                    item))
            # 'A' and 'B' takes precedence over 'S' or 'Y'
            if item.type in ['A', 'B']:
                items_map[item.epoch] = item
        else:
            items_map[item.epoch] = item

    def _create_transition_test_items(self, zone_name, tz, zone_specifier):
        """Create a TestItem for the tz for each zone, for each year from
        start_year to until_year, exclusive. The following test samples are
        created:

        * One test point for each month, on the first of the month.
        * One test point for Dec 31, 23:00 for each year.
        * A test point at the transition from DST to Standard, or vise versa.
        * A test point one second before the transition.

        Each TestData is annotated as:
        * 'a': corrected pre-transition
        * 'b': corrected post-transition
        * 'A': pre-transition
        * 'B': post-transition
        * 'S': a monthly test sample
        * 'Y': end of year test sample

        For [2000, 2038], this generates about 100,000 data points.
        """
        items_map = {}
        for year in range(self.start_year, self.until_year):
            # Skip start_year when viewing months is 36, because it needs data
            # for (start_year - 3), but ZoneSpecifier won't generate data for
            # years that old.
            if self.viewing_months == 36 and year == self_start_year: continue

            # Add samples just before and just after the DST transition.
            zone_specifier.init_for_year(year)
            for transition in zone_specifier.transitions:
                # Some Transitions from ZoneSpecifier are in previous or post
                # years (e.g. viewing_months = [14, 36]), so skip those.
                start = transition.startDateTime
                transition_year = start.y
                if transition_year != year: continue

                # If viewing_months== (13 or 36), don't look at Transitions at
                # the beginning of the year since those have been already added.
                if self.viewing_months in [13, 36]:
                    if start.M == 1 and start.d == 1 and start.ss == 0:
                        continue

                correction = self._get_correction(
                    zone_name, transition.transitionTime)

                epoch_seconds = transition.startEpochSecond

                # Add a test data just before the transition
                test_item = self._create_test_item_from_epoch_seconds(
                    tz, epoch_seconds - 1, correction,
                    'a' if correction else 'A')
                self._add_test_item(items_map, test_item)

                # Add a test data at the transition itself (which will
                # normally be shifted forward or backwards).
                test_item = self._create_test_item_from_epoch_seconds(
                    tz, epoch_seconds, correction, 'b' if correction else 'B')
                self._add_test_item(items_map, test_item)

            # Add one sample test point on the first of each month
            for month in range(1, 13):
                tt = DateTuple(y=year, M=month, d=1, ss=0, f='w')
                correction = self._get_correction(zone_name, tt)
                test_item = self._create_test_item_from_datetime(
                    tz, tt, correction, type='S')
                self._add_test_item(items_map, test_item)

            # Add a sample test point at the end of the year.
            tt = DateTuple(y=year, M=12, d=31, ss=23*3600, f='w')
            correction = self._get_correction(zone_name, tt)
            test_item = self._create_test_item_from_datetime(
                tz, tt, correction, type='Y')
            self._add_test_item(items_map, test_item)

        # Return the TestItems ordered by epoch
        return [items_map[x] for x in sorted(items_map)]

    def _get_correction(self, zone_name, tt):
        """Given the DateTuple of interest, return the correction (in seconds)
        due to truncation of the transition time caused by the granularity. For
        example, if the actual transition time was 00:01, but the granularity is
        15 minutes, then the various transition times got truncated to 00:00 and
        the correction will be 60 seconds.
        """
        if self.granularity <= 60:
            return 0

        correction_list = TestDataGenerator.CORRECTIONS.get(zone_name)
        if correction_list:
            for correction in correction_list:
                if tt == correction[0]:
                    return correction[1]
        return 0

    def _create_test_item_from_datetime(self, tz, tt, correction, type):
        """Create a TestItem for the given DateTuple in the local time zone.
        """
        # Can't use the normal datetime constructor for pytz. Must use
        # timezone.localize(). See https://stackoverflow.com/questions/18541051
        ldt = datetime.datetime(tt.y, tt.M, tt.d, tt.ss//3600)
        dt = tz.localize(ldt)
        unix_seconds = int(dt.timestamp())
        epoch_seconds = unix_seconds - SECONDS_SINCE_UNIX_EPOCH
        return self._create_test_item_from_epoch_seconds(
            tz, epoch_seconds, correction, type)


    def _create_test_item_from_epoch_seconds(self, tz, epoch_seconds,
        correction, type):
        """Determine the expected date and time components for the given
        'epoch_seconds' for the given 'tz'. The 'epoch_seconds' is the
        transition time calculated by the ZoneSpecifier class (which is the
        Python implementation of the C++ ExtendedZoneSpecifier class). That
        epoch_seconds will be off by 'correction' seconds (usually 60 seconds)
        for a handful of zones at small number of transitions because
        ZoneSpecifier truncates transition time to multiples of 15 minutes. For
        example, a transition time of 00:01 will be truncated to 00:00.

        To calculate the expected date & time components using pytz, the actual
        transition time must be corrected using 'epoch_seconds + correction',
        and this corrected epoch_seconds must be given to pytz to retrieve the
        expected DST offsets.

        Return the TestItem with the following fields:
            epoch: epoch seconds from AceTime epoch (2000-01-01T00:00:00Z)
            total_offset: the expected total UTC offset at epoch_seconds
            dst_offset: the expected DST offset at epoch_seconds
            y, M, d, h, m, s: expected date&time components at epoch_seconds
            type: 'a', 'b', 'A', 'B', 'S', 'Y'
        """

        # Convert AceTime epoch_seconds to Unix epoch_seconds.
        unix_seconds = epoch_seconds + SECONDS_SINCE_UNIX_EPOCH

        # Shift the epoch seconds by its correction to get the actual transition
        # time, then feed that into pytz to get the total offset and DST shift
        corrected_utc_dt = datetime.datetime.fromtimestamp(
            unix_seconds + correction, tz=datetime.timezone.utc)
        corrected_dt = corrected_utc_dt.astimezone(tz)
        total_offset = int(corrected_dt.utcoffset().total_seconds())
        dst_offset = int(corrected_dt.dst().total_seconds())

        # The expected YMDHMS components (from AceTime) are determined by taking
        # the corrected datetime (from pytz), and shifting back by the given
        # correction.
        rdt = corrected_dt - timedelta(seconds=correction)

        return TestItem(
            epoch=epoch_seconds,
            total_offset=total_offset,
            dst_offset=dst_offset,
            y=rdt.year,
            M=rdt.month,
            d=rdt.day,
            h=rdt.hour,
            m=rdt.minute,
            s=rdt.second,
            type=type)