use icu_locid::Locale;
use std::convert::TryFrom;
use std::fmt;
use std::ops::{Add, Sub};
use std::str::FromStr;
use tinystr::TinyStr8;
#[derive(Debug)]
pub enum DateTimeError {
Parse(std::num::ParseIntError),
Overflow { field: &'static str, max: usize },
Underflow { field: &'static str, min: isize },
InvalidTimeZoneOffset,
}
impl fmt::Display for DateTimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Parse(err) => write!(f, "{}", err),
Self::Overflow { field, max } => write!(f, "{} must be between 0-{}", field, max),
Self::Underflow { field, min } => write!(f, "{} must be between {}-0", field, min),
Self::InvalidTimeZoneOffset => write!(f, "Failed to parse time-zone offset"),
}
}
}
impl From<std::num::ParseIntError> for DateTimeError {
fn from(input: std::num::ParseIntError) -> Self {
Self::Parse(input)
}
}
pub trait DateInput {
fn year(&self) -> Option<Year>;
fn month(&self) -> Option<Month>;
fn day_of_month(&self) -> Option<DayOfMonth>;
fn iso_weekday(&self) -> Option<IsoWeekday>;
fn day_of_year_info(&self) -> Option<DayOfYearInfo>;
}
pub trait IsoTimeInput {
fn hour(&self) -> Option<IsoHour>;
fn minute(&self) -> Option<IsoMinute>;
fn second(&self) -> Option<IsoSecond>;
fn fraction(&self) -> Option<FractionalSecond>;
}
pub trait TimeZoneInput {
fn gmt_offset(&self) -> GmtOffset;
fn time_zone_id(&self) -> Option<&str>;
fn metazone_id(&self) -> Option<&str>;
fn time_variant(&self) -> Option<&str>;
}
pub trait DateTimeInput: DateInput + IsoTimeInput {}
pub trait ZonedDateTimeInput: TimeZoneInput + DateTimeInput {}
impl<T> DateTimeInput for T where T: DateInput + IsoTimeInput {}
impl<T> ZonedDateTimeInput for T where T: TimeZoneInput + DateTimeInput {}
pub trait LocalizedDateTimeInput<T: DateTimeInput> {
fn datetime(&self) -> &T;
fn year_week(&self) -> Year;
fn week_of_month(&self) -> WeekOfMonth;
fn week_of_year(&self) -> WeekOfYear;
fn flexible_day_period(&self);
}
pub(crate) struct DateTimeInputWithLocale<'s, T: DateTimeInput> {
data: &'s T,
_first_weekday: u8,
_anchor_weekday: u8,
}
impl<'s, T: DateTimeInput> DateTimeInputWithLocale<'s, T> {
pub fn new(data: &'s T, _locale: &Locale) -> Self {
Self {
data,
_first_weekday: 1,
_anchor_weekday: 4,
}
}
}
pub(crate) struct ZonedDateTimeInputWithLocale<'s, T: ZonedDateTimeInput> {
data: &'s T,
_first_weekday: u8,
_anchor_weekday: u8,
}
impl<'s, T: ZonedDateTimeInput> ZonedDateTimeInputWithLocale<'s, T> {
pub fn new(data: &'s T, _locale: &Locale) -> Self {
Self {
data,
_first_weekday: 1,
_anchor_weekday: 4,
}
}
}
impl<'s, T: DateTimeInput> LocalizedDateTimeInput<T> for DateTimeInputWithLocale<'s, T> {
fn datetime(&self) -> &T {
self.data
}
fn year_week(&self) -> Year {
todo!("#488")
}
fn week_of_month(&self) -> WeekOfMonth {
todo!("#488")
}
fn week_of_year(&self) -> WeekOfYear {
todo!("#488")
}
fn flexible_day_period(&self) {
todo!("#487")
}
}
impl<'s, T: ZonedDateTimeInput> LocalizedDateTimeInput<T> for ZonedDateTimeInputWithLocale<'s, T> {
fn datetime(&self) -> &T {
self.data
}
fn year_week(&self) -> Year {
todo!("#488")
}
fn week_of_month(&self) -> WeekOfMonth {
todo!("#488")
}
fn week_of_year(&self) -> WeekOfYear {
todo!("#488")
}
fn flexible_day_period(&self) {
todo!("#487")
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Era(pub TinyStr8);
#[derive(Clone, Debug, PartialEq)]
pub struct Year {
pub era: Era,
pub number: i32,
pub related_iso: i32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct MonthCode(pub TinyStr8);
#[derive(Clone, Debug, PartialEq)]
pub struct Month {
pub number: u32,
pub code: MonthCode,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DayOfYearInfo {
pub day_of_year: u32,
pub days_in_year: u32,
pub prev_year: Year,
pub next_year: Year,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i8)]
pub enum IsoWeekday {
Monday = 1,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
impl From<usize> for IsoWeekday {
fn from(input: usize) -> Self {
let mut ordinal = (input % 7) as i8;
if ordinal == 0 {
ordinal = 7;
}
unsafe { std::mem::transmute(ordinal) }
}
}
pub struct DayOfMonth(pub u32);
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct WeekOfMonth(pub u32);
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct WeekOfYear(pub u32);
macro_rules! dt_unit {
($name:ident, $value:expr) => {
#[derive(Debug, Default, Clone, Copy, PartialEq, Hash)]
pub struct $name(u8);
impl $name {
pub const fn new_unchecked(input: u8) -> Self {
Self(input)
}
}
impl FromStr for $name {
type Err = DateTimeError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let val: u8 = input.parse()?;
if val > $value {
Err(DateTimeError::Overflow {
field: "$name",
max: $value,
})
} else {
Ok(Self(val))
}
}
}
impl TryFrom<u8> for $name {
type Error = DateTimeError;
fn try_from(input: u8) -> Result<Self, Self::Error> {
if input > $value {
Err(DateTimeError::Overflow {
field: "$name",
max: $value,
})
} else {
Ok(Self(input))
}
}
}
impl TryFrom<usize> for $name {
type Error = DateTimeError;
fn try_from(input: usize) -> Result<Self, Self::Error> {
if input > $value {
Err(DateTimeError::Overflow {
field: "$name",
max: $value,
})
} else {
Ok(Self(input as u8))
}
}
}
impl From<$name> for u8 {
fn from(input: $name) -> Self {
input.0
}
}
impl From<$name> for usize {
fn from(input: $name) -> Self {
input.0 as Self
}
}
impl Add<u8> for $name {
type Output = Self;
fn add(self, other: u8) -> Self {
Self(self.0 + other)
}
}
impl Sub<u8> for $name {
type Output = Self;
fn sub(self, other: u8) -> Self {
Self(self.0 - other)
}
}
};
}
dt_unit!(IsoHour, 24);
dt_unit!(IsoMinute, 60);
dt_unit!(IsoSecond, 61);
#[derive(Clone, Debug, PartialEq)]
pub enum FractionalSecond {
Millisecond(u16),
Microsecond(u32),
Nanosecond(u32),
}
#[derive(Copy, Clone, Debug, Default)]
pub struct GmtOffset(i32);
impl GmtOffset {
pub fn try_new(seconds: i32) -> Result<Self, DateTimeError> {
if seconds < -(12 * 60 * 60) {
Err(DateTimeError::Underflow {
field: "GmtOffset",
min: -(12 * 60 * 60),
})
} else if seconds > (14 * 60 * 60) {
Err(DateTimeError::Overflow {
field: "GmtOffset",
max: (14 * 60 * 60),
})
} else {
Ok(Self(seconds))
}
}
pub fn raw_offset_seconds(&self) -> i32 {
self.0
}
pub fn is_positive(&self) -> bool {
self.0 >= 0
}
pub fn is_zero(&self) -> bool {
self.0 == 0
}
pub fn has_minutes(&self) -> bool {
self.0 % 3600 / 60 > 0
}
pub fn has_seconds(&self) -> bool {
self.0 % 3600 % 60 > 0
}
}
impl FromStr for GmtOffset {
type Err = DateTimeError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let offset_sign;
match input.chars().next() {
Some('+') => offset_sign = 1,
Some('-') => offset_sign = -1,
Some('−') => offset_sign = -1,
Some('Z') => return Ok(Self(0)),
_ => return Err(DateTimeError::InvalidTimeZoneOffset),
};
let seconds = match input.chars().count() {
3 => {
let hour: u8 = input[1..3].parse()?;
offset_sign * (hour as i32 * 60 * 60)
}
5 => {
let hour: u8 = input[1..3].parse()?;
let minute: u8 = input[3..5].parse()?;
offset_sign * (hour as i32 * 60 * 60 + minute as i32 * 60)
}
6 => {
let hour: u8 = input[1..3].parse()?;
let minute: u8 = input[4..6].parse()?;
offset_sign * (hour as i32 * 60 * 60 + minute as i32 * 60)
}
_ => panic!("Invalid time-zone designator"),
};
Self::try_new(seconds)
}
}