1import sys 2import os 3import struct 4from array import array 5from collections import namedtuple 6from datetime import datetime 7 8ttinfo = namedtuple('ttinfo', ['tt_gmtoff', 'tt_isdst', 'tt_abbrind']) 9 10class TZInfo: 11 def __init__(self, transitions, type_indices, ttis, abbrs): 12 self.transitions = transitions 13 self.type_indices = type_indices 14 self.ttis = ttis 15 self.abbrs = abbrs 16 17 @classmethod 18 def fromfile(cls, fileobj): 19 if fileobj.read(4).decode() != "TZif": 20 raise ValueError("not a zoneinfo file") 21 fileobj.seek(20) 22 header = fileobj.read(24) 23 tzh = (tzh_ttisgmtcnt, tzh_ttisstdcnt, tzh_leapcnt, 24 tzh_timecnt, tzh_typecnt, tzh_charcnt) = struct.unpack(">6l", header) 25 transitions = array('i') 26 transitions.fromfile(fileobj, tzh_timecnt) 27 if sys.byteorder != 'big': 28 transitions.byteswap() 29 30 type_indices = array('B') 31 type_indices.fromfile(fileobj, tzh_timecnt) 32 33 ttis = [] 34 for i in range(tzh_typecnt): 35 ttis.append(ttinfo._make(struct.unpack(">lbb", fileobj.read(6)))) 36 37 abbrs = fileobj.read(tzh_charcnt) 38 39 self = cls(transitions, type_indices, ttis, abbrs) 40 self.tzh = tzh 41 42 return self 43 44 def dump(self, stream, start=None, end=None): 45 for j, (trans, i) in enumerate(zip(self.transitions, self.type_indices)): 46 utc = datetime.utcfromtimestamp(trans) 47 tti = self.ttis[i] 48 lmt = datetime.utcfromtimestamp(trans + tti.tt_gmtoff) 49 abbrind = tti.tt_abbrind 50 abbr = self.abbrs[abbrind:self.abbrs.find(0, abbrind)].decode() 51 if j > 0: 52 prev_tti = self.ttis[self.type_indices[j - 1]] 53 shift = " %+g" % ((tti.tt_gmtoff - prev_tti.tt_gmtoff) / 3600) 54 else: 55 shift = '' 56 print("%s UTC = %s %-5s isdst=%d" % (utc, lmt, abbr, tti[1]) + shift, file=stream) 57 58 @classmethod 59 def zonelist(cls, zonedir='/usr/share/zoneinfo'): 60 zones = [] 61 for root, _, files in os.walk(zonedir): 62 for f in files: 63 p = os.path.join(root, f) 64 with open(p, 'rb') as o: 65 magic = o.read(4) 66 if magic == b'TZif': 67 zones.append(p[len(zonedir) + 1:]) 68 return zones 69 70if __name__ == '__main__': 71 if len(sys.argv) < 2: 72 zones = TZInfo.zonelist() 73 for z in zones: 74 print(z) 75 sys.exit() 76 filepath = sys.argv[1] 77 if not filepath.startswith('/'): 78 filepath = os.path.join('/usr/share/zoneinfo', filepath) 79 with open(filepath, 'rb') as fileobj: 80 tzi = TZInfo.fromfile(fileobj) 81 tzi.dump(sys.stdout) 82