ako 0.0.3

Ako is a Rust crate that offers a practical and human-friendly approach to creating, manipulating, formatting and converting dates, times and timestamps.
Documentation
use alloc::boxed::Box;
use alloc::sync::Arc;

use crate::Offset;
use crate::offset::OffsetName;

#[cfg(feature = "std")]
mod tzif;

pub(crate) struct Location {
    // TODO: id
    transitions: Box<[Transition]>,
    zones: Box<[Zone]>,
}

/// A single time-zone transition.
struct Transition {
    /// The transition time, in Unix seconds.
    when: i64,

    /// The index of the zone that goes into effect at `when`.
    zone: u8,
}

/// A single time-zone, such as PDT (Pacific Daylight Time).
#[derive(Debug)]
struct Zone {
    /// Seconds east of UTC (positive = ahead of UTC, negative = behind UTC)
    offset: i32,

    /// Is this zone considered to in “daylight savings time”?
    dst: bool,

    /// The short name of this zone (e.g., PDT or CET).
    name: Arc<str>,
}

impl Location {
    /// Obtains a location with the given time-zone identifier.
    #[cfg(feature = "std")]
    pub(crate) fn of(id: &str) -> crate::Result<Self> {
        // TODO: handle alternative locations for time-zone information
        // TODO: handle TZPATH
        let location = tzif::read(std::path::Path::new("/usr/share/zoneinfo").join(id))?;

        Ok(location)
    }

    /// Obtains a location with the given time-zone identifier.
    #[allow(unused_variables)]
    #[cfg(not(feature = "std"))]
    pub(crate) fn of(id: &str) -> crate::Result<Self> {
        // TODO: might be a good idea for a specific error variant for "tried to load a time zone but we have no database"
        Err(crate::error::ErrorKind::TimeZoneFile.into())
    }

    /// Gets the time-zone rule in effect at the given UTC timestamp, in seconds from the Unix epoch.
    pub(crate) fn offset_at_utc_seconds(&self, seconds: i64) -> Offset {
        match self.transitions.binary_search_by(|t| t.when.cmp(&seconds)) {
            // time is exactly at the start of a transition window
            Ok(index) => self.offset_at_transition_index(index),

            // before the first transition
            Err(index) if index == 0 => {
                todo!()
            }

            // after the last transition
            Err(index) if index == self.transitions.len() => {
                todo!()
            }

            // between two transition windows
            // likely the most common
            Err(index) => self.offset_at_transition_index(index - 1),
        }
    }

    fn offset_at_transition_index(&self, index: usize) -> Offset {
        let zone = self.transitions[index].zone;
        let zone = &self.zones[zone as usize];

        Offset {
            name: OffsetName::Location(zone.name.clone()),
            seconds: zone.offset,
            dst: Some(zone.dst),
        }
    }
}