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}