use alloc::{
string::{String, ToString},
vec::Vec,
};
use crate::{
error::{
tz::concatenated::{Error as E, ALLOC_LIMIT},
Error, ErrorContext,
},
tz::TimeZone,
util::{array_str::ArrayStr, escape, utf8},
};
#[derive(Debug)]
pub(crate) struct ConcatenatedTzif<R> {
rdr: R,
header: Header,
}
impl<R: Read> ConcatenatedTzif<R> {
pub(crate) fn open(rdr: R) -> Result<ConcatenatedTzif<R>, Error> {
let header = Header::read(&rdr)?;
Ok(ConcatenatedTzif { rdr, header })
}
pub(crate) fn version(&self) -> ArrayStr<5> {
self.header.version
}
pub(crate) fn get(
&self,
query: &str,
scratch1: &mut Vec<u8>,
scratch2: &mut Vec<u8>,
) -> Result<Option<TimeZone>, Error> {
scratch1.clear();
alloc(scratch1, self.header.index_len())?;
self.rdr
.read_exact_at(scratch1, self.header.index_offset)
.context(E::FailedReadIndex)?;
let mut index = &**scratch1;
while !index.is_empty() {
let entry = IndexEntry::new(&index[..IndexEntry::LEN]);
index = &index[IndexEntry::LEN..];
let ordering = utf8::cmp_ignore_ascii_case_bytes(
entry.name_bytes(),
query.as_bytes(),
);
if ordering.is_ne() {
continue;
}
let name = entry.name().unwrap();
scratch2.clear();
alloc(scratch2, entry.len())?;
let start = self.header.data_offset.saturating_add(entry.start());
self.rdr
.read_exact_at(scratch2, start)
.context(E::FailedReadData)?;
return TimeZone::tzif(name, scratch2).map(Some);
}
Ok(None)
}
pub(crate) fn available(
&self,
scratch: &mut Vec<u8>,
) -> Result<Vec<String>, Error> {
scratch.clear();
alloc(scratch, self.header.index_len())?;
self.rdr
.read_exact_at(scratch, self.header.index_offset)
.context(E::FailedReadIndex)?;
let names_len = self.header.index_len() / IndexEntry::LEN;
let mut names = Vec::with_capacity(names_len);
let mut index = &**scratch;
while !index.is_empty() {
let entry = IndexEntry::new(&index[..IndexEntry::LEN]);
index = &index[IndexEntry::LEN..];
names.push(entry.name()?.to_string());
}
Ok(names)
}
}
#[derive(Debug)]
struct Header {
version: ArrayStr<5>,
index_offset: u64,
data_offset: u64,
}
impl Header {
fn read<R: Read + ?Sized>(rdr: &R) -> Result<Header, Error> {
let mut buf = [0; 12 + 3 * 4];
rdr.read_exact_at(&mut buf, 0).context(E::FailedReadHeader)?;
if &buf[..6] != b"tzdata" {
return Err(Error::from(E::ExpectedFirstSixBytes));
}
if buf[11] != 0 {
return Err(Error::from(E::ExpectedLastByte));
}
let version = {
let version = core::str::from_utf8(&buf[6..11])
.map_err(|_| E::ExpectedVersion)?;
ArrayStr::new(version).unwrap()
};
let index_offset = u64::from(read_be32(&buf[12..16]));
let data_offset = u64::from(read_be32(&buf[16..20]));
if index_offset > data_offset {
return Err(Error::from(E::InvalidIndexDataOffsets));
}
let header = Header { version, index_offset, data_offset };
if header.index_len() % IndexEntry::LEN != 0 {
return Err(Error::from(E::InvalidLengthIndexBlock));
}
Ok(header)
}
fn index_len(&self) -> usize {
let len = self.data_offset.checked_sub(self.index_offset).unwrap();
usize::try_from(len).unwrap_or(usize::MAX)
}
}
#[derive(Clone, Copy)]
struct IndexEntry<'a>(&'a [u8]);
impl<'a> IndexEntry<'a> {
const LEN: usize = 40 + 3 * 4;
fn new(slice: &'a [u8]) -> IndexEntry<'a> {
assert_eq!(slice.len(), IndexEntry::LEN, "invalid index entry length");
IndexEntry(slice)
}
fn name(&self) -> Result<&str, Error> {
core::str::from_utf8(self.name_bytes())
.map_err(|_| Error::from(E::ExpectedIanaName))
}
fn name_bytes(&self) -> &'a [u8] {
let mut block = &self.0[..40];
while block.last().copied() == Some(0) {
block = &block[..block.len() - 1];
}
block
}
fn start(&self) -> u64 {
u64::from(read_be32(&self.0[40..44]))
}
fn len(&self) -> usize {
usize::try_from(read_be32(&self.0[44..48])).unwrap_or(usize::MAX)
}
}
impl<'a> core::fmt::Debug for IndexEntry<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_struct("IndexEntry")
.field("name", &escape::Bytes(self.name_bytes()))
.field("start", &self.start())
.field("len", &self.len())
.finish()
}
}
pub(crate) trait Read {
fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> Result<(), Error>;
}
impl<'a, R: Read + ?Sized> Read for &'a R {
fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> Result<(), Error> {
(**self).read_exact_at(buf, offset)
}
}
fn read_be32(bytes: &[u8]) -> u32 {
u32::from_be_bytes(bytes.try_into().expect("slice of length 4"))
}
#[cfg(test)]
impl Read for [u8] {
fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> Result<(), Error> {
let offset = usize::try_from(offset)
.map_err(|_| E::InvalidOffsetOverflowSlice)?;
let Some(slice) = self.get(offset..) else {
return Err(Error::from(E::InvalidOffsetTooBig));
};
if buf.len() > slice.len() {
return Err(Error::from(E::ExpectedMoreData));
}
buf.copy_from_slice(&slice[..buf.len()]);
Ok(())
}
}
#[cfg(all(feature = "std", unix))]
impl Read for std::fs::File {
fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> Result<(), Error> {
use std::os::unix::fs::FileExt;
FileExt::read_exact_at(self, buf, offset).map_err(Error::io)
}
}
#[cfg(all(feature = "std", windows))]
impl Read for std::fs::File {
fn read_exact_at(
&self,
mut buf: &mut [u8],
mut offset: u64,
) -> Result<(), Error> {
use std::{io, os::windows::fs::FileExt};
while !buf.is_empty() {
match self.seek_read(buf, offset) {
Ok(0) => break,
Ok(n) => {
buf = &mut buf[n..];
offset = u64::try_from(n)
.ok()
.and_then(|n| n.checked_add(offset))
.ok_or(E::InvalidOffsetOverflowFile)?;
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(Error::io(e)),
}
}
if !buf.is_empty() {
Err(Error::io(io::Error::new(
io::ErrorKind::UnexpectedEof,
"failed to fill whole buffer",
)))
} else {
Ok(())
}
}
}
#[cfg(all(feature = "std", all(not(unix), not(windows))))]
impl Read for std::fs::File {
fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> Result<(), Error> {
use std::io::{Read as _, Seek as _, SeekFrom};
let mut file = self;
file.seek(SeekFrom::Start(offset))
.map_err(Error::io)
.context(E::FailedSeek)?;
file.read_exact(buf).map_err(Error::io)
}
}
fn alloc(bytes: &mut Vec<u8>, additional: usize) -> Result<(), Error> {
if additional > ALLOC_LIMIT {
return Err(Error::from(E::AllocRequestOverLimit));
}
bytes.try_reserve_exact(additional).map_err(|_| E::AllocFailed)?;
let new_len =
bytes.len().checked_add(additional).ok_or(E::AllocOverflow)?;
bytes.resize(new_len, 0);
Ok(())
}
#[cfg(test)]
mod tests {
use crate::{
civil::date,
tz::{
offset, testdata::ANDROID_CONCATENATED_TZIF, AmbiguousOffset,
Offset,
},
Timestamp,
};
use super::*;
fn unambiguous(offset_hours: i8) -> AmbiguousOffset {
let offset = offset(offset_hours);
o_unambiguous(offset)
}
fn gap(
earlier_offset_hours: i8,
later_offset_hours: i8,
) -> AmbiguousOffset {
let earlier = offset(earlier_offset_hours);
let later = offset(later_offset_hours);
o_gap(earlier, later)
}
fn fold(
earlier_offset_hours: i8,
later_offset_hours: i8,
) -> AmbiguousOffset {
let earlier = offset(earlier_offset_hours);
let later = offset(later_offset_hours);
o_fold(earlier, later)
}
fn o_unambiguous(offset: Offset) -> AmbiguousOffset {
AmbiguousOffset::Unambiguous { offset }
}
fn o_gap(earlier: Offset, later: Offset) -> AmbiguousOffset {
AmbiguousOffset::Gap { before: earlier, after: later }
}
fn o_fold(earlier: Offset, later: Offset) -> AmbiguousOffset {
AmbiguousOffset::Fold { before: earlier, after: later }
}
#[test]
fn time_zone_tzif_to_ambiguous_timestamp() {
let tests: &[(&str, &[_])] = &[
(
"America/New_York",
&[
((1969, 12, 31, 19, 0, 0, 0), unambiguous(-5)),
((2024, 3, 10, 1, 59, 59, 999_999_999), unambiguous(-5)),
((2024, 3, 10, 2, 0, 0, 0), gap(-5, -4)),
((2024, 3, 10, 2, 59, 59, 999_999_999), gap(-5, -4)),
((2024, 3, 10, 3, 0, 0, 0), unambiguous(-4)),
((2024, 11, 3, 0, 59, 59, 999_999_999), unambiguous(-4)),
((2024, 11, 3, 1, 0, 0, 0), fold(-4, -5)),
((2024, 11, 3, 1, 59, 59, 999_999_999), fold(-4, -5)),
((2024, 11, 3, 2, 0, 0, 0), unambiguous(-5)),
],
),
(
"Europe/Dublin",
&[
((1970, 1, 1, 0, 0, 0, 0), unambiguous(1)),
((2024, 3, 31, 0, 59, 59, 999_999_999), unambiguous(0)),
((2024, 3, 31, 1, 0, 0, 0), gap(0, 1)),
((2024, 3, 31, 1, 59, 59, 999_999_999), gap(0, 1)),
((2024, 3, 31, 2, 0, 0, 0), unambiguous(1)),
((2024, 10, 27, 0, 59, 59, 999_999_999), unambiguous(1)),
((2024, 10, 27, 1, 0, 0, 0), fold(1, 0)),
((2024, 10, 27, 1, 59, 59, 999_999_999), fold(1, 0)),
((2024, 10, 27, 2, 0, 0, 0), unambiguous(0)),
],
),
(
"Australia/Tasmania",
&[
((1970, 1, 1, 11, 0, 0, 0), unambiguous(11)),
((2024, 4, 7, 1, 59, 59, 999_999_999), unambiguous(11)),
((2024, 4, 7, 2, 0, 0, 0), fold(11, 10)),
((2024, 4, 7, 2, 59, 59, 999_999_999), fold(11, 10)),
((2024, 4, 7, 3, 0, 0, 0), unambiguous(10)),
((2024, 10, 6, 1, 59, 59, 999_999_999), unambiguous(10)),
((2024, 10, 6, 2, 0, 0, 0), gap(10, 11)),
((2024, 10, 6, 2, 59, 59, 999_999_999), gap(10, 11)),
((2024, 10, 6, 3, 0, 0, 0), unambiguous(11)),
],
),
(
"Antarctica/Troll",
&[
((1970, 1, 1, 0, 0, 0, 0), unambiguous(0)),
((2024, 3, 31, 0, 59, 59, 999_999_999), unambiguous(0)),
((2024, 3, 31, 1, 0, 0, 0), gap(0, 2)),
((2024, 3, 31, 1, 59, 59, 999_999_999), gap(0, 2)),
((2024, 3, 31, 2, 0, 0, 0), gap(0, 2)),
((2024, 3, 31, 2, 59, 59, 999_999_999), gap(0, 2)),
((2024, 3, 31, 3, 0, 0, 0), unambiguous(2)),
((2024, 10, 27, 0, 59, 59, 999_999_999), unambiguous(2)),
((2024, 10, 27, 1, 0, 0, 0), fold(2, 0)),
((2024, 10, 27, 1, 59, 59, 999_999_999), fold(2, 0)),
((2024, 10, 27, 2, 0, 0, 0), fold(2, 0)),
((2024, 10, 27, 2, 59, 59, 999_999_999), fold(2, 0)),
((2024, 10, 27, 3, 0, 0, 0), unambiguous(0)),
],
),
(
"America/St_Johns",
&[
(
(1969, 12, 31, 20, 30, 0, 0),
o_unambiguous(-Offset::hms(3, 30, 0)),
),
(
(2024, 3, 10, 1, 59, 59, 999_999_999),
o_unambiguous(-Offset::hms(3, 30, 0)),
),
(
(2024, 3, 10, 2, 0, 0, 0),
o_gap(-Offset::hms(3, 30, 0), -Offset::hms(2, 30, 0)),
),
(
(2024, 3, 10, 2, 59, 59, 999_999_999),
o_gap(-Offset::hms(3, 30, 0), -Offset::hms(2, 30, 0)),
),
(
(2024, 3, 10, 3, 0, 0, 0),
o_unambiguous(-Offset::hms(2, 30, 0)),
),
(
(2024, 11, 3, 0, 59, 59, 999_999_999),
o_unambiguous(-Offset::hms(2, 30, 0)),
),
(
(2024, 11, 3, 1, 0, 0, 0),
o_fold(-Offset::hms(2, 30, 0), -Offset::hms(3, 30, 0)),
),
(
(2024, 11, 3, 1, 59, 59, 999_999_999),
o_fold(-Offset::hms(2, 30, 0), -Offset::hms(3, 30, 0)),
),
(
(2024, 11, 3, 2, 0, 0, 0),
o_unambiguous(-Offset::hms(3, 30, 0)),
),
],
),
(
"America/Sitka",
&[
((1969, 12, 31, 16, 0, 0, 0), unambiguous(-8)),
(
(-9999, 1, 2, 16, 58, 46, 0),
o_unambiguous(Offset::hms(14, 58, 47)),
),
(
(1867, 10, 18, 15, 29, 59, 0),
o_unambiguous(Offset::hms(14, 58, 47)),
),
(
(1867, 10, 18, 15, 30, 0, 0),
o_fold(
Offset::hms(14, 58, 47),
-Offset::hms(9, 1, 13),
),
),
(
(1867, 10, 19, 15, 29, 59, 999_999_999),
o_fold(
Offset::hms(14, 58, 47),
-Offset::hms(9, 1, 13),
),
),
(
(1867, 10, 19, 15, 30, 0, 0),
o_unambiguous(-Offset::hms(9, 1, 13)),
),
],
),
(
"Pacific/Honolulu",
&[
(
(1896, 1, 13, 11, 59, 59, 0),
o_unambiguous(-Offset::hms(10, 31, 26)),
),
(
(1896, 1, 13, 12, 0, 0, 0),
o_gap(
-Offset::hms(10, 31, 26),
-Offset::hms(10, 30, 0),
),
),
(
(1896, 1, 13, 12, 1, 25, 0),
o_gap(
-Offset::hms(10, 31, 26),
-Offset::hms(10, 30, 0),
),
),
(
(1896, 1, 13, 12, 1, 26, 0),
o_unambiguous(-Offset::hms(10, 30, 0)),
),
(
(1933, 4, 30, 1, 59, 59, 0),
o_unambiguous(-Offset::hms(10, 30, 0)),
),
(
(1933, 4, 30, 2, 0, 0, 0),
o_gap(-Offset::hms(10, 30, 0), -Offset::hms(9, 30, 0)),
),
(
(1933, 4, 30, 2, 59, 59, 0),
o_gap(-Offset::hms(10, 30, 0), -Offset::hms(9, 30, 0)),
),
(
(1933, 4, 30, 3, 0, 0, 0),
o_unambiguous(-Offset::hms(9, 30, 0)),
),
(
(1933, 5, 21, 10, 59, 59, 0),
o_unambiguous(-Offset::hms(9, 30, 0)),
),
(
(1933, 5, 21, 11, 0, 0, 0),
o_fold(
-Offset::hms(9, 30, 0),
-Offset::hms(10, 30, 0),
),
),
(
(1933, 5, 21, 11, 59, 59, 0),
o_fold(
-Offset::hms(9, 30, 0),
-Offset::hms(10, 30, 0),
),
),
(
(1933, 5, 21, 12, 0, 0, 0),
o_unambiguous(-Offset::hms(10, 30, 0)),
),
(
(1942, 2, 9, 1, 59, 59, 0),
o_unambiguous(-Offset::hms(10, 30, 0)),
),
(
(1942, 2, 9, 2, 0, 0, 0),
o_gap(-Offset::hms(10, 30, 0), -Offset::hms(9, 30, 0)),
),
(
(1942, 2, 9, 2, 59, 59, 0),
o_gap(-Offset::hms(10, 30, 0), -Offset::hms(9, 30, 0)),
),
(
(1942, 2, 9, 3, 0, 0, 0),
o_unambiguous(-Offset::hms(9, 30, 0)),
),
(
(1945, 8, 14, 13, 29, 59, 0),
o_unambiguous(-Offset::hms(9, 30, 0)),
),
(
(1945, 8, 14, 13, 30, 0, 0),
o_unambiguous(-Offset::hms(9, 30, 0)),
),
(
(1945, 8, 14, 13, 30, 1, 0),
o_unambiguous(-Offset::hms(9, 30, 0)),
),
(
(1945, 9, 30, 0, 59, 59, 0),
o_unambiguous(-Offset::hms(9, 30, 0)),
),
(
(1945, 9, 30, 1, 0, 0, 0),
o_fold(
-Offset::hms(9, 30, 0),
-Offset::hms(10, 30, 0),
),
),
(
(1945, 9, 30, 1, 59, 59, 0),
o_fold(
-Offset::hms(9, 30, 0),
-Offset::hms(10, 30, 0),
),
),
(
(1945, 9, 30, 2, 0, 0, 0),
o_unambiguous(-Offset::hms(10, 30, 0)),
),
(
(1947, 6, 8, 1, 59, 59, 0),
o_unambiguous(-Offset::hms(10, 30, 0)),
),
(
(1947, 6, 8, 2, 0, 0, 0),
o_gap(-Offset::hms(10, 30, 0), -offset(10)),
),
(
(1947, 6, 8, 2, 29, 59, 0),
o_gap(-Offset::hms(10, 30, 0), -offset(10)),
),
((1947, 6, 8, 2, 30, 0, 0), unambiguous(-10)),
],
),
];
let db = ConcatenatedTzif::open(ANDROID_CONCATENATED_TZIF).unwrap();
let (mut buf1, mut buf2) = (alloc::vec![], alloc::vec![]);
for &(tzname, datetimes_to_ambiguous) in tests {
let tz = db.get(tzname, &mut buf1, &mut buf2).unwrap().unwrap();
for &(datetime, ambiguous_kind) in datetimes_to_ambiguous {
let (year, month, day, hour, min, sec, nano) = datetime;
let dt = date(year, month, day).at(hour, min, sec, nano);
let got = tz.to_ambiguous_zoned(dt);
assert_eq!(
got.offset(),
ambiguous_kind,
"\nTZ: {tzname}\ndatetime: \
{year:04}-{month:02}-{day:02}T\
{hour:02}:{min:02}:{sec:02}.{nano:09}",
);
}
}
}
#[test]
fn time_zone_tzif_to_datetime() {
let o = |hours| offset(hours);
let tests: &[(&str, &[_])] = &[
(
"America/New_York",
&[
((0, 0), o(-5), "EST", (1969, 12, 31, 19, 0, 0, 0)),
(
(1710052200, 0),
o(-5),
"EST",
(2024, 3, 10, 1, 30, 0, 0),
),
(
(1710053999, 999_999_999),
o(-5),
"EST",
(2024, 3, 10, 1, 59, 59, 999_999_999),
),
((1710054000, 0), o(-4), "EDT", (2024, 3, 10, 3, 0, 0, 0)),
(
(1710055800, 0),
o(-4),
"EDT",
(2024, 3, 10, 3, 30, 0, 0),
),
((1730610000, 0), o(-4), "EDT", (2024, 11, 3, 1, 0, 0, 0)),
(
(1730611800, 0),
o(-4),
"EDT",
(2024, 11, 3, 1, 30, 0, 0),
),
(
(1730613599, 999_999_999),
o(-4),
"EDT",
(2024, 11, 3, 1, 59, 59, 999_999_999),
),
((1730613600, 0), o(-5), "EST", (2024, 11, 3, 1, 0, 0, 0)),
(
(1730615400, 0),
o(-5),
"EST",
(2024, 11, 3, 1, 30, 0, 0),
),
],
),
(
"Australia/Tasmania",
&[
((0, 0), o(11), "AEDT", (1970, 1, 1, 11, 0, 0, 0)),
(
(1728142200, 0),
o(10),
"AEST",
(2024, 10, 6, 1, 30, 0, 0),
),
(
(1728143999, 999_999_999),
o(10),
"AEST",
(2024, 10, 6, 1, 59, 59, 999_999_999),
),
(
(1728144000, 0),
o(11),
"AEDT",
(2024, 10, 6, 3, 0, 0, 0),
),
(
(1728145800, 0),
o(11),
"AEDT",
(2024, 10, 6, 3, 30, 0, 0),
),
((1712415600, 0), o(11), "AEDT", (2024, 4, 7, 2, 0, 0, 0)),
(
(1712417400, 0),
o(11),
"AEDT",
(2024, 4, 7, 2, 30, 0, 0),
),
(
(1712419199, 999_999_999),
o(11),
"AEDT",
(2024, 4, 7, 2, 59, 59, 999_999_999),
),
((1712419200, 0), o(10), "AEST", (2024, 4, 7, 2, 0, 0, 0)),
(
(1712421000, 0),
o(10),
"AEST",
(2024, 4, 7, 2, 30, 0, 0),
),
],
),
(
"Pacific/Honolulu",
&[
(
(-2334101315, 0),
-Offset::hms(10, 31, 26),
"LMT",
(1896, 1, 13, 11, 59, 59, 0),
),
(
(-2334101314, 0),
-Offset::hms(10, 30, 0),
"HST",
(1896, 1, 13, 12, 1, 26, 0),
),
(
(-2334101313, 0),
-Offset::hms(10, 30, 0),
"HST",
(1896, 1, 13, 12, 1, 27, 0),
),
(
(-1157283001, 0),
-Offset::hms(10, 30, 0),
"HST",
(1933, 4, 30, 1, 59, 59, 0),
),
(
(-1157283000, 0),
-Offset::hms(9, 30, 0),
"HDT",
(1933, 4, 30, 3, 0, 0, 0),
),
(
(-1157282999, 0),
-Offset::hms(9, 30, 0),
"HDT",
(1933, 4, 30, 3, 0, 1, 0),
),
(
(-1155436201, 0),
-Offset::hms(9, 30, 0),
"HDT",
(1933, 5, 21, 11, 59, 59, 0),
),
(
(-1155436200, 0),
-Offset::hms(10, 30, 0),
"HST",
(1933, 5, 21, 11, 0, 0, 0),
),
(
(-1155436199, 0),
-Offset::hms(10, 30, 0),
"HST",
(1933, 5, 21, 11, 0, 1, 0),
),
(
(-880198201, 0),
-Offset::hms(10, 30, 0),
"HST",
(1942, 2, 9, 1, 59, 59, 0),
),
(
(-880198200, 0),
-Offset::hms(9, 30, 0),
"HWT",
(1942, 2, 9, 3, 0, 0, 0),
),
(
(-880198199, 0),
-Offset::hms(9, 30, 0),
"HWT",
(1942, 2, 9, 3, 0, 1, 0),
),
(
(-769395601, 0),
-Offset::hms(9, 30, 0),
"HWT",
(1945, 8, 14, 13, 29, 59, 0),
),
(
(-769395600, 0),
-Offset::hms(9, 30, 0),
"HPT",
(1945, 8, 14, 13, 30, 0, 0),
),
(
(-769395599, 0),
-Offset::hms(9, 30, 0),
"HPT",
(1945, 8, 14, 13, 30, 1, 0),
),
(
(-765376201, 0),
-Offset::hms(9, 30, 0),
"HPT",
(1945, 9, 30, 1, 59, 59, 0),
),
(
(-765376200, 0),
-Offset::hms(10, 30, 0),
"HST",
(1945, 9, 30, 1, 0, 0, 0),
),
(
(-765376199, 0),
-Offset::hms(10, 30, 0),
"HST",
(1945, 9, 30, 1, 0, 1, 0),
),
(
(-712150201, 0),
-Offset::hms(10, 30, 0),
"HST",
(1947, 6, 8, 1, 59, 59, 0),
),
(
(-712150200, 0),
-Offset::hms(10, 0, 0),
"HST",
(1947, 6, 8, 2, 30, 0, 0),
),
(
(-712150199, 0),
-Offset::hms(10, 0, 0),
"HST",
(1947, 6, 8, 2, 30, 1, 0),
),
],
),
(
"America/Sitka",
&[
((0, 0), o(-8), "PST", (1969, 12, 31, 16, 0, 0, 0)),
(
(-377705023201, 0),
Offset::hms(14, 58, 47),
"LMT",
(-9999, 1, 2, 16, 58, 46, 0),
),
(
(-3225223728, 0),
Offset::hms(14, 58, 47),
"LMT",
(1867, 10, 19, 15, 29, 59, 0),
),
(
(-3225223727, 0),
-Offset::hms(9, 1, 13),
"LMT",
(1867, 10, 18, 15, 30, 0, 0),
),
(
(-3225223726, 0),
-Offset::hms(9, 1, 13),
"LMT",
(1867, 10, 18, 15, 30, 1, 0),
),
],
),
];
let db = ConcatenatedTzif::open(ANDROID_CONCATENATED_TZIF).unwrap();
let (mut buf1, mut buf2) = (alloc::vec![], alloc::vec![]);
for &(tzname, timestamps_to_datetimes) in tests {
let tz = db.get(tzname, &mut buf1, &mut buf2).unwrap().unwrap();
for &((unix_sec, unix_nano), offset, abbrev, datetime) in
timestamps_to_datetimes
{
let (year, month, day, hour, min, sec, nano) = datetime;
let timestamp = Timestamp::new(unix_sec, unix_nano).unwrap();
let info = tz.to_offset_info(timestamp);
assert_eq!(
info.offset(),
offset,
"\nTZ={tzname}, timestamp({unix_sec}, {unix_nano})",
);
assert_eq!(
info.abbreviation(),
abbrev,
"\nTZ={tzname}, timestamp({unix_sec}, {unix_nano})",
);
assert_eq!(
info.offset().to_datetime(timestamp),
date(year, month, day).at(hour, min, sec, nano),
"\nTZ={tzname}, timestamp({unix_sec}, {unix_nano})",
);
}
}
}
#[test]
#[cfg(not(miri))]
fn read_all_time_zones() {
let db = ConcatenatedTzif::open(ANDROID_CONCATENATED_TZIF).unwrap();
let available = db.available(&mut alloc::vec![]).unwrap();
let (mut buf1, mut buf2) = (alloc::vec![], alloc::vec![]);
for tzname in available.iter() {
let tz = db.get(tzname, &mut buf1, &mut buf2).unwrap().unwrap();
assert_eq!(tzname, tz.iana_name().unwrap());
}
}
#[test]
fn available_len() {
let db = ConcatenatedTzif::open(ANDROID_CONCATENATED_TZIF).unwrap();
let available = db.available(&mut alloc::vec![]).unwrap();
assert_eq!(596, available.len());
for window in available.windows(2) {
let (x1, x2) = (&window[0], &window[1]);
assert!(x1 < x2, "{x1} is not less than {x2}");
}
}
}