Skip to main content

leptos_use/
use_intl_datetime_format.rs

1#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))]
2
3use crate::{js, sendwrap_fn, utils::js_value_from_to_string};
4use cfg_if::cfg_if;
5use chrono::{DateTime, TimeZone};
6use default_struct_builder::DefaultBuilder;
7use leptos::prelude::*;
8use leptos::reactive::wrappers::read::Signal;
9use std::fmt::Display;
10use wasm_bindgen::JsValue;
11
12/// Reactive [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat).
13///
14/// ## Demo
15///
16/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_intl_datetime_format)
17///
18/// ## Usage
19///
20/// Dates are passed in as [`chrono::DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html)
21/// (just like in [`fn@crate::use_calendar`]).
22///
23/// In basic use without specifying a locale, a formatted string in the default locale and with default
24/// options is returned.
25///
26/// ```
27/// # use leptos::prelude::*;
28/// # use leptos_use::{use_intl_datetime_format, UseIntlDateTimeFormatOptions};
29/// # use chrono::{TimeZone, Utc};
30/// #
31/// # #[component]
32/// # fn Demo() -> impl IntoView {
33/// let (date, set_date) = signal(Utc.with_ymd_and_hms(2020, 12, 20, 3, 23, 16).unwrap());
34///
35/// let date_time_format = use_intl_datetime_format(UseIntlDateTimeFormatOptions::default());
36///
37/// let formatted = date_time_format.format(date); // "12/20/2020" if in US English locale
38/// #
39/// # view! { }
40/// # }
41/// ```
42///
43/// ## Using locales
44///
45/// In order to get the format of the language used in the user interface of your application, make
46/// sure to specify that language (and possibly some fallback languages) using the `locales` argument:
47///
48/// ```
49/// # use leptos::prelude::*;
50/// # use leptos_use::{use_intl_datetime_format, UseIntlDateTimeFormatOptions};
51/// # use chrono::{TimeZone, Utc};
52/// #
53/// # #[component]
54/// # fn Demo() -> impl IntoView {
55/// let date = Utc.with_ymd_and_hms(2012, 12, 20, 3, 0, 0).unwrap();
56///
57/// // British English uses day-month-year order and 24-hour time without AM/PM
58/// let date_time_format = use_intl_datetime_format(
59///     UseIntlDateTimeFormatOptions::default().locale("en-GB"),
60/// );
61/// let formatted = date_time_format.format(date); // 20/12/2012
62///
63/// // Korean uses year-month-day order
64/// let date_time_format = use_intl_datetime_format(
65///     UseIntlDateTimeFormatOptions::default().locale("ko-KR"),
66/// );
67/// let formatted = date_time_format.format(date); // 2012. 12. 20.
68///
69/// // Arabic in most Arabic speaking countries uses real Arabic digits
70/// let date_time_format = use_intl_datetime_format(
71///     UseIntlDateTimeFormatOptions::default().locale("ar-EG"),
72/// );
73/// let formatted = date_time_format.format(date); // ٢٠‏/١٢‏/٢٠١٢
74///
75/// // when requesting a language that may not be supported, such as
76/// // Balinese, include a fallback language, in this case Indonesian
77/// let date_time_format = use_intl_datetime_format(
78///     UseIntlDateTimeFormatOptions::default()
79///         .locales(vec!["ban".to_string(), "id".to_string()]),
80/// );
81/// let formatted = date_time_format.format(date); // 20/12/2012
82/// #
83/// # view! { }
84/// # }
85/// ```
86///
87/// ## Using options
88///
89/// The results can be customized in multiple ways.
90///
91/// ```
92/// # use leptos::prelude::*;
93/// # use leptos_use::{
94/// #     use_intl_datetime_format, UseIntlDateTimeFormatOptions, WeekdayFormat, EraFormat,
95/// #     YearFormat, MonthFormat, DayFormat, TimeNumericFormat, TimeZoneNameFormat,
96/// # };
97/// # use chrono::{TimeZone, Utc};
98/// #
99/// # #[component]
100/// # fn Demo() -> impl IntoView {
101/// let date = Utc.with_ymd_and_hms(2012, 12, 20, 3, 0, 0).unwrap();
102///
103/// // request a weekday along with a long date
104/// let date_time_format = use_intl_datetime_format(
105///     UseIntlDateTimeFormatOptions::default()
106///         .locale("en-US")
107///         .weekday(WeekdayFormat::Long)
108///         .year(YearFormat::Numeric)
109///         .month(MonthFormat::Long)
110///         .day(DayFormat::Numeric),
111/// );
112/// let formatted = date_time_format.format(date); // "Thursday, December 20, 2012"
113///
114/// // an application may want to use UTC and make that visible
115/// let date_time_format = use_intl_datetime_format(
116///     UseIntlDateTimeFormatOptions::default()
117///         .locale("en-US")
118///         .hour(TimeNumericFormat::Numeric)
119///         .minute(TimeNumericFormat::TwoDigit)
120///         .second(TimeNumericFormat::TwoDigit)
121///         .time_zone("UTC")
122///         .time_zone_name(TimeZoneNameFormat::Short),
123/// );
124/// let formatted = date_time_format.format(date); // "3:00:00 AM UTC"
125/// #
126/// # view! { }
127/// # }
128/// ```
129///
130/// Instead of specifying the individual date and time components you can use the
131/// [`UseIntlDateTimeFormatOptions::date_style`] and [`UseIntlDateTimeFormatOptions::time_style`]
132/// shortcuts.
133///
134/// ```
135/// # use leptos::prelude::*;
136/// # use leptos_use::{use_intl_datetime_format, UseIntlDateTimeFormatOptions, DateTimeStyle};
137/// # use chrono::{TimeZone, Utc};
138/// #
139/// # #[component]
140/// # fn Demo() -> impl IntoView {
141/// let date = Utc.with_ymd_and_hms(2012, 12, 20, 3, 0, 0).unwrap();
142///
143/// let date_time_format = use_intl_datetime_format(
144///     UseIntlDateTimeFormatOptions::default()
145///         .locale("en-GB")
146///         .date_style(DateTimeStyle::Full)
147///         .time_style(DateTimeStyle::Long)
148///         .time_zone("UTC"),
149/// );
150/// let formatted = date_time_format.format(date); // "Thursday 20 December 2012 at 03:00:00 UTC"
151/// #
152/// # view! { }
153/// # }
154/// ```
155///
156/// > Note that `date_style` and `time_style` cannot be mixed with the individual date-time component
157/// > options (like `weekday`, `year`, `hour`, ...). Doing so makes `Intl.DateTimeFormat` throw a
158/// > `RangeError` which results in an empty string being returned.
159///
160/// For an exhaustive list of options see [`UseIntlDateTimeFormatOptions`](https://docs.rs/leptos_use/latest/leptos_use/struct.UseIntlDateTimeFormatOptions.html).
161///
162/// ## Formatting ranges
163///
164/// Apart from the `format` method, the `format_range` method can be used to format a range of dates.
165/// Please see [`UseIntlDateTimeFormatReturn::format_range`](https://docs.rs/leptos_use/latest/leptos_use/struct.UseIntlDateTimeFormatReturn.html#method.format_range)
166/// for details.
167///
168/// ## Server-Side Rendering
169///
170/// > Make sure you follow the [instructions in Server-Side Rendering](https://leptos-use.rs/server_side_rendering.html).
171///
172/// Since `Intl.DateTimeFormat` is a JavaScript API it is not available on the server. That's why
173/// it falls back to a simple call to `format!()` on the server, which is **not** locale-aware.
174pub fn use_intl_datetime_format(
175    options: UseIntlDateTimeFormatOptions,
176) -> UseIntlDateTimeFormatReturn {
177    cfg_if! { if #[cfg(feature = "ssr")] {
178        UseIntlDateTimeFormatReturn
179    } else {
180        let datetime_format = js_sys::Intl::DateTimeFormat::new(
181            &js_sys::Array::from_iter(options.locales.iter().map(JsValue::from)),
182            &js_sys::Object::from(options),
183        );
184
185        UseIntlDateTimeFormatReturn {
186            js_intl_datetime_format: datetime_format,
187        }
188    }}
189}
190
191#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
192pub enum FormatMatcher {
193    Basic,
194    #[default]
195    BestFit,
196}
197
198impl Display for FormatMatcher {
199    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200        match self {
201            Self::Basic => write!(f, "basic"),
202            Self::BestFit => write!(f, "best fit"),
203        }
204    }
205}
206
207js_value_from_to_string!(FormatMatcher);
208
209#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)]
210pub enum LocaleMatcher {
211    #[default]
212    BestFit,
213    Lookup,
214}
215
216impl Display for LocaleMatcher {
217    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218        match self {
219            Self::BestFit => write!(f, "best fit"),
220            Self::Lookup => write!(f, "lookup"),
221        }
222    }
223}
224
225js_value_from_to_string!(LocaleMatcher);
226
227#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
228pub enum HourCycle {
229    /// hour system using 0–11; corresponds to midnight starting at 0:00 AM.
230    H11,
231    /// hour system using 1–12; corresponds to midnight starting at 12:00 AM.
232    H12,
233    /// hour system using 0–23; corresponds to midnight starting at 0:00.
234    H23,
235    /// hour system using 1–24; corresponds to midnight starting at 24:00.
236    H24,
237}
238
239impl Display for HourCycle {
240    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241        match self {
242            Self::H11 => write!(f, "h11"),
243            Self::H12 => write!(f, "h12"),
244            Self::H23 => write!(f, "h23"),
245            Self::H24 => write!(f, "h24"),
246        }
247    }
248}
249
250js_value_from_to_string!(HourCycle);
251
252#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
253pub enum DateTimeStyle {
254    Full,
255    Long,
256    Medium,
257    Short,
258}
259
260impl Display for DateTimeStyle {
261    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262        match self {
263            Self::Full => write!(f, "full"),
264            Self::Long => write!(f, "long"),
265            Self::Medium => write!(f, "medium"),
266            Self::Short => write!(f, "short"),
267        }
268    }
269}
270
271js_value_from_to_string!(DateTimeStyle);
272
273#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
274pub enum WeekdayFormat {
275    /// e.g., `Thursday`
276    Long,
277    /// e.g., `Thu`
278    Short,
279    /// e.g., `T`. Note that two weekdays may have the same narrow style for some locales (e.g. `Tuesday`'s narrow style is also `T`).
280    Narrow,
281}
282
283impl Display for WeekdayFormat {
284    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285        match self {
286            Self::Long => write!(f, "long"),
287            Self::Short => write!(f, "short"),
288            Self::Narrow => write!(f, "narrow"),
289        }
290    }
291}
292
293js_value_from_to_string!(WeekdayFormat);
294
295#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
296pub enum EraFormat {
297    /// e.g., `Anno Domini`
298    Long,
299    /// e.g., `AD`
300    Short,
301    /// e.g., `A`
302    Narrow,
303}
304
305impl Display for EraFormat {
306    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307        match self {
308            Self::Long => write!(f, "long"),
309            Self::Short => write!(f, "short"),
310            Self::Narrow => write!(f, "narrow"),
311        }
312    }
313}
314
315js_value_from_to_string!(EraFormat);
316
317#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
318pub enum YearFormat {
319    /// e.g., `2012`
320    Numeric,
321    /// e.g., `12`
322    TwoDigit,
323}
324
325impl Display for YearFormat {
326    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327        match self {
328            Self::Numeric => write!(f, "numeric"),
329            Self::TwoDigit => write!(f, "2-digit"),
330        }
331    }
332}
333
334js_value_from_to_string!(YearFormat);
335
336#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
337pub enum MonthFormat {
338    /// e.g., `2`
339    Numeric,
340    /// e.g., `02`
341    TwoDigit,
342    /// e.g., `March`
343    Long,
344    /// e.g., `Mar`
345    Short,
346    /// e.g., `M`
347    Narrow,
348}
349
350impl Display for MonthFormat {
351    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352        match self {
353            Self::Numeric => write!(f, "numeric"),
354            Self::TwoDigit => write!(f, "2-digit"),
355            Self::Long => write!(f, "long"),
356            Self::Short => write!(f, "short"),
357            Self::Narrow => write!(f, "narrow"),
358        }
359    }
360}
361
362js_value_from_to_string!(MonthFormat);
363
364#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
365pub enum DayFormat {
366    /// e.g., `1`
367    Numeric,
368    /// e.g., `01`
369    TwoDigit,
370}
371
372impl Display for DayFormat {
373    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374        match self {
375            Self::Numeric => write!(f, "numeric"),
376            Self::TwoDigit => write!(f, "2-digit"),
377        }
378    }
379}
380
381js_value_from_to_string!(DayFormat);
382
383#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
384pub enum TimeNumericFormat {
385    /// e.g., `1`
386    Numeric,
387    /// e.g., `01`
388    TwoDigit,
389}
390
391impl Display for TimeNumericFormat {
392    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
393        match self {
394            Self::Numeric => write!(f, "numeric"),
395            Self::TwoDigit => write!(f, "2-digit"),
396        }
397    }
398}
399
400js_value_from_to_string!(TimeNumericFormat);
401
402#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
403pub enum DayPeriodFormat {
404    Long,
405    Short,
406    Narrow,
407}
408
409impl Display for DayPeriodFormat {
410    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
411        match self {
412            Self::Long => write!(f, "long"),
413            Self::Short => write!(f, "short"),
414            Self::Narrow => write!(f, "narrow"),
415        }
416    }
417}
418
419js_value_from_to_string!(DayPeriodFormat);
420
421#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
422pub enum TimeZoneNameFormat {
423    /// Long localized form (e.g., `Pacific Standard Time`, `Nordamerikanische Westküsten-Normalzeit`)
424    Long,
425    /// Short localized form (e.g.: `PST`, `GMT-8`)
426    Short,
427    /// Short localized GMT format (e.g., `GMT-8`)
428    ShortOffset,
429    /// Long localized GMT format (e.g., `GMT-0800`)
430    LongOffset,
431    /// Short generic non-location format (e.g.: `PT`, `Los Angeles Zeit`).
432    ShortGeneric,
433    /// Long generic non-location format (e.g.: `Pacific Time`, `Nordamerikanische Westküstenzeit`)
434    LongGeneric,
435}
436
437impl Display for TimeZoneNameFormat {
438    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439        match self {
440            Self::Long => write!(f, "long"),
441            Self::Short => write!(f, "short"),
442            Self::ShortOffset => write!(f, "shortOffset"),
443            Self::LongOffset => write!(f, "longOffset"),
444            Self::ShortGeneric => write!(f, "shortGeneric"),
445            Self::LongGeneric => write!(f, "longGeneric"),
446        }
447    }
448}
449
450js_value_from_to_string!(TimeZoneNameFormat);
451
452#[derive(DefaultBuilder, Default)]
453pub struct UseIntlDateTimeFormatOptions {
454    /// A vec of strings, each with a BCP 47 language tag. Please refer to the
455    /// [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#parameters)
456    /// for more info.
457    locales: Vec<String>,
458
459    /// The locale matching algorithm to use. Possible values are `Lookup` and `BestFit`; the default is `BestFit`.
460    /// For information about this option, see the [Intl page](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locale_identification_and_negotiation).
461    locale_matcher: LocaleMatcher,
462
463    /// The calendar to use, such as `"chinese"`, `"gregory"`, `"persian"` etc. For a list of the
464    /// supported calendar types, see [the MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getCalendars#supported_calendar_types).
465    #[builder(into)]
466    calendar: Option<String>,
467
468    /// The way day periods should be expressed. This option only has an effect if a 12-hour clock
469    /// (`hour_cycle` `H11` or `H12`, or `hour12` is `true`) is used. Note that the day period may
470    /// be displayed even if the `hour` is not set to be displayed.
471    #[builder(into)]
472    day_period: Option<DayPeriodFormat>,
473
474    /// The numbering system to use. Possible values include: `"arab"`, `"arabext"`, `"bali"`, `"beng"`, `"deva"`, `"fullwide"`, `"gujr"`, `"guru"`, `"hanidec"`, `"khmr"`, `"knda"`, `"laoo"`, `"latn"`, `"limb"`, `"mlym"`, `"mong"`, `"mymr"`, `"orya"`, `"tamldec"`, `"telu"`, `"thai"`, `"tibt"`.
475    #[builder(into)]
476    numbering_system: Option<String>,
477
478    /// Whether to use 12-hour time (as opposed to 24-hour time). The default is locale dependent.
479    /// This option overrides the `hc` language tag and/or the `hour_cycle` option in case both are present.
480    #[builder(into)]
481    hour12: Option<bool>,
482
483    /// The hour cycle to use. This option overrides the `hc` language tag, if both are present, and
484    /// the `hour12` option takes precedence in case both options have been specified.
485    ///
486    /// - `H11`: hour system using 0–11; corresponds to midnight starting at 0:00 AM.
487    /// - `H12`: hour system using 1–12; corresponds to midnight starting at 12:00 AM.
488    /// - `H23`: hour system using 0–23; corresponds to midnight starting at 0:00.
489    /// - `H24`: hour system using 1–24; corresponds to midnight starting at 24:00.
490    #[builder(into)]
491    hour_cycle: Option<HourCycle>,
492
493    /// The time zone to use. The only value implementations must recognize is `"UTC"`; the default
494    /// is the runtime's default time zone. Implementations may also recognize the time zone names
495    /// of the [IANA time zone database](https://www.iana.org/time-zones), such as `"Asia/Shanghai"`,
496    /// `"Asia/Kolkata"`, `"America/New_York"`.
497    #[builder(into)]
498    time_zone: Option<String>,
499
500    /// The format matching algorithm to use. Possible values are `Basic` and `BestFit`; the default
501    /// is `BestFit`. See the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#formatmatcher)
502    /// for details. Only has an effect when using the individual date-time component options
503    /// (and not [`UseIntlDateTimeFormatOptions::date_style`] / [`UseIntlDateTimeFormatOptions::time_style`]).
504    format_matcher: FormatMatcher,
505
506    /// The representation of the weekday.
507    ///
508    /// - `Long`: e.g., `Thursday`
509    /// - `Short`: e.g., `Thu`
510    /// - `Narrow`: e.g., `T`. Two weekdays may have the same narrow style for some locales (e.g. `Tuesday`'s narrow style is also `T`).
511    ///
512    /// Cannot be combined with [`UseIntlDateTimeFormatOptions::date_style`] / [`UseIntlDateTimeFormatOptions::time_style`].
513    #[builder(into)]
514    weekday: Option<WeekdayFormat>,
515
516    /// The representation of the era.
517    ///
518    /// - `Long`: e.g., `Anno Domini`
519    /// - `Short`: e.g., `AD`
520    /// - `Narrow`: e.g., `A`
521    ///
522    /// Cannot be combined with [`UseIntlDateTimeFormatOptions::date_style`] / [`UseIntlDateTimeFormatOptions::time_style`].
523    #[builder(into)]
524    era: Option<EraFormat>,
525
526    /// The representation of the year.
527    ///
528    /// - `Numeric`: e.g., `2012`
529    /// - `TwoDigit`: e.g., `12`
530    ///
531    /// Cannot be combined with [`UseIntlDateTimeFormatOptions::date_style`] / [`UseIntlDateTimeFormatOptions::time_style`].
532    #[builder(into)]
533    year: Option<YearFormat>,
534
535    /// The representation of the month.
536    ///
537    /// - `Numeric`: e.g., `3`
538    /// - `TwoDigit`: e.g., `03`
539    /// - `Long`: e.g., `March`
540    /// - `Short`: e.g., `Mar`
541    /// - `Narrow`: e.g., `M`. Two months may have the same narrow style for some locales (e.g. `May`'s narrow style is also `M`).
542    ///
543    /// Cannot be combined with [`UseIntlDateTimeFormatOptions::date_style`] / [`UseIntlDateTimeFormatOptions::time_style`].
544    #[builder(into)]
545    month: Option<MonthFormat>,
546
547    /// The representation of the day.
548    ///
549    /// - `Numeric`: e.g., `1`
550    /// - `TwoDigit`: e.g., `01`
551    ///
552    /// Cannot be combined with [`UseIntlDateTimeFormatOptions::date_style`] / [`UseIntlDateTimeFormatOptions::time_style`].
553    #[builder(into)]
554    day: Option<DayFormat>,
555
556    /// The representation of the hour.
557    ///
558    /// - `Numeric`: e.g., `1`
559    /// - `TwoDigit`: e.g., `01`
560    ///
561    /// Cannot be combined with [`UseIntlDateTimeFormatOptions::date_style`] / [`UseIntlDateTimeFormatOptions::time_style`].
562    #[builder(into)]
563    hour: Option<TimeNumericFormat>,
564
565    /// The representation of the minute.
566    ///
567    /// - `Numeric`: e.g., `1`
568    /// - `TwoDigit`: e.g., `01`
569    ///
570    /// Cannot be combined with [`UseIntlDateTimeFormatOptions::date_style`] / [`UseIntlDateTimeFormatOptions::time_style`].
571    #[builder(into)]
572    minute: Option<TimeNumericFormat>,
573
574    /// The representation of the second.
575    ///
576    /// - `Numeric`: e.g., `1`
577    /// - `TwoDigit`: e.g., `01`
578    ///
579    /// Cannot be combined with [`UseIntlDateTimeFormatOptions::date_style`] / [`UseIntlDateTimeFormatOptions::time_style`].
580    #[builder(into)]
581    second: Option<TimeNumericFormat>,
582
583    /// The number of digits used to represent fractions of a second (any additional digits are
584    /// truncated). Possible values are from `1` to `3`.
585    #[builder(into)]
586    fractional_second_digits: Option<u8>,
587
588    /// The localized representation of the time zone name.
589    ///
590    /// - `Long`: long localized form (e.g., `Pacific Standard Time`).
591    /// - `Short`: short localized form (e.g.: `PST`).
592    /// - `ShortOffset`: short localized GMT format (e.g., `GMT-8`).
593    /// - `LongOffset`: long localized GMT format (e.g., `GMT-0800`).
594    /// - `ShortGeneric`: short generic non-location format (e.g.: `PT`).
595    /// - `LongGeneric`: long generic non-location format (e.g.: `Pacific Time`).
596    #[builder(into)]
597    time_zone_name: Option<TimeZoneNameFormat>,
598
599    /// The date formatting length. Cannot be combined with the individual date-time component
600    /// options (`weekday`, `era`, `year`, `month`, `day`, `hour`, `minute`, `second`,
601    /// `fractional_second_digits`, `time_zone_name`, `day_period`).
602    ///
603    /// Possible values are `Full`, `Long`, `Medium` and `Short`.
604    #[builder(into)]
605    date_style: Option<DateTimeStyle>,
606
607    /// The time formatting length. Cannot be combined with the individual date-time component
608    /// options (`weekday`, `era`, `year`, `month`, `day`, `hour`, `minute`, `second`,
609    /// `fractional_second_digits`, `time_zone_name`, `day_period`).
610    ///
611    /// Possible values are `Full`, `Long`, `Medium` and `Short`.
612    #[builder(into)]
613    time_style: Option<DateTimeStyle>,
614}
615
616impl UseIntlDateTimeFormatOptions {
617    /// A string with a BCP 47 language tag. Please refer to the
618    /// [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#parameters)
619    /// for more info.
620    pub fn locale(self, locale: &str) -> Self {
621        Self {
622            locales: vec![locale.to_string()],
623            ..self
624        }
625    }
626}
627
628impl From<UseIntlDateTimeFormatOptions> for js_sys::Object {
629    fn from(options: UseIntlDateTimeFormatOptions) -> Self {
630        let object = Self::new();
631
632        _ = js!(object["localeMatcher"] = options.locale_matcher);
633        _ = js!(object["formatMatcher"] = options.format_matcher);
634
635        if let Some(calendar) = options.calendar {
636            _ = js!(object["calendar"] = calendar);
637        }
638        if let Some(day_period) = options.day_period {
639            _ = js!(object["dayPeriod"] = day_period);
640        }
641        if let Some(numbering_system) = options.numbering_system {
642            _ = js!(object["numberingSystem"] = numbering_system);
643        }
644        if let Some(hour12) = options.hour12 {
645            _ = js!(object["hour12"] = hour12);
646        }
647        if let Some(hour_cycle) = options.hour_cycle {
648            _ = js!(object["hourCycle"] = hour_cycle);
649        }
650        if let Some(time_zone) = options.time_zone {
651            _ = js!(object["timeZone"] = time_zone);
652        }
653        if let Some(weekday) = options.weekday {
654            _ = js!(object["weekday"] = weekday);
655        }
656        if let Some(era) = options.era {
657            _ = js!(object["era"] = era);
658        }
659        if let Some(year) = options.year {
660            _ = js!(object["year"] = year);
661        }
662        if let Some(month) = options.month {
663            _ = js!(object["month"] = month);
664        }
665        if let Some(day) = options.day {
666            _ = js!(object["day"] = day);
667        }
668        if let Some(hour) = options.hour {
669            _ = js!(object["hour"] = hour);
670        }
671        if let Some(minute) = options.minute {
672            _ = js!(object["minute"] = minute);
673        }
674        if let Some(second) = options.second {
675            _ = js!(object["second"] = second);
676        }
677        if let Some(fractional_second_digits) = options.fractional_second_digits {
678            _ = js!(object["fractionalSecondDigits"] = fractional_second_digits);
679        }
680        if let Some(time_zone_name) = options.time_zone_name {
681            _ = js!(object["timeZoneName"] = time_zone_name);
682        }
683        if let Some(date_style) = options.date_style {
684            _ = js!(object["dateStyle"] = date_style);
685        }
686        if let Some(time_style) = options.time_style {
687            _ = js!(object["timeStyle"] = time_style);
688        }
689
690        object
691    }
692}
693
694cfg_if! { if #[cfg(feature = "ssr")] {
695    /// Return type of [`use_intl_datetime_format`].
696    pub struct UseIntlDateTimeFormatReturn;
697} else {
698    /// Return type of [`use_intl_datetime_format`].
699    pub struct UseIntlDateTimeFormatReturn {
700        /// The instance of [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat).
701        pub js_intl_datetime_format: js_sys::Intl::DateTimeFormat,
702    }
703}}
704
705#[cfg(not(feature = "ssr"))]
706fn datetime_to_js_date<Tz>(date: &DateTime<Tz>) -> js_sys::Date
707where
708    Tz: TimeZone,
709{
710    js_sys::Date::new(&JsValue::from_f64(date.timestamp_millis() as f64))
711}
712
713impl UseIntlDateTimeFormatReturn {
714    /// Formats a date according to the [locale and formatting options](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#parameters) of this `Intl.DateTimeFormat` object.
715    /// See [`use_intl_datetime_format`] for more information.
716    /// In the browser this uses `SendWrapper` internally so the returned signal can only be used on
717    /// the same thread where this method was called.
718    pub fn format<Tz>(&self, date: impl Into<Signal<DateTime<Tz>>>) -> Signal<String>
719    where
720        Tz: TimeZone,
721        DateTime<Tz>: Clone + Display + Send + Sync + 'static,
722    {
723        let date = date.into();
724
725        cfg_if! { if #[cfg(feature = "ssr")] {
726            Signal::derive(move || {
727                format!("{}", date.get())
728            })
729        } else {
730            let datetime_format = self.js_intl_datetime_format.clone();
731
732            Signal::derive(sendwrap_fn!(move || {
733                if let Ok(result) = datetime_format
734                    .format()
735                    .call1(&datetime_format, &datetime_to_js_date(&date.get()).into())
736                {
737                    result.as_string().unwrap_or_default()
738                } else {
739                    "".to_string()
740                }
741            }))
742        }}
743    }
744
745    /// Formats a range of dates according to the locale and formatting options of this
746    /// `Intl.DateTimeFormat` object.
747    ///
748    /// ```
749    /// # use leptos::prelude::*;
750    /// # use leptos_use::{use_intl_datetime_format, UseIntlDateTimeFormatOptions, MonthFormat, DayFormat, YearFormat};
751    /// # use chrono::{TimeZone, Utc};
752    /// #
753    /// # #[component]
754    /// # fn Demo() -> impl IntoView {
755    /// let start = Utc.with_ymd_and_hms(2007, 1, 10, 10, 0, 0).unwrap();
756    /// let end = Utc.with_ymd_and_hms(2008, 1, 10, 11, 0, 0).unwrap();
757    ///
758    /// let date_time_format = use_intl_datetime_format(
759    ///     UseIntlDateTimeFormatOptions::default()
760    ///         .locale("en-US")
761    ///         .year(YearFormat::Numeric)
762    ///         .month(MonthFormat::Short)
763    ///         .day(DayFormat::Numeric),
764    /// );
765    ///
766    /// let formatted = date_time_format.format_range(start, end); // "Jan 10, 2007 – Jan 10, 2008"
767    /// #
768    /// # view! { }
769    /// # }
770    /// ```
771    pub fn format_range<TzStart, TzEnd>(
772        &self,
773        start: impl Into<Signal<DateTime<TzStart>>>,
774        end: impl Into<Signal<DateTime<TzEnd>>>,
775    ) -> Signal<String>
776    where
777        TzStart: TimeZone,
778        TzEnd: TimeZone,
779        DateTime<TzStart>: Clone + Display + Send + Sync + 'static,
780        DateTime<TzEnd>: Clone + Display + Send + Sync + 'static,
781    {
782        let start = start.into();
783        let end = end.into();
784
785        cfg_if! { if #[cfg(feature = "ssr")] {
786            Signal::derive(move || {
787                format!("{} - {}", start.get(), end.get())
788            })
789        } else {
790            let datetime_format = self.js_intl_datetime_format.clone();
791
792            Signal::derive(sendwrap_fn!(move || {
793                datetime_format
794                    .format_range(
795                        &datetime_to_js_date(&start.get()),
796                        &datetime_to_js_date(&end.get()),
797                    )
798                    .map(|s| s.as_string().unwrap_or_default())
799                    .unwrap_or_default()
800            }))
801        }}
802    }
803}