1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
//! # tzcraft
//!
//! A date and time library that refuses the usual design axioms.
//!
//! ## The four axioms
//!
//! **1. One timeline.** [`Ticks`] is the only type that does instant
//! arithmetic: a signed 128-bit nanosecond counter since the Unix epoch.
//! That single width buys full nanosecond precision *and* a range of roughly
//! ±5.4×10^21 years, so there is no "small instant / large instant" split
//! and no overflow-collapse strategy to learn. [`Duration`] is a distinct
//! signed span type — you cannot add two instants, the type system says so.
//!
//! **2. Civil types are projections, not owners.** [`Date`],
//! [`TimeOfDay`] and [`CivilDateTime`] hold no arithmetic of their own; they
//! are pure projections of the timeline onto the proleptic Gregorian
//! calendar. Calendar-aware operations (months, years) exist once, as
//! project → adjust → re-project. There is no matrix of `Add` impls to
//! implement or misuse.
//!
//! **3. The compiler is the calendar.** Every civil computation — leap
//! rules, the day-count inversion, weekdays, ISO weeks — is a `const fn`,
//! so the compiler folds calendar math at compile time. Timezones are
//! `const` data too: a [`Zone`] is either UTC or a fixed offset, carried
//! inline with the instant in [`Zoned`]. There is no global registry, no
//! mutable "current zone", no IANA download, no hidden context.
//!
//! **4. The codec picks the wire shape.** Every type implements `nextjson`'s
//! format-neutral contracts exactly once. Human-readable codecs (JSON via
//! `nextjson`) see ISO 8601 / RFC 3339 text; binary codecs (`rustbinary`)
//! see compact integers. The same implementation, two shapes, zero feature
//! toggles. JSON stays readable; the binary profile stays small.
//!
//! ## Scope, stated plainly
//!
//! `tzcraft` is `#![no_std]`, `#![deny(unsafe_code)]`, with no dependencies
//! beyond `nextjson` (text/`serde`) and `rustbinary` (binary). With the
//! default features it links `alloc` for the `String`-returning formatting
//! methods and the codecs; with `--no-default-features` it builds **without
//! an allocator at all** — parsing, arithmetic, `Display`/`FromStr` and the
//! `write_*` buffer APIs all keep working. It does **not** ship an IANA
//! timezone database and does **not** pretend that fixed offsets are
//! daylight-saving rules. If a wall clock must follow real transitions,
//! resolve the offset with your own policy and hand the resulting
//! `Zone::Fixed` to the library. The seam is explicit on purpose.
//!
//! ## `chrono` replacement surface
//!
//! The everyday `chrono` API is covered: strftime-style `format` /
//! `parse_from_str` on every type, `Days` / `Months` / `IsoWeek` units,
//! `from_timestamp` / `timestamp(_millis/_micros/_nanos)`, `Duration`
//! constructors and `num_*` counts, `signed_duration_since`,
//! `checked_add_signed`, `with_year`/`with_month`/..., RFC 2822, and the
//! serde story via `nextjson` / `rustbinary`. Three deliberate differences:
//! `format()` returns `Result` (unknown directives are errors), timestamps
//! use floor semantics for pre-epoch instants, and `num_*` returns
//! `Result<i64>` instead of silently overflowing. `Local` (system-local
//! offset) is out of scope without platform FFI; supply the offset yourself
//! through `Zone::fixed`. The full mapping table lives in the README.
//!
//! ## Quick start
//!
//! The example below uses only allocator-free APIs (`write_*` buffer
//! rendering), so it runs in every configuration. The codec round-trip is
//! shown in the [`codec`] module.
//!
//! ```
//! use tzcraft::{Date, Duration, Months, Offset, Ticks, Weekday, Zone, Zoned};
//!
//! // One timeline, any reading.
//! let launch = Ticks::from_rfc3339("2024-06-15T08:30:00Z")?;
//! let local = launch.to_zoned(Zone::fixed(Offset::from_hms(8, 0, 0)?));
//!
//! // Buffer rendering: no allocator required.
//! let mut out = [0u8; 64];
//! let n = local.write_rfc3339(&mut out, tzcraft::FractionDigits::None)?;
//! assert_eq!(&out[..n], b"2024-06-15T16:30:00+08:00");
//! assert_eq!(local.date()?.weekday(), Weekday::Saturday);
//!
//! // Const calendar math: the compiler computes this at compile time.
//! const NEW_YEAR_2025: Date = Date::from_days_since_epoch(20_089);
//! const WEEKDAY: Weekday = NEW_YEAR_2025.weekday();
//! assert_eq!(WEEKDAY, Weekday::Wednesday);
//!
//! // Calendar-aware months clamp instead of overflowing.
//! let jan = Date::from_ymd(2023, 1, 31)?;
//! assert_eq!(jan.checked_add_months(Months::new(1))?, Date::from_ymd(2023, 2, 28)?);
//!
//! // Durations are signed and ISO 8601 round-trip cleanly.
//! let span = Duration::from_iso8601("P1DT2H3M4.5S")?;
//! let n = span.write_iso8601(&mut out)?;
//! assert_eq!(&out[..n], b"P1DT2H3M4.5S");
//! # Ok::<(), tzcraft::Error>(())
//! ```
//!
//! ## `chrono` migration in one glance
//!
//! ```
//! use tzcraft::{CivilDateTime, Date, Duration, Months, Ticks, Weekday};
//!
//! // `DateTime::from_timestamp(1_700_000_000, 0)` / `.timestamp_millis()`
//! let now_ms = Ticks::from_timestamp(1_700_000_000, 0)?.timestamp_millis()?;
//! assert_eq!(now_ms, 1_700_000_000_000);
//! // `NaiveDate::from_ymd_opt(2024, 2, 29).unwrap()`
//! let d = Date::from_ymd(2024, 2, 29)?;
//! // `d.format("%A %d %B %Y")` (allocator-free: `write_format`)
//! let mut out = [0u8; 64];
//! let n = d.write_format("%A %d %B %Y", &mut out)?;
//! assert_eq!(&out[..n], b"Thursday 29 February 2024");
//! // `d.and_hms_opt(12, 0, 0).unwrap()`
//! let dt = d.and_hms(12, 0, 0)?;
//! // `dt.format("%Y-%m-%d %H:%M:%S")`
//! let n = dt.write_format("%Y-%m-%d %H:%M:%S", &mut out)?;
//! assert_eq!(&out[..n], b"2024-02-29 12:00:00");
//! // `d.checked_add_months(Months::new(1))`, `checked_add_days(Days::new(1))`
//! assert_eq!(d.checked_add_months(Months::new(1))?, Date::from_ymd(2024, 3, 29)?);
//! // `NaiveDateTime::parse_from_str`
//! assert_eq!(
//! CivilDateTime::parse_from_str("2024-02-29 12:00:00", "%Y-%m-%d %H:%M:%S")?,
//! dt
//! );
//! // `dt.signed_duration_since(...)`
//! assert_eq!(dt.signed_duration_since(dt), Duration::ZERO);
//! # Ok::<(), tzcraft::Error>(())
//! ```
// `doc_cfg` (which absorbed `doc_auto_cfg` in Rust 1.92) adds "Available on
// crate feature ..." badges on docs.rs (nightly); on stable it is inert.
// The crate is `#![no_std]` in every configuration. With the default
// features it additionally links `alloc` (for the `String`-returning
// formatting methods and the codecs); with `--no-default-features` it builds
// without an allocator entirely (parsing, arithmetic, `Display`/`FromStr`
// and the `write_*` buffer APIs all keep working).
extern crate alloc;
extern crate std;
/// Migration guide: bringing `chrono` / `time` / `rustix` code in
/// (documentation only, no code).
/// Allocator-free output sinks ([`write::Buf`] / [`write::Write`]).
pub use crate;
pub use crateDate;
pub use crateCivilDateTime;
pub use crateDuration;
pub use crate;
pub use crateFractionDigits;
pub use crateOffset;
pub use crateTicks;
pub use crateTimeOfDay;
pub use crate;
pub use crateZone;
pub use crateZoned;