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
// This is free and unencumbered software released into the public domain.
use crateTimezoneOffset;
use fmt;
/// A validated XSD 1.1 `gYearMonth` with an optional timezone.
///
/// Construction, parsing, and formatting require neither `alloc` nor `jiff`.
/// Formatting can target a fixed-capacity `core::fmt::Write` buffer; writer
/// errors are propagated, including at the signed year boundaries. Serde and
/// Borsh support each require only their own feature, which implies `alloc`
/// but does not enable date/time support.
///
/// Available without allocation or date/time features. Years span the entire
/// `i32` range, including zero as in XSD 1.1; months must be in `1..=12`.
/// Formatting writes a signed year padded to at least four digits, then `-mm`
/// and the optional offset (`Z` for UTC), without an era adjustment. Equality,
/// ordering, and hashing are structural; absence differs from explicit UTC.
/// `Ord` compares the signed year, then month, then optional signed minute
/// count, with absence before any present offset. This total order is for Rust
/// collections, not XSD temporal comparison: no timezone normalization or
/// uncertainty from absent timezones is considered. `Eq` and `Hash` use the same
/// fields. No XSD semantic comparison operation is provided yet.
///
/// ```
/// use xsd::{primitive::GYearMonth, TimezoneOffset};
/// let year_zero = GYearMonth::new(0, 1).unwrap();
/// let utc = year_zero.with_timezone(Some(TimezoneOffset::UTC));
/// assert_ne!(year_zero, utc);
/// assert!(year_zero < utc); // structural ordering only
/// ```
///
/// This replaces the raw `(i32, u8)` alias: use [`Self::new`] for construction
/// and [`Self::year`] and [`Self::month`] for the fields.
/// Parsing follows XSD 1.1: both `0000` and `-0000` map to year zero, a leading
/// plus sign is rejected, and years wider than four digits must not start with
/// zero. Use [`crate::parse_g_year_month`] for lexical validation; input is not
/// trimmed. XSD years are unbounded, but this representation returns range
/// errors outside `i32`, rather than truncating or wrapping.
/// Parsed values do not retain RDF lexical identity: `0000-01Z`,
/// `0000-01+00:00`, and `-0000-01Z` become the same value. RDF applications must
/// retain original lexical strings and datatype identifiers separately when
/// term identity matters.
///
/// With `serde`, the representation is a struct with `year`, `month`, and
/// `timezone` (optional signed minutes), replacing the former two-element tuple.
/// Decoding validates the month and offset and enforces the `i32` year range.
/// The enclosing `PrimitiveValue` enum retains its `GYearMonth` tag; old
/// payloads must migrate.
/// A missing `timezone` field decodes as absent. Round trips preserve signed
/// years (including both `i32` bounds), months, offsets, and enclosing enum tags,
/// but not original lexical spelling. Invalid fields are rejected even inside
/// value wrappers.
///
/// Explicit JSON conversion on [`crate::PrimitiveValue`] or [`crate::Value`]
/// (requires `serde`) instead emits the formatted XSD string. Recover the value
/// with [`crate::parse`] and [`crate::G_YEAR_MONTH`], not Serde deserialization of
/// the wrapper. This preserves signed years, months, and optional offsets, but
/// carries no datatype tag and normalizes signed zero offsets to `Z`.
/// Explicit BSON conversion (requires `bson`, hence `std`) uses the same XSD
/// string, not a BSON date-time or array. Reparse with [`crate::G_YEAR_MONTH`]
/// to recover the value; a string literal with the same text has identical BSON.
///
/// With `borsh`, the type-local version-1 encoding is a version byte `1`, a
/// little-endian `i32` year, a `u8` month, then `Option<TimezoneOffset>`: tag `0`
/// for absent, or tag `1` followed by little-endian `i16` minutes. This is 7 or 9
/// bytes with no datatype tag. It replaces the raw alias's five-byte encoding;
/// decode legacy data as `(i32, u8)` and validate with [`Self::new`] before
/// re-encoding. Decoding rejects unknown versions, invalid months and option
/// tags, and out-of-range offsets.
///
/// See: <https://www.w3.org/TR/xmlschema-2/#gYearMonth>