1
1
# -*- coding: utf-8 -*-
2
2
3
+ import inspect
3
4
import os
4
5
import pytz
5
- import inspect
6
6
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' ))
9
26
10
27
11
28
class Loader (object ):
@@ -14,17 +31,104 @@ class Loader(object):
14
31
15
32
@classmethod
16
33
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 ))
18
40
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 ))
20
47
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 )
24
50
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 ))
26
57
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
29
129
30
- return Parser .parse (filepath )
130
+ return (
131
+ transitions ,
132
+ transition_types ,
133
+ transition_types [default_transition_type_index ]
134
+ )
0 commit comments