leapsec.ha (3090B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use bufio; 5 use encoding::utf8; 6 use fs; 7 use io; 8 use os; 9 use strconv; 10 use strings; 11 12 // Hare uses raw leap second information when dealing with the UTC and TAI 13 // timescales. This information is source from a standard file installed at 14 // /usr/share/zoneinfo/leap-seconds.list, which itself is fetched from and 15 // periodically maintained at various observatories. 16 // 17 // https://data.iana.org/time-zones/code/leap-seconds.list 18 // https://www.ietf.org/timezones/data/leap-seconds.list 19 // ftp://ftp.nist.gov/pub/time/leap-seconds.list 20 // ftp://ftp.boulder.nist.gov/pub/time/leap-seconds.list 21 // 22 // This is in contrast to previous systems which rely on TZif files, which are 23 // installed typically at /usr/share/zoneinfo, as part of the "Olson" IANA 24 // Timezone database. These files couple timezone and leap second data. 25 // 26 // Depending on a system's installation, leap second information may be 27 // deliberately left out of the TZif files, or duplicated throughout. This 28 // design also inhibits our ambitions for dealing with multiple, dynamic 29 // timescales. Therefore, we have decided to take an alternative approach. 30 31 // Error initializing the [[utc]] [[timescale]]. 32 type utciniterror = !(fs::error | io::error | utf8::invalid); 33 34 // The number of seconds between the years 1900 and 1970. 35 // 36 // This number is hypothetical since timekeeping before atomic clocks was not 37 // accurate enough to account for small changes in time. 38 export def SECS_1900_1970: i64 = 2208988800; 39 40 // UTC/TAI leap second data; UTC timestamps and their offsets from TAI. 41 // Sourced from [[UTC_LEAPSECS_PATH]]. 42 let utc_leapsecs: [](i64, i64) = []; 43 44 let utc_isinitialized: bool = false; 45 46 @fini fn free_utc() void = { 47 free(utc_leapsecs); 48 }; 49 50 fn init_utc_leapsecs() (void | utciniterror) = { 51 const file = os::open(UTC_LEAPSECS_PATH)?; 52 defer io::close(file)!; 53 parse_utc_leapsecs(file)?; 54 }; 55 56 // Parse UTC/TAI leap second data from [[UTC_LEAPSECS_PATH]]. 57 // See file for format details. 58 fn parse_utc_leapsecs(h: io::handle) (void | utf8::invalid | io::error) = { 59 for (let line => bufio::read_line(h)?) { 60 let line = strings::fromutf8(line)?; 61 defer free(line); 62 63 if (strings::hasprefix(line, '#')) { 64 continue; 65 }; 66 67 const iter = strings::iter(line); 68 const secs = scan_number(&iter); scan_whitespace(&iter); 69 const diff = scan_number(&iter); 70 if (secs is void || diff is void) { 71 continue; 72 }; 73 74 let secs = secs as i64 - SECS_1900_1970; 75 let diff = diff as i64; 76 append(utc_leapsecs, (secs, diff)); 77 }; 78 }; 79 80 fn scan_number(iter: *strings::iterator) (i64 | void) = { 81 let begin = *iter; 82 83 for (let rn => strings::next(iter)) { 84 switch (rn) { 85 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => 86 continue; 87 case => 88 strings::prev(iter); 89 break; 90 }; 91 }; 92 93 return strconv::stoi64(strings::slice(&begin, iter))!; 94 }; 95 96 fn scan_whitespace(iter: *strings::iterator) void = { 97 for (let rn => strings::next(iter)) { 98 switch (rn) { 99 case ' ', '\t' => 100 continue; 101 case => 102 strings::prev(iter); 103 return; 104 }; 105 }; 106 };