pub const DATE_KIND: &str = "day.piece.datepicker";
pub const TIME_KIND: &str = "day.piece.timepicker";
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DayDate {
pub year: i32,
pub month: u8,
pub day: u8,
}
impl Default for DayDate {
fn default() -> Self {
DayDate {
year: 1970,
month: 1,
day: 1,
}
}
}
fn now_epoch_secs() -> i64 {
#[cfg(target_arch = "wasm32")]
{
unsafe { day_datetime_now_secs() as i64 }
}
#[cfg(not(target_arch = "wasm32"))]
{
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0)
}
}
#[cfg(target_arch = "wasm32")]
#[link(wasm_import_module = "env")]
unsafe extern "C" {
fn day_datetime_now_secs() -> f64;
}
impl DayDate {
pub fn new(year: i32, month: u8, day: u8) -> Option<Self> {
if (1..=12).contains(&month) && day >= 1 && day <= days_in_month(year, month) {
Some(DayDate { year, month, day })
} else {
None
}
}
pub fn today() -> Self {
Self::from_epoch_days(now_epoch_secs().div_euclid(86_400))
}
pub fn to_epoch_days(self) -> i64 {
let y = self.year as i64 - if self.month <= 2 { 1 } else { 0 };
let era = if y >= 0 { y } else { y - 399 } / 400;
let yoe = y - era * 400;
let m = self.month as i64;
let doy = (153 * (m + if m > 2 { -3 } else { 9 }) + 2) / 5 + self.day as i64 - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
era * 146097 + doe - 719468
}
pub fn from_epoch_days(days: i64) -> Self {
let z = days + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = z - era * 146097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = (doy - (153 * mp + 2) / 5 + 1) as u8;
let month = (if mp < 10 { mp + 3 } else { mp - 9 }) as u8;
DayDate {
year: (y + if month <= 2 { 1 } else { 0 }) as i32,
month,
day,
}
}
pub fn parse_iso(s: &str) -> Option<Self> {
let mut it = s.split('-');
let (y, m, d) = (it.next()?, it.next()?, it.next()?);
if it.next().is_some() || y.len() != 4 || m.len() != 2 || d.len() != 2 {
return None;
}
if [y, m, d]
.iter()
.any(|p| !p.bytes().all(|b| b.is_ascii_digit()))
{
return None;
}
Self::new(y.parse().ok()?, m.parse().ok()?, d.parse().ok()?)
}
pub fn clamped(self, min: Option<DayDate>, max: Option<DayDate>) -> Self {
let mut d = self;
if let Some(min) = min
&& d < min
{
d = min;
}
if let Some(max) = max
&& d > max
{
d = max;
}
d
}
}
impl std::fmt::Display for DayDate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
}
}
pub fn is_leap(year: i32) -> bool {
(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}
pub fn days_in_month(year: i32, month: u8) -> u8 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 if is_leap(year) => 29,
2 => 28,
_ => 0,
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DayTime {
pub hour: u8,
pub minute: u8,
pub second: u8,
}
impl DayTime {
pub fn new(hour: u8, minute: u8, second: u8) -> Option<Self> {
if hour < 24 && minute < 60 && second < 60 {
Some(DayTime {
hour,
minute,
second,
})
} else {
None
}
}
pub fn now() -> Self {
Self::from_seconds_of_day(now_epoch_secs().rem_euclid(86_400))
}
pub fn seconds_of_day(self) -> i64 {
self.hour as i64 * 3600 + self.minute as i64 * 60 + self.second as i64
}
pub fn from_seconds_of_day(secs: i64) -> Self {
let s = secs.rem_euclid(86_400);
DayTime {
hour: (s / 3600) as u8,
minute: (s / 60 % 60) as u8,
second: (s % 60) as u8,
}
}
pub fn parse_iso(s: &str) -> Option<Self> {
let mut it = s.split(':');
let (h, m) = (it.next()?, it.next()?);
let sec = it.next();
if it.next().is_some() || h.len() != 2 || m.len() != 2 {
return None;
}
if let Some(sec) = sec
&& sec.len() != 2
{
return None;
}
if [Some(h), Some(m), sec]
.iter()
.flatten()
.any(|p| !p.bytes().all(|b| b.is_ascii_digit()))
{
return None;
}
Self::new(
h.parse().ok()?,
m.parse().ok()?,
sec.map_or(Some(0), |s| s.parse().ok())?,
)
}
}
impl std::fmt::Display for DayTime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.second == 0 {
write!(f, "{:02}:{:02}", self.hour, self.minute)
} else {
write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Style {
#[default]
Automatic,
Compact,
Inline,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct DateProps {
pub date: DayDate,
pub style: Style,
pub min: Option<DayDate>,
pub max: Option<DayDate>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DatePatch {
SetDate(DayDate),
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct TimeProps {
pub time: DayTime,
pub style: Style,
pub seconds: bool,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TimePatch {
SetTime(DayTime),
}