leap_sec/builder.rs
1//! Builder for custom leap-second tables.
2//!
3//! Useful for testing with synthetic schedules or for constructing tables
4//! from parsed data sources.
5
6use crate::error::Error;
7use crate::table::{LeapEntryInner, LeapSeconds};
8use crate::types::UtcUnixSeconds;
9
10/// A builder for constructing custom [`LeapSeconds`] tables.
11///
12/// # Example
13///
14/// ```
15/// use leap_sec::prelude::*;
16///
17/// let table = LeapSeconds::builder()
18/// .add(UtcUnixSeconds(63_072_000), 10) // 1972-01-01
19/// .add(UtcUnixSeconds(78_796_800), 11) // 1972-07-01
20/// .build()
21/// .unwrap();
22///
23/// let tai = table.utc_to_tai(UtcUnixSeconds(70_000_000)).unwrap();
24/// assert_eq!(tai, TaiSeconds(70_000_010));
25/// ```
26#[derive(Debug, Clone)]
27pub struct LeapSecondsBuilder {
28 entries: Vec<LeapEntryInner>,
29 expires_at: Option<i64>,
30}
31
32impl LeapSecondsBuilder {
33 /// Create a new empty builder.
34 pub const fn new() -> Self {
35 Self {
36 entries: Vec::new(),
37 expires_at: None,
38 }
39 }
40
41 /// Add a leap-second entry.
42 ///
43 /// `utc` is the UTC Unix timestamp at which the offset takes effect.
44 /// `tai_minus_utc` is the cumulative TAI−UTC offset from this point forward.
45 #[must_use]
46 pub fn add(mut self, utc: UtcUnixSeconds, tai_minus_utc: i32) -> Self {
47 self.entries.push(LeapEntryInner {
48 utc_unix: utc.0,
49 tai_minus_utc,
50 });
51 self
52 }
53
54 /// Set an expiration timestamp for the table.
55 #[must_use]
56 pub const fn expires_at(mut self, at: UtcUnixSeconds) -> Self {
57 self.expires_at = Some(at.0);
58 self
59 }
60
61 /// Build the leap-second table.
62 ///
63 /// # Errors
64 ///
65 /// Returns [`Error::InvalidTable`] if:
66 /// - The table is empty
67 /// - Timestamps are not monotonically increasing
68 pub fn build(self) -> Result<LeapSeconds, Error> {
69 if self.entries.is_empty() {
70 return Err(Error::InvalidTable {
71 detail: "table must contain at least one entry",
72 });
73 }
74
75 for w in self.entries.windows(2) {
76 if w[1].utc_unix <= w[0].utc_unix {
77 return Err(Error::InvalidTable {
78 detail: "timestamps must be monotonically increasing",
79 });
80 }
81 }
82
83 Ok(LeapSeconds::from_owned(self.entries, self.expires_at))
84 }
85}
86
87impl Default for LeapSecondsBuilder {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93impl LeapSeconds {
94 /// Create a builder for constructing a custom leap-second table.
95 pub const fn builder() -> LeapSecondsBuilder {
96 LeapSecondsBuilder::new()
97 }
98}