1''' 2$Id: tzfile.py,v 1.8 2004/06/03 00:15:24 zenzen Exp $ 3''' 4 5from datetime import datetime 6from struct import unpack, calcsize 7 8from pytz.tzinfo import StaticTzInfo, DstTzInfo, memorized_ttinfo 9from pytz.tzinfo import memorized_datetime, memorized_timedelta 10 11 12def _byte_string(s): 13 """Cast a string or byte string to an ASCII byte string.""" 14 return s.encode('ASCII') 15 16_NULL = _byte_string('\0') 17 18 19def _std_string(s): 20 """Cast a string or byte string to an ASCII string.""" 21 return str(s.decode('ASCII')) 22 23 24def build_tzinfo(zone, fp): 25 head_fmt = '>4s c 15x 6l' 26 head_size = calcsize(head_fmt) 27 (magic, format, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, 28 typecnt, charcnt) = unpack(head_fmt, fp.read(head_size)) 29 30 # Make sure it is a tzfile(5) file 31 assert magic == _byte_string('TZif'), 'Got magic %s' % repr(magic) 32 33 # Read out the transition times, localtime indices and ttinfo structures. 34 data_fmt = '>%(timecnt)dl %(timecnt)dB %(ttinfo)s %(charcnt)ds' % dict( 35 timecnt=timecnt, ttinfo='lBB' * typecnt, charcnt=charcnt) 36 data_size = calcsize(data_fmt) 37 data = unpack(data_fmt, fp.read(data_size)) 38 39 # make sure we unpacked the right number of values 40 assert len(data) == 2 * timecnt + 3 * typecnt + 1 41 transitions = [memorized_datetime(trans) 42 for trans in data[:timecnt]] 43 lindexes = list(data[timecnt:2 * timecnt]) 44 ttinfo_raw = data[2 * timecnt:-1] 45 tznames_raw = data[-1] 46 del data 47 48 # Process ttinfo into separate structs 49 ttinfo = [] 50 tznames = {} 51 i = 0 52 while i < len(ttinfo_raw): 53 # have we looked up this timezone name yet? 54 tzname_offset = ttinfo_raw[i + 2] 55 if tzname_offset not in tznames: 56 nul = tznames_raw.find(_NULL, tzname_offset) 57 if nul < 0: 58 nul = len(tznames_raw) 59 tznames[tzname_offset] = _std_string( 60 tznames_raw[tzname_offset:nul]) 61 ttinfo.append((ttinfo_raw[i], 62 bool(ttinfo_raw[i + 1]), 63 tznames[tzname_offset])) 64 i += 3 65 66 # Now build the timezone object 67 if len(ttinfo) == 1 or len(transitions) == 0: 68 ttinfo[0][0], ttinfo[0][2] 69 cls = type(zone, (StaticTzInfo,), dict( 70 zone=zone, 71 _utcoffset=memorized_timedelta(ttinfo[0][0]), 72 _tzname=ttinfo[0][2])) 73 else: 74 # Early dates use the first standard time ttinfo 75 i = 0 76 while ttinfo[i][1]: 77 i += 1 78 if ttinfo[i] == ttinfo[lindexes[0]]: 79 transitions[0] = datetime.min 80 else: 81 transitions.insert(0, datetime.min) 82 lindexes.insert(0, i) 83 84 # calculate transition info 85 transition_info = [] 86 for i in range(len(transitions)): 87 inf = ttinfo[lindexes[i]] 88 utcoffset = inf[0] 89 if not inf[1]: 90 dst = 0 91 else: 92 for j in range(i - 1, -1, -1): 93 prev_inf = ttinfo[lindexes[j]] 94 if not prev_inf[1]: 95 break 96 dst = inf[0] - prev_inf[0] # dst offset 97 98 # Bad dst? Look further. DST > 24 hours happens when 99 # a timzone has moved across the international dateline. 100 if dst <= 0 or dst > 3600 * 3: 101 for j in range(i + 1, len(transitions)): 102 stdinf = ttinfo[lindexes[j]] 103 if not stdinf[1]: 104 dst = inf[0] - stdinf[0] 105 if dst > 0: 106 break # Found a useful std time. 107 108 tzname = inf[2] 109 110 # Round utcoffset and dst to the nearest minute or the 111 # datetime library will complain. Conversions to these timezones 112 # might be up to plus or minus 30 seconds out, but it is 113 # the best we can do. 114 utcoffset = int((utcoffset + 30) // 60) * 60 115 dst = int((dst + 30) // 60) * 60 116 transition_info.append(memorized_ttinfo(utcoffset, dst, tzname)) 117 118 cls = type(zone, (DstTzInfo,), dict( 119 zone=zone, 120 _utc_transition_times=transitions, 121 _transition_info=transition_info)) 122 123 return cls() 124 125if __name__ == '__main__': 126 import os.path 127 from pprint import pprint 128 base = os.path.join(os.path.dirname(__file__), 'zoneinfo') 129 tz = build_tzinfo('Australia/Melbourne', 130 open(os.path.join(base, 'Australia', 'Melbourne'), 'rb')) 131 tz = build_tzinfo('US/Eastern', 132 open(os.path.join(base, 'US', 'Eastern'), 'rb')) 133 pprint(tz._utc_transition_times) 134