11# -*- coding: utf-8 -*-
22
3+ import inspect
34import os
45import pytz
5- import inspect
66
7- from .parser import Parser
8- from .._compat import decode
7+ from datetime import datetime
8+ from struct import unpack , calcsize
9+
10+ from .. import _compat
11+ from .breakdown import local_time
12+ from .transition import Transition
13+ from .transition_type import TransitionType
14+
15+
16+ def _byte_string (s ):
17+ """Cast a string or byte string to an ASCII byte string."""
18+ return s .encode ('US-ASCII' )
19+
20+ _NULL = _byte_string ('\0 ' )
21+
22+
23+ def _std_string (s ):
24+ """Cast a string or byte string to an ASCII string."""
25+ return str (s .decode ('US-ASCII' ))
926
1027
1128class Loader (object ):
@@ -14,17 +31,104 @@ class Loader(object):
1431
1532 @classmethod
1633 def load (cls , name ):
17- name = decode (name )
34+ name = _compat .decode (name )
35+ try :
36+ with pytz .open_resource (name ) as f :
37+ return cls ._load (f )
38+ except _compat .FileNotFoundError :
39+ raise ValueError ('Unknown timezone [{}]' .format (name ))
1840
19- name_parts = name .lstrip ('/' ).split ('/' )
41+ @classmethod
42+ def _load (cls , fp ):
43+ head_fmt = '>4s c 15x 6l'
44+ head_size = calcsize (head_fmt )
45+ (magic , fmt , ttisgmtcnt , ttisstdcnt , leapcnt , timecnt ,
46+ typecnt , charcnt ) = unpack (head_fmt , fp .read (head_size ))
2047
21- for part in name_parts :
22- if part == os .path .pardir or os .path .sep in part :
23- raise ValueError ('Bad path segment: %r' % part )
48+ # Make sure it is a tzfile(5) file
49+ assert magic == _byte_string ('TZif' ), 'Got magic %s' % repr (magic )
2450
25- filepath = os .path .join (cls .path , * name_parts )
51+ # Read out the transition times,
52+ # localtime indices and ttinfo structures.
53+ data_fmt = '>%(timecnt)dl %(timecnt)dB %(ttinfo)s %(charcnt)ds' % dict (
54+ timecnt = timecnt , ttinfo = 'lBB' * typecnt , charcnt = charcnt )
55+ data_size = calcsize (data_fmt )
56+ data = unpack (data_fmt , fp .read (data_size ))
2657
27- if not os .path .exists (filepath ):
28- raise ValueError ('Unknown timezone [{}]' .format (name ))
58+ # make sure we unpacked the right number of values
59+ assert len (data ) == 2 * timecnt + 3 * typecnt + 1
60+ transition_times = tuple (trans for trans in data [:timecnt ])
61+ lindexes = tuple (data [timecnt :2 * timecnt ])
62+ ttinfo_raw = data [2 * timecnt :- 1 ]
63+ tznames_raw = data [- 1 ]
64+ del data
65+
66+ # Process ttinfo into separate structs
67+ transition_types = tuple ()
68+ tznames = {}
69+ i = 0
70+ while i < len (ttinfo_raw ):
71+ # have we looked up this timezone name yet?
72+ tzname_offset = ttinfo_raw [i + 2 ]
73+ if tzname_offset not in tznames :
74+ nul = tznames_raw .find (_NULL , tzname_offset )
75+ if nul < 0 :
76+ nul = len (tznames_raw )
77+ tznames [tzname_offset ] = _std_string (
78+ tznames_raw [tzname_offset :nul ])
79+ transition_types += (
80+ TransitionType (
81+ ttinfo_raw [i ], bool (ttinfo_raw [i + 1 ]),
82+ tznames [tzname_offset ]
83+ ),
84+ )
85+ i += 3
86+
87+ # Now build the timezone object
88+ if len (transition_times ) == 0 :
89+ transitions = tuple ()
90+ else :
91+ # calculate transition info
92+ transitions = tuple ()
93+ for i in range (len (transition_times )):
94+ transition_type = transition_types [lindexes [i ]]
95+
96+ if i == 0 :
97+ pre_transition_type = transition_types [lindexes [i ]]
98+ else :
99+ pre_transition_type = transition_types [lindexes [i - 1 ]]
100+
101+ pre_time = datetime (* local_time (transition_times [i ],
102+ pre_transition_type )[:7 ])
103+ time = datetime (* local_time (transition_times [i ],
104+ transition_type )[:7 ])
105+ tr = Transition (
106+ transition_times [i ],
107+ transition_type ,
108+ pre_time ,
109+ time ,
110+ pre_transition_type
111+ )
112+
113+ transitions += (tr ,)
114+
115+ # Determine the before-first-transition type
116+ default_transition_type_index = 0
117+ if transitions :
118+ index = 0
119+ if transition_types [0 ].is_dst :
120+ index = transition_types .index (transitions [0 ].transition_type )
121+ while index != 0 and transition_types [index ].is_dst :
122+ index -= 1
123+
124+ while index != len (transitions ) and transition_types [index ].is_dst :
125+ index += 1
126+
127+ if index != len (transitions ):
128+ default_transition_type_index = index
29129
30- return Parser .parse (filepath )
130+ return (
131+ transitions ,
132+ transition_types ,
133+ transition_types [default_transition_type_index ]
134+ )
0 commit comments