1#![doc = include_str!("README.md")]
8
9pub(crate) mod packed;
10mod parser;
11mod validate;
12
13mod basic;
14
15pub mod iter;
17
18#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
19#[cfg(feature = "chrono")]
20mod sorting;
21#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
22#[cfg(feature = "chrono")]
23pub use sorting::{SortOrder, SortOrderEnd, SortOrderStart};
24
25#[cfg(test)]
26mod test;
27
28use core::convert::TryInto;
29use core::fmt;
30use core::num::NonZeroU8;
31use core::str::FromStr;
32
33use crate::helpers;
34use crate::{DateComplete, DateTime, ParseError, Time, TzOffset};
35
36use self::{
37 packed::{DMMask, PackedInt, PackedU8, PackedYear, YearMask},
38 parser::{ParsedEdtf, UnvalidatedDate},
39};
40
41pub use packed::Certainty;
43
44#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
63pub struct Date {
64 pub(crate) year: PackedYear,
65 pub(crate) month: Option<PackedU8>,
66 pub(crate) day: Option<PackedU8>,
67 pub(crate) certainty: Certainty,
68}
69
70#[derive(Clone, Copy, PartialEq, Eq, Hash)]
72pub enum Edtf {
73 DateTime(DateTime),
75 Date(Date),
77 YYear(YYear),
82 Interval(Date, Date),
84 IntervalFrom(Date, Terminal),
86 IntervalTo(Terminal, Date),
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
92pub enum Terminal {
93 Unknown,
95 Open,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
101pub enum Season {
102 Spring = 21,
104 Summer = 22,
106 Autumn = 23,
108 Winter = 24,
110}
111
112impl Season {
113 fn from_u32(value: u32) -> Self {
114 match value {
115 21 => Self::Spring,
116 22 => Self::Summer,
117 23 => Self::Autumn,
118 24 => Self::Winter,
119 _ => panic!("invalid season number {}", value),
120 }
121 }
122 fn from_u32_opt(value: u32) -> Option<Self> {
123 Some(match value {
124 21 => Self::Spring,
125 22 => Self::Summer,
126 23 => Self::Autumn,
127 24 => Self::Winter,
128 _ => return None,
129 })
130 }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154pub enum Precision {
155 Century(i32),
157 Decade(i32),
159 Year(i32),
161 Season(i32, Season),
163 Month(i32, u32),
165 Day(i32, u32, u32),
169 MonthOfYear(i32),
171 DayOfYear(i32),
173 DayOfMonth(i32, u32),
177}
178
179#[doc = include_str!("YYear.md")]
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
183pub struct YYear(i64);
184
185impl YYear {
186 pub fn year(&self) -> i64 {
188 self.0
189 }
190
191 pub fn value(self) -> i64 {
198 self.0
199 }
200
201 pub(crate) fn raw(y: i64) -> Self {
202 Self(y)
203 }
204
205 pub fn new(value: i64) -> Self {
207 Self::new_opt(value).expect("value outside range for YYear, must be 5-digit year")
208 }
209
210 pub fn new_opt(value: i64) -> Option<Self> {
212 if helpers::inside_9999(value) {
213 return None;
214 }
215 Some(Self(value))
216 }
217
218 pub fn new_or_cal(value: i64) -> Result<Self, Edtf> {
221 if helpers::inside_9999(value) {
222 let date = value
223 .try_into()
224 .ok()
225 .and_then(|y| Date::from_ymd_opt(y, 0, 0))
226 .map(Edtf::Date)
227 .expect("should have already validated as within -9999..=9999");
228 return Err(date);
229 }
230 Ok(Self(value))
231 }
232}
233
234#[derive(Debug, Copy, Clone, PartialEq, Eq)]
236pub enum YearDigits {
237 NoX,
239 X,
241 XX,
243}
244
245#[doc(hidden)]
246impl From<YearMask> for YearDigits {
247 fn from(ym: YearMask) -> Self {
248 match ym {
249 YearMask::None => Self::NoX,
250 YearMask::OneDigit => Self::X,
251 YearMask::TwoDigits => Self::XX,
252 }
253 }
254}
255
256#[doc(hidden)]
257impl From<YearDigits> for YearMask {
258 fn from(ym: YearDigits) -> Self {
259 match ym {
260 YearDigits::NoX => YearMask::None,
261 YearDigits::X => YearMask::OneDigit,
262 YearDigits::XX => YearMask::TwoDigits,
263 }
264 }
265}
266
267#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
277pub enum Component {
278 Value(u32),
280 Unspecified,
282}
283
284impl fmt::Display for Component {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 match self {
287 Component::Value(val) => val.fmt(f),
289 Component::Unspecified => {
290 let precision = f.width().unwrap_or(1);
292 write!(f, "{:X<1$}", "", precision)
293 }
294 }
295 }
296}
297
298impl Component {
299 pub fn value(self) -> Option<u32> {
301 match self {
302 Component::Value(v) => Some(v),
303 Component::Unspecified => None,
304 }
305 }
306
307 fn from_packed_filter(packed: PackedU8, range: std::ops::RangeInclusive<u32>) -> Option<Self> {
308 let (val, flags) = packed.unpack();
309 let val = val as u32;
310 if flags.is_masked() {
311 Some(Component::Unspecified)
312 } else if range.contains(&val) {
313 Some(Component::Value(val as u32))
314 } else {
315 None
316 }
317 }
318 fn from_packed(packed: PackedU8) -> Self {
319 let (val, flags) = packed.unpack();
320 if flags.is_masked() {
321 Component::Unspecified
322 } else {
323 Component::Value(val as u32)
324 }
325 }
326}
327
328impl From<Date> for Edtf {
329 fn from(date: Date) -> Self {
330 Self::Date(date)
331 }
332}
333
334impl From<(Date, Date)> for Edtf {
335 fn from((a, b): (Date, Date)) -> Self {
336 Self::Interval(a, b)
337 }
338}
339
340impl FromStr for Edtf {
341 type Err = ParseError;
342
343 fn from_str(s: &str) -> Result<Self, Self::Err> {
344 Edtf::parse(s)
345 }
346}
347
348impl fmt::Display for Date {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 let Date {
351 year,
352 month,
353 day,
354 certainty,
355 } = *self;
356 let (year, yf) = year.unpack();
357 let sign = helpers::sign_str_if_neg(year);
358 let year = year.abs();
359 match yf.mask {
360 YearMask::None => write!(f, "{}{:04}", sign, year)?,
361 YearMask::OneDigit => write!(f, "{}{:03}X", sign, year / 10)?,
362 YearMask::TwoDigits => write!(f, "{}{:02}XX", sign, year / 100)?,
363 }
364 if let Some(month) = month {
365 let (m, mf) = month.unpack();
366 match mf.mask {
367 DMMask::None => write!(f, "-{:02}", m)?,
368 DMMask::Unspecified => write!(f, "-XX")?,
369 }
370 if let Some(day) = day {
371 let (d, df) = day.unpack();
372 match df.mask {
373 DMMask::None => write!(f, "-{:02}", d)?,
374 DMMask::Unspecified => write!(f, "-XX")?,
375 }
376 }
377 }
378 if let Some(cert) = match certainty {
379 Certainty::Certain => None,
380 Certainty::Uncertain => Some("?"),
381 Certainty::Approximate => Some("~"),
382 Certainty::ApproximateUncertain => Some("%"),
383 } {
384 write!(f, "{}", cert)?;
385 }
386 Ok(())
387 }
388}
389
390impl fmt::Debug for Date {
391 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392 write!(f, "{}", self)
393 }
394}
395
396impl fmt::Display for DateTime {
397 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
398 let DateComplete { year, month, day } = self.date;
399 let Time { hh, mm, ss, tz } = self.time;
400 write!(
401 f,
402 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
403 year, month, day, hh, mm, ss
404 )?;
405 match tz {
406 TzOffset::Unspecified => {}
407 TzOffset::Utc => write!(f, "Z")?,
408 TzOffset::Hours(h) => {
409 let off_h = h % 24;
410 write!(f, "{:+03}", off_h)?;
411 }
412 TzOffset::Minutes(min) => {
413 let off_m = (min.abs()) % 60;
414 let off_h = (min / 60) % 24;
415 write!(f, "{:+03}:{:02}", off_h, off_m)?;
416 }
417 }
418 Ok(())
419 }
420}
421
422impl fmt::Display for YYear {
423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424 write!(f, "Y{}", self.0)
425 }
426}
427
428impl fmt::Display for Terminal {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 match self {
431 Self::Open => write!(f, ".."),
432 Self::Unknown => Ok(()),
433 }
434 }
435}
436
437impl fmt::Display for Edtf {
438 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439 match self {
440 Self::Date(d) => write!(f, "{}", d),
441 Self::Interval(d, d2) => write!(f, "{}/{}", d, d2),
442 Self::IntervalFrom(d, t) => write!(f, "{}/{}", d, t),
443 Self::IntervalTo(t, d) => write!(f, "{}/{}", t, d),
444 Self::YYear(s) => write!(f, "{}", s),
445 Self::DateTime(dt) => write!(f, "{}", dt),
446 }
447 }
448}
449
450impl fmt::Debug for Edtf {
451 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452 <Self as fmt::Display>::fmt(self, f)
453 }
454}