UNPKG

parse-zoneinfo

Version:

Async parser for binary tzdata zoneinfo files.

243 lines (207 loc) 7.18 kB
const fs = require('node:fs').promises; module.exports = parse; module.exports.PREFIX = '/usr/share/zoneinfo'; async function parse(name) { const filename = `${parse.PREFIX}/${name}`; const buffer = await fs.readFile(filename); const tzinfo = parseBuffer(buffer); if (!tzinfo) { throw new Error('invalid file format'); } return tzinfo; } /* initial version of parseBuffer was copied from zoneinfo package license MIT https://github.com/gsmcwhirter/node-zoneinfo/blob/master/index.js */ function parseBuffer(buffer) { let buffer_idx = 0; function readUInt8() { return buffer.readUInt8(buffer_idx++); } function readInt8() { return buffer.readUInt8(buffer_idx++); } function readInt32() { const l = buffer.readInt32BE(buffer_idx); buffer_idx += 4; return l; } function readInt8Array(len) { const r = []; while (len--) { r.push(readInt8()); } return r; } function readUInt8Array(len) { const r = []; while (len--) { r.push(readUInt8()); } return r; } function readInt32Array(len) { const r = []; while (len--) { r.push(readInt32()); } return r; } function readString(len) { const start = buffer_idx; const end = start + len; buffer_idx = end; return buffer.slice(start, end).toString('ascii'); } function readTTInfo() { const gmtoff = readInt32(); const isdst = readInt8(); const abbrind = readInt8(); return [gmtoff, isdst, abbrind]; } // From tzfile(5): // // The time zone information files used by tzset(3) // begin with the magic characters "TZif" to identify // them as time zone information files, followed by // sixteen bytes reserved for future use, followed by // six four-byte values of type long, written in a // ``standard'' byte order (the high-order byte // of the value is written first). if (readString(4) !== 'TZif') { return; } //ignore 16 bytes buffer_idx = 20; // The number of UTC/local indicators stored in the file. const _ttisgmtct = readInt32(); // The number of standard/wall indicators stored in the file. const _ttisstdct = readInt32(); // The number of leap seconds for which data is // stored in the file. const _leapct = readInt32(); // The number of "transition times" for which data // is stored in the file. const _timect = readInt32(); // The number of "local time types" for which data // is stored in the file (must not be zero). const _typect = readInt32(); // The number of characters of "time zone // abbreviation strings" stored in the file. const _charct = readInt32(); const tzinfo = { trans_list: null, trans_idx: null, ttinfo_list: null, ttinfo_std: null, ttinfo_dst: null, ttinfo_before: null }; // The above header is followed by tzh_timecnt four-byte // values of type long, sorted in ascending order. // These values are written in ``standard'' byte order. // Each is used as a transition time (as returned by // time(2)) at which the rules for computing local time // change. tzinfo.trans_list = readInt32Array(_timect); // Next come tzh_timecnt one-byte values of type unsigned // char; each one tells which of the different types of // ``local time'' types described in the file is associated // with the same-indexed transition time. These values // serve as indices into an array of ttinfo structures that // appears next in the file. tzinfo.trans_idx = readUInt8Array(_timect); // Each ttinfo structure is written as a four-byte value // for tt_gmtoff of type long, in a standard byte // order, followed by a one-byte value for tt_isdst // and a one-byte value for tt_abbrind. In each // structure, tt_gmtoff gives the number of // seconds to be added to UTC, tt_isdst tells whether // tm_isdst should be set by localtime(3), and // tt_abbrind serves as an index into the array of // time zone abbreviation characters that follow the // ttinfo structure(s) in the file. const _ttinfo = []; for (let i = 0; i < _typect; i++) { _ttinfo.push(readTTInfo()); } const _abbr = readString(_charct); // Then there are tzh_leapcnt pairs of four-byte // values, written in standard byte order; the // first value of each pair gives the time (as // returned by time(2)) at which a leap second // occurs; the second gives the total number of // leap seconds to be applied after the given time. // The pairs of values are sorted in ascending order // by time. // not using _leap - at the moment // var _leap = readInt32Array(_leapct * 2) ; // just move counter instead buffer_idx += _leapct * 8; // Then there are tzh_ttisstdcnt standard/wall // indicators, each stored as a one-byte value; // they tell whether the transition times associated // with local time types were specified as standard // time or wall clock time, and are used when // a time zone file is used in handling POSIX-style // time zone environment variables. const _isstd = readInt8Array(_ttisstdct); // Finally, there are tzh_ttisgmtcnt UTC/local // indicators, each stored as a one-byte value; // they tell whether the transition times associated // with local time types were specified as UTC or // local time, and are used when a time zone file // is used in handling POSIX-style time zone envi- // ronment variables. const _isgmt = readUInt8Array(_ttisgmtct); //finished reading tzinfo.ttinfo_list = _ttinfo.map((item, index) => { item[0] = Math.floor(item[0] / 60); return { offset: item[0], isdst: item[1], abbr: _abbr.slice(item[2], _abbr.indexOf('\x00', item[2])), isstd: _ttisstdct > index && _isstd[index] !== 0, isgmt: _ttisgmtct > index && _isgmt[index] !== 0 }; }); // Replace ttinfo indexes for ttinfo objects. tzinfo.trans_idx = tzinfo.trans_idx.map(item => { return tzinfo.ttinfo_list[item]; }); // Set standard, dst, and before ttinfos. before will be // used when a given time is before any transitions, // and will be set to the first non-dst ttinfo, or to // the first dst, if all of them are dst. if (tzinfo.ttinfo_list.length) { if (!tzinfo.trans_list.length) { tzinfo.ttinfo_std = tzinfo.ttinfo_first = tzinfo.ttinfo_list[0]; } else { for (let j = _timect - 1; j > -1; j--) { const tti = tzinfo.trans_idx[j]; if (!tzinfo.ttinfo_std && !tti.isdst) { tzinfo.ttinfo_std = tti; } else if (!tzinfo.ttinfo_dst && tti.isdst) { tzinfo.ttinfo_dst = tti; } if (tzinfo.ttinfo_dst && tzinfo.ttinfo_std) { break; } } if (tzinfo.ttinfo_dst && !tzinfo.ttinfo_std) { tzinfo.ttinfo_std = tzinfo.ttinfo_dst; } for (const k in tzinfo.ttinfo_list) { if (!tzinfo.ttinfo_list[k].isdst) { tzinfo.ttinfo_before = tzinfo.ttinfo_list[k]; break; } } if (!tzinfo.ttinfo_before) { tzinfo.ttinfo_before = tzinfo.ttinfo_list[0]; } } } return tzinfo; }