#![doc = include_str!("README.md")]
pub(crate) mod packed;
mod parser;
mod validate;
mod basic;
pub mod iter;
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
#[cfg(feature = "chrono")]
mod sorting;
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
#[cfg(feature = "chrono")]
pub use sorting::{SortOrder, SortOrderEnd, SortOrderStart};
#[cfg(test)]
mod test;
use core::convert::TryInto;
use core::fmt;
use core::num::NonZeroU8;
use core::str::FromStr;
use crate::helpers;
use crate::{DateComplete, DateTime, ParseError, Time, TzOffset};
use self::{
packed::{DMMask, PackedInt, PackedU8, PackedYear, YearMask},
parser::{ParsedEdtf, UnvalidatedDate},
};
pub use packed::Certainty;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Date {
pub(crate) year: PackedYear,
pub(crate) month: Option<PackedU8>,
pub(crate) day: Option<PackedU8>,
pub(crate) certainty: Certainty,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum Edtf {
DateTime(DateTime),
Date(Date),
YYear(YYear),
Interval(Date, Date),
IntervalFrom(Date, Terminal),
IntervalTo(Terminal, Date),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Terminal {
Unknown,
Open,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Season {
Spring = 21,
Summer = 22,
Autumn = 23,
Winter = 24,
}
impl Season {
fn from_u32(value: u32) -> Self {
match value {
21 => Self::Spring,
22 => Self::Summer,
23 => Self::Autumn,
24 => Self::Winter,
_ => panic!("invalid season number {}", value),
}
}
fn from_u32_opt(value: u32) -> Option<Self> {
Some(match value {
21 => Self::Spring,
22 => Self::Summer,
23 => Self::Autumn,
24 => Self::Winter,
_ => return None,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Precision {
Century(i32),
Decade(i32),
Year(i32),
Season(i32, Season),
Month(i32, u32),
Day(i32, u32, u32),
MonthOfYear(i32),
DayOfYear(i32),
DayOfMonth(i32, u32),
}
#[doc = include_str!("YYear.md")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct YYear(i64);
impl YYear {
pub fn year(&self) -> i64 {
self.0
}
pub fn value(self) -> i64 {
self.0
}
pub(crate) fn raw(y: i64) -> Self {
Self(y)
}
pub fn new(value: i64) -> Self {
Self::new_opt(value).expect("value outside range for YYear, must be 5-digit year")
}
pub fn new_opt(value: i64) -> Option<Self> {
if helpers::inside_9999(value) {
return None;
}
Some(Self(value))
}
pub fn new_or_cal(value: i64) -> Result<Self, Edtf> {
if helpers::inside_9999(value) {
let date = value
.try_into()
.ok()
.and_then(|y| Date::from_ymd_opt(y, 0, 0))
.map(Edtf::Date)
.expect("should have already validated as within -9999..=9999");
return Err(date);
}
Ok(Self(value))
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum YearDigits {
NoX,
X,
XX,
}
#[doc(hidden)]
impl From<YearMask> for YearDigits {
fn from(ym: YearMask) -> Self {
match ym {
YearMask::None => Self::NoX,
YearMask::OneDigit => Self::X,
YearMask::TwoDigits => Self::XX,
}
}
}
#[doc(hidden)]
impl From<YearDigits> for YearMask {
fn from(ym: YearDigits) -> Self {
match ym {
YearDigits::NoX => YearMask::None,
YearDigits::X => YearMask::OneDigit,
YearDigits::XX => YearMask::TwoDigits,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Component {
Value(u32),
Unspecified,
}
impl fmt::Display for Component {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Component::Value(val) => val.fmt(f),
Component::Unspecified => {
let precision = f.width().unwrap_or(1);
write!(f, "{:X<1$}", "", precision)
}
}
}
}
impl Component {
pub fn value(self) -> Option<u32> {
match self {
Component::Value(v) => Some(v),
Component::Unspecified => None,
}
}
fn from_packed_filter(packed: PackedU8, range: std::ops::RangeInclusive<u32>) -> Option<Self> {
let (val, flags) = packed.unpack();
let val = val as u32;
if flags.is_masked() {
Some(Component::Unspecified)
} else if range.contains(&val) {
Some(Component::Value(val as u32))
} else {
None
}
}
fn from_packed(packed: PackedU8) -> Self {
let (val, flags) = packed.unpack();
if flags.is_masked() {
Component::Unspecified
} else {
Component::Value(val as u32)
}
}
}
impl From<Date> for Edtf {
fn from(date: Date) -> Self {
Self::Date(date)
}
}
impl From<(Date, Date)> for Edtf {
fn from((a, b): (Date, Date)) -> Self {
Self::Interval(a, b)
}
}
impl FromStr for Edtf {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Edtf::parse(s)
}
}
impl fmt::Display for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Date {
year,
month,
day,
certainty,
} = *self;
let (year, yf) = year.unpack();
let sign = helpers::sign_str_if_neg(year);
let year = year.abs();
match yf.mask {
YearMask::None => write!(f, "{}{:04}", sign, year)?,
YearMask::OneDigit => write!(f, "{}{:03}X", sign, year / 10)?,
YearMask::TwoDigits => write!(f, "{}{:02}XX", sign, year / 100)?,
}
if let Some(month) = month {
let (m, mf) = month.unpack();
match mf.mask {
DMMask::None => write!(f, "-{:02}", m)?,
DMMask::Unspecified => write!(f, "-XX")?,
}
if let Some(day) = day {
let (d, df) = day.unpack();
match df.mask {
DMMask::None => write!(f, "-{:02}", d)?,
DMMask::Unspecified => write!(f, "-XX")?,
}
}
}
if let Some(cert) = match certainty {
Certainty::Certain => None,
Certainty::Uncertain => Some("?"),
Certainty::Approximate => Some("~"),
Certainty::ApproximateUncertain => Some("%"),
} {
write!(f, "{}", cert)?;
}
Ok(())
}
}
impl fmt::Debug for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
}
}
impl fmt::Display for DateTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let DateComplete { year, month, day } = self.date;
let Time { hh, mm, ss, tz } = self.time;
write!(
f,
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
year, month, day, hh, mm, ss
)?;
match tz {
TzOffset::Unspecified => {}
TzOffset::Utc => write!(f, "Z")?,
TzOffset::Hours(h) => {
let off_h = h % 24;
write!(f, "{:+03}", off_h)?;
}
TzOffset::Minutes(min) => {
let off_m = (min.abs()) % 60;
let off_h = (min / 60) % 24;
write!(f, "{:+03}:{:02}", off_h, off_m)?;
}
}
Ok(())
}
}
impl fmt::Display for YYear {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Y{}", self.0)
}
}
impl fmt::Display for Terminal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Open => write!(f, ".."),
Self::Unknown => Ok(()),
}
}
}
impl fmt::Display for Edtf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Date(d) => write!(f, "{}", d),
Self::Interval(d, d2) => write!(f, "{}/{}", d, d2),
Self::IntervalFrom(d, t) => write!(f, "{}/{}", d, t),
Self::IntervalTo(t, d) => write!(f, "{}/{}", t, d),
Self::YYear(s) => write!(f, "{}", s),
Self::DateTime(dt) => write!(f, "{}", dt),
}
}
}
impl fmt::Debug for Edtf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<Self as fmt::Display>::fmt(self, f)
}
}