Skip to main content

tzcompile/model/
mod.rs

1//! The parsed, still-textual *semantic model* of a tzdata source: the records the parser
2//! produces and the compiler consumes.
3//!
4//! These types mirror the three tzdata line kinds (`Rule`, `Zone`, `Link`) plus the
5//! continuation structure of zones. They are intentionally close to the source — value
6//! interpretation (calendar math, transition generation) happens later, in `compile`.
7
8pub mod calendar;
9pub mod leap;
10pub mod time;
11
12use std::collections::BTreeMap;
13use std::path::{Path, PathBuf};
14
15pub use leap::{LeapSecond, LeapTable};
16pub use time::{Offset, Save, TimeOfDay, TimeRef};
17
18/// Where a record came from, for diagnostics.
19#[derive(Debug, Clone)]
20pub struct Origin {
21    pub file: PathBuf,
22    pub line: usize,
23}
24
25impl Origin {
26    pub fn new(file: &Path, line: usize) -> Self {
27        Origin {
28            file: file.to_path_buf(),
29            line,
30        }
31    }
32}
33
34/// A `Rule` line. Stored verbatim-ish in T1 (only validated, not yet expanded); the
35/// transition compiler in T2 consumes the typed fields.
36#[derive(Debug, Clone)]
37pub struct RuleRecord {
38    pub name: String,
39    /// `FROM` year.
40    pub from: i32,
41    /// `TO` year (after resolving `only`/`maximum`).
42    pub to: YearBound,
43    pub in_month: u8,
44    pub on: calendar::OnDay,
45    pub at: TimeOfDay,
46    pub save: Save,
47    /// `LETTER/S` — the variable part of the abbreviation (`-` becomes empty).
48    pub letter: String,
49    pub origin: Origin,
50}
51
52/// Upper bound of a rule's year range.
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum YearBound {
55    /// A concrete year.
56    Year(i32),
57    /// `maximum` — extends indefinitely.
58    Max,
59}
60
61/// The `RULES` column of a zone era.
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub enum ZoneRules {
64    /// `-` : standard time throughout this era.
65    None,
66    /// An inline saving (the column held a time like `1:00`), not a named ruleset.
67    Save(Save),
68    /// A named ruleset (`Rule NAME ...`).
69    Named(String),
70}
71
72/// One era of a zone (one `Zone`/continuation line up to its `UNTIL`).
73#[derive(Debug, Clone)]
74pub struct ZoneEra {
75    /// Standard UT offset for the era (`STDOFF`), seconds east of UTC.
76    pub stdoff: Offset,
77    pub rules: ZoneRules,
78    /// Abbreviation template (`FORMAT`).
79    pub format: String,
80    /// `UNTIL` instant ending this era; `None` for the final era.
81    pub until: Option<Until>,
82    pub origin: Origin,
83}
84
85/// The `UNTIL` field: a partially-specified wall/standard/UT instant.
86#[derive(Debug, Clone)]
87pub struct Until {
88    pub year: i32,
89    pub month: u8,
90    pub day: calendar::OnDay,
91    pub time: TimeOfDay,
92}
93
94/// A `Zone` plus its continuation lines.
95#[derive(Debug, Clone)]
96pub struct ZoneRecord {
97    pub name: String,
98    pub eras: Vec<ZoneEra>,
99    pub origin: Origin,
100}
101
102/// A `Link` line: `LINK-NAME` is an alias for `TARGET`.
103#[derive(Debug, Clone)]
104pub struct LinkRecord {
105    pub target: String,
106    pub link_name: String,
107    pub origin: Origin,
108}
109
110/// The whole parsed source: zones, links, and rules keyed by name.
111#[derive(Debug, Default, Clone)]
112pub struct Database {
113    pub zones: Vec<ZoneRecord>,
114    pub links: Vec<LinkRecord>,
115    pub rules: BTreeMap<String, Vec<RuleRecord>>,
116}
117
118impl Database {
119    /// Find a zone by exact name.
120    pub fn zone(&self, name: &str) -> Option<&ZoneRecord> {
121        self.zones.iter().find(|z| z.name == name)
122    }
123}