#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))]
use crate::{js, sendwrap_fn, utils::js_value_from_to_string};
use cfg_if::cfg_if;
use chrono::{DateTime, TimeZone};
use default_struct_builder::DefaultBuilder;
use leptos::prelude::*;
use leptos::reactive::wrappers::read::Signal;
use std::fmt::Display;
use wasm_bindgen::JsValue;
pub fn use_intl_datetime_format(
options: UseIntlDateTimeFormatOptions,
) -> UseIntlDateTimeFormatReturn {
cfg_if! { if #[cfg(feature = "ssr")] {
UseIntlDateTimeFormatReturn
} else {
let datetime_format = js_sys::Intl::DateTimeFormat::new(
&js_sys::Array::from_iter(options.locales.iter().map(JsValue::from)),
&js_sys::Object::from(options),
);
UseIntlDateTimeFormatReturn {
js_intl_datetime_format: datetime_format,
}
}}
}
#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum FormatMatcher {
Basic,
#[default]
BestFit,
}
impl Display for FormatMatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Basic => write!(f, "basic"),
Self::BestFit => write!(f, "best fit"),
}
}
}
js_value_from_to_string!(FormatMatcher);
#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum LocaleMatcher {
#[default]
BestFit,
Lookup,
}
impl Display for LocaleMatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BestFit => write!(f, "best fit"),
Self::Lookup => write!(f, "lookup"),
}
}
}
js_value_from_to_string!(LocaleMatcher);
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum HourCycle {
H11,
H12,
H23,
H24,
}
impl Display for HourCycle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::H11 => write!(f, "h11"),
Self::H12 => write!(f, "h12"),
Self::H23 => write!(f, "h23"),
Self::H24 => write!(f, "h24"),
}
}
}
js_value_from_to_string!(HourCycle);
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum DateTimeStyle {
Full,
Long,
Medium,
Short,
}
impl Display for DateTimeStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Full => write!(f, "full"),
Self::Long => write!(f, "long"),
Self::Medium => write!(f, "medium"),
Self::Short => write!(f, "short"),
}
}
}
js_value_from_to_string!(DateTimeStyle);
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum WeekdayFormat {
Long,
Short,
Narrow,
}
impl Display for WeekdayFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Long => write!(f, "long"),
Self::Short => write!(f, "short"),
Self::Narrow => write!(f, "narrow"),
}
}
}
js_value_from_to_string!(WeekdayFormat);
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum EraFormat {
Long,
Short,
Narrow,
}
impl Display for EraFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Long => write!(f, "long"),
Self::Short => write!(f, "short"),
Self::Narrow => write!(f, "narrow"),
}
}
}
js_value_from_to_string!(EraFormat);
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum YearFormat {
Numeric,
TwoDigit,
}
impl Display for YearFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Numeric => write!(f, "numeric"),
Self::TwoDigit => write!(f, "2-digit"),
}
}
}
js_value_from_to_string!(YearFormat);
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum MonthFormat {
Numeric,
TwoDigit,
Long,
Short,
Narrow,
}
impl Display for MonthFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Numeric => write!(f, "numeric"),
Self::TwoDigit => write!(f, "2-digit"),
Self::Long => write!(f, "long"),
Self::Short => write!(f, "short"),
Self::Narrow => write!(f, "narrow"),
}
}
}
js_value_from_to_string!(MonthFormat);
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum DayFormat {
Numeric,
TwoDigit,
}
impl Display for DayFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Numeric => write!(f, "numeric"),
Self::TwoDigit => write!(f, "2-digit"),
}
}
}
js_value_from_to_string!(DayFormat);
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum TimeNumericFormat {
Numeric,
TwoDigit,
}
impl Display for TimeNumericFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Numeric => write!(f, "numeric"),
Self::TwoDigit => write!(f, "2-digit"),
}
}
}
js_value_from_to_string!(TimeNumericFormat);
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum DayPeriodFormat {
Long,
Short,
Narrow,
}
impl Display for DayPeriodFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Long => write!(f, "long"),
Self::Short => write!(f, "short"),
Self::Narrow => write!(f, "narrow"),
}
}
}
js_value_from_to_string!(DayPeriodFormat);
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum TimeZoneNameFormat {
Long,
Short,
ShortOffset,
LongOffset,
ShortGeneric,
LongGeneric,
}
impl Display for TimeZoneNameFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Long => write!(f, "long"),
Self::Short => write!(f, "short"),
Self::ShortOffset => write!(f, "shortOffset"),
Self::LongOffset => write!(f, "longOffset"),
Self::ShortGeneric => write!(f, "shortGeneric"),
Self::LongGeneric => write!(f, "longGeneric"),
}
}
}
js_value_from_to_string!(TimeZoneNameFormat);
#[derive(DefaultBuilder, Default)]
pub struct UseIntlDateTimeFormatOptions {
locales: Vec<String>,
locale_matcher: LocaleMatcher,
#[builder(into)]
calendar: Option<String>,
#[builder(into)]
day_period: Option<DayPeriodFormat>,
#[builder(into)]
numbering_system: Option<String>,
#[builder(into)]
hour12: Option<bool>,
#[builder(into)]
hour_cycle: Option<HourCycle>,
#[builder(into)]
time_zone: Option<String>,
format_matcher: FormatMatcher,
#[builder(into)]
weekday: Option<WeekdayFormat>,
#[builder(into)]
era: Option<EraFormat>,
#[builder(into)]
year: Option<YearFormat>,
#[builder(into)]
month: Option<MonthFormat>,
#[builder(into)]
day: Option<DayFormat>,
#[builder(into)]
hour: Option<TimeNumericFormat>,
#[builder(into)]
minute: Option<TimeNumericFormat>,
#[builder(into)]
second: Option<TimeNumericFormat>,
#[builder(into)]
fractional_second_digits: Option<u8>,
#[builder(into)]
time_zone_name: Option<TimeZoneNameFormat>,
#[builder(into)]
date_style: Option<DateTimeStyle>,
#[builder(into)]
time_style: Option<DateTimeStyle>,
}
impl UseIntlDateTimeFormatOptions {
pub fn locale(self, locale: &str) -> Self {
Self {
locales: vec![locale.to_string()],
..self
}
}
}
impl From<UseIntlDateTimeFormatOptions> for js_sys::Object {
fn from(options: UseIntlDateTimeFormatOptions) -> Self {
let object = Self::new();
_ = js!(object["localeMatcher"] = options.locale_matcher);
_ = js!(object["formatMatcher"] = options.format_matcher);
if let Some(calendar) = options.calendar {
_ = js!(object["calendar"] = calendar);
}
if let Some(day_period) = options.day_period {
_ = js!(object["dayPeriod"] = day_period);
}
if let Some(numbering_system) = options.numbering_system {
_ = js!(object["numberingSystem"] = numbering_system);
}
if let Some(hour12) = options.hour12 {
_ = js!(object["hour12"] = hour12);
}
if let Some(hour_cycle) = options.hour_cycle {
_ = js!(object["hourCycle"] = hour_cycle);
}
if let Some(time_zone) = options.time_zone {
_ = js!(object["timeZone"] = time_zone);
}
if let Some(weekday) = options.weekday {
_ = js!(object["weekday"] = weekday);
}
if let Some(era) = options.era {
_ = js!(object["era"] = era);
}
if let Some(year) = options.year {
_ = js!(object["year"] = year);
}
if let Some(month) = options.month {
_ = js!(object["month"] = month);
}
if let Some(day) = options.day {
_ = js!(object["day"] = day);
}
if let Some(hour) = options.hour {
_ = js!(object["hour"] = hour);
}
if let Some(minute) = options.minute {
_ = js!(object["minute"] = minute);
}
if let Some(second) = options.second {
_ = js!(object["second"] = second);
}
if let Some(fractional_second_digits) = options.fractional_second_digits {
_ = js!(object["fractionalSecondDigits"] = fractional_second_digits);
}
if let Some(time_zone_name) = options.time_zone_name {
_ = js!(object["timeZoneName"] = time_zone_name);
}
if let Some(date_style) = options.date_style {
_ = js!(object["dateStyle"] = date_style);
}
if let Some(time_style) = options.time_style {
_ = js!(object["timeStyle"] = time_style);
}
object
}
}
cfg_if! { if #[cfg(feature = "ssr")] {
pub struct UseIntlDateTimeFormatReturn;
} else {
pub struct UseIntlDateTimeFormatReturn {
pub js_intl_datetime_format: js_sys::Intl::DateTimeFormat,
}
}}
#[cfg(not(feature = "ssr"))]
fn datetime_to_js_date<Tz>(date: &DateTime<Tz>) -> js_sys::Date
where
Tz: TimeZone,
{
js_sys::Date::new(&JsValue::from_f64(date.timestamp_millis() as f64))
}
impl UseIntlDateTimeFormatReturn {
pub fn format<Tz>(&self, date: impl Into<Signal<DateTime<Tz>>>) -> Signal<String>
where
Tz: TimeZone,
DateTime<Tz>: Clone + Display + Send + Sync + 'static,
{
let date = date.into();
cfg_if! { if #[cfg(feature = "ssr")] {
Signal::derive(move || {
format!("{}", date.get())
})
} else {
let datetime_format = self.js_intl_datetime_format.clone();
Signal::derive(sendwrap_fn!(move || {
if let Ok(result) = datetime_format
.format()
.call1(&datetime_format, &datetime_to_js_date(&date.get()).into())
{
result.as_string().unwrap_or_default()
} else {
"".to_string()
}
}))
}}
}
pub fn format_range<TzStart, TzEnd>(
&self,
start: impl Into<Signal<DateTime<TzStart>>>,
end: impl Into<Signal<DateTime<TzEnd>>>,
) -> Signal<String>
where
TzStart: TimeZone,
TzEnd: TimeZone,
DateTime<TzStart>: Clone + Display + Send + Sync + 'static,
DateTime<TzEnd>: Clone + Display + Send + Sync + 'static,
{
let start = start.into();
let end = end.into();
cfg_if! { if #[cfg(feature = "ssr")] {
Signal::derive(move || {
format!("{} - {}", start.get(), end.get())
})
} else {
let datetime_format = self.js_intl_datetime_format.clone();
Signal::derive(sendwrap_fn!(move || {
datetime_format
.format_range(
&datetime_to_js_date(&start.get()),
&datetime_to_js_date(&end.get()),
)
.map(|s| s.as_string().unwrap_or_default())
.unwrap_or_default()
}))
}}
}
}