1#![allow(non_camel_case_types)]
15
16use std::fmt;
17
18use aho_corasick::AhoCorasickBuilder;
19use enum_iterator::Sequence;
20use mz_ore::cast::CastFrom;
21use num_enum::{IntoPrimitive, TryFromPrimitive};
22use serde::{Deserialize, Serialize};
23
24use crate::scalar::func::TimestampLike;
25
26#[repr(u8)]
29#[derive(Eq, PartialEq, TryFromPrimitive, IntoPrimitive, Sequence)]
30enum DateTimeToken {
31 a_d,
32 A_D,
33 a_m,
34 A_M,
35 ad,
36 AD,
37 am,
38 AM,
39 b_c,
40 B_C,
41 bc,
42 BC,
43 cc,
44 CC,
45 d,
46 D,
47 day,
48 Day,
49 DAY,
50 dd,
51 DD,
52 ddd,
53 DDD,
54 dy,
55 Dy,
56 DY,
57 fm,
58 FM,
59 fx,
60 FX,
61 hh,
62 HH,
63 hh12,
64 HH12,
65 hh24,
66 HH24,
67 i,
68 I,
69 id,
70 ID,
71 iddd,
72 IDDD,
73 iw,
74 IW,
75 iy,
76 IY,
77 iyy,
78 IYY,
79 iyyy,
80 IYYY,
81 j,
82 J,
83 mi,
84 MI,
85 mm,
86 MM,
87 mon,
88 Mon,
89 MON,
90 month,
91 Month,
92 MONTH,
93 ms,
94 MS,
95 OF,
96 p_m,
97 P_M,
98 pm,
99 PM,
100 q,
101 Q,
102 rm,
103 RM,
104 ss,
105 SS,
106 ssss,
107 SSSS,
108 sssss,
109 SSSSS,
110 tz,
111 TZ,
112 TZH,
113 TZM,
114 us,
115 US,
116 w,
117 W,
118 ww,
119 WW,
120 y_yyy,
121 Y_YYY,
122 y,
123 Y,
124 yy,
125 YY,
126 yyy,
127 YYY,
128 yyyy,
129 YYYY,
130 th,
131 TH,
132 EscQuote,
133 Quote,
134}
135
136impl DateTimeToken {
137 const fn pattern(&self) -> &'static str {
140 match self {
141 DateTimeToken::AD => "AD",
142 DateTimeToken::ad => "ad",
143 DateTimeToken::A_D => "A.D.",
144 DateTimeToken::a_d => "a.d.",
145 DateTimeToken::AM => "AM",
146 DateTimeToken::am => "am",
147 DateTimeToken::A_M => "A.M.",
148 DateTimeToken::a_m => "a.m.",
149 DateTimeToken::BC => "BC",
150 DateTimeToken::bc => "bc",
151 DateTimeToken::B_C => "B.C.",
152 DateTimeToken::b_c => "b.c.",
153 DateTimeToken::CC => "CC",
154 DateTimeToken::cc => "cc",
155 DateTimeToken::D => "D",
156 DateTimeToken::d => "d",
157 DateTimeToken::DAY => "DAY",
158 DateTimeToken::Day => "Day",
159 DateTimeToken::day => "day",
160 DateTimeToken::DD => "DD",
161 DateTimeToken::dd => "dd",
162 DateTimeToken::DDD => "DDD",
163 DateTimeToken::ddd => "ddd",
164 DateTimeToken::DY => "DY",
165 DateTimeToken::Dy => "Dy",
166 DateTimeToken::dy => "dy",
167 DateTimeToken::FM => "FM",
168 DateTimeToken::fm => "fm",
169 DateTimeToken::FX => "FX",
170 DateTimeToken::fx => "fx",
171 DateTimeToken::HH => "HH",
172 DateTimeToken::hh => "hh",
173 DateTimeToken::HH12 => "HH12",
174 DateTimeToken::hh12 => "hh12",
175 DateTimeToken::HH24 => "HH24",
176 DateTimeToken::hh24 => "hh24",
177 DateTimeToken::I => "I",
178 DateTimeToken::i => "i",
179 DateTimeToken::ID => "ID",
180 DateTimeToken::id => "id",
181 DateTimeToken::IDDD => "IDDD",
182 DateTimeToken::iddd => "iddd",
183 DateTimeToken::IW => "IW",
184 DateTimeToken::iw => "iw",
185 DateTimeToken::IY => "IY",
186 DateTimeToken::iy => "iy",
187 DateTimeToken::IYY => "IYY",
188 DateTimeToken::iyy => "iyy",
189 DateTimeToken::IYYY => "IYYY",
190 DateTimeToken::iyyy => "iyyy",
191 DateTimeToken::J => "J",
192 DateTimeToken::j => "j",
193 DateTimeToken::MI => "MI",
194 DateTimeToken::mi => "mi",
195 DateTimeToken::MM => "MM",
196 DateTimeToken::mm => "mm",
197 DateTimeToken::MON => "MON",
198 DateTimeToken::Mon => "Mon",
199 DateTimeToken::mon => "mon",
200 DateTimeToken::MONTH => "MONTH",
201 DateTimeToken::Month => "Month",
202 DateTimeToken::month => "month",
203 DateTimeToken::MS => "MS",
204 DateTimeToken::ms => "ms",
205 DateTimeToken::OF => "OF",
206 DateTimeToken::PM => "PM",
207 DateTimeToken::pm => "pm",
208 DateTimeToken::P_M => "P.M.",
209 DateTimeToken::p_m => "p.m.",
210 DateTimeToken::Q => "Q",
211 DateTimeToken::q => "q",
212 DateTimeToken::rm => "rm",
213 DateTimeToken::RM => "RM",
214 DateTimeToken::SS => "ss",
215 DateTimeToken::ss => "SS",
216 DateTimeToken::SSSS => "SSSS",
217 DateTimeToken::ssss => "ssss",
218 DateTimeToken::SSSSS => "SSSSS",
219 DateTimeToken::sssss => "sssss",
220 DateTimeToken::TZ => "TZ",
221 DateTimeToken::tz => "tz",
222 DateTimeToken::TZH => "TZH",
223 DateTimeToken::TZM => "TZM",
224 DateTimeToken::US => "US",
225 DateTimeToken::us => "us",
226 DateTimeToken::W => "W",
227 DateTimeToken::w => "w",
228 DateTimeToken::WW => "ww",
229 DateTimeToken::ww => "WW",
230 DateTimeToken::Y => "Y",
231 DateTimeToken::y => "y",
232 DateTimeToken::Y_YYY => "Y,YYY",
233 DateTimeToken::y_yyy => "y,yyy",
234 DateTimeToken::YY => "YY",
235 DateTimeToken::yy => "yy",
236 DateTimeToken::YYY => "YYY",
237 DateTimeToken::yyy => "yyy",
238 DateTimeToken::YYYY => "YYYY",
239 DateTimeToken::yyyy => "yyyy",
240 DateTimeToken::Quote => "\"",
241 DateTimeToken::EscQuote => "\\\"",
242 DateTimeToken::TH => "TH",
243 DateTimeToken::th => "th",
244 }
245 }
246
247 fn patterns() -> Vec<&'static str> {
250 enum_iterator::all::<Self>().map(|v| v.pattern()).collect()
251 }
252
253 fn field(&self) -> Option<DateTimeField> {
258 use DateTimeToken::*;
259 use WordCaps::*;
260 match self {
261 AD | BC => Some(DateTimeField::Era {
262 dots: false,
263 caps: true,
264 }),
265 ad | bc => Some(DateTimeField::Era {
266 dots: false,
267 caps: false,
268 }),
269 A_D | B_C => Some(DateTimeField::Era {
270 dots: true,
271 caps: true,
272 }),
273 a_d | b_c => Some(DateTimeField::Era {
274 dots: true,
275 caps: false,
276 }),
277 AM | PM => Some(DateTimeField::Meridiem {
278 dots: false,
279 caps: true,
280 }),
281 am | pm => Some(DateTimeField::Meridiem {
282 dots: false,
283 caps: false,
284 }),
285 A_M | P_M => Some(DateTimeField::Meridiem {
286 dots: true,
287 caps: true,
288 }),
289 a_m | p_m => Some(DateTimeField::Meridiem {
290 dots: true,
291 caps: false,
292 }),
293 cc | CC => Some(DateTimeField::Century),
294 d | D => Some(DateTimeField::DayOfWeek),
295 day => Some(DateTimeField::DayName {
296 abbrev: false,
297 caps: NoCaps,
298 }),
299 Day => Some(DateTimeField::DayName {
300 abbrev: false,
301 caps: FirstCaps,
302 }),
303 DAY => Some(DateTimeField::DayName {
304 abbrev: false,
305 caps: AllCaps,
306 }),
307 dy => Some(DateTimeField::DayName {
308 abbrev: true,
309 caps: NoCaps,
310 }),
311 Dy => Some(DateTimeField::DayName {
312 abbrev: true,
313 caps: FirstCaps,
314 }),
315 DY => Some(DateTimeField::DayName {
316 abbrev: true,
317 caps: AllCaps,
318 }),
319 dd | DD => Some(DateTimeField::DayOfMonth),
320 ddd | DDD => Some(DateTimeField::DayOfYear),
321 fm | FM | fx | FX | th | TH | Quote | EscQuote => None,
322 hh | HH | hh12 | HH12 => Some(DateTimeField::Hour12),
323 hh24 | HH24 => Some(DateTimeField::Hour24),
324 id | ID => Some(DateTimeField::IsoDayOfWeek),
325 iddd | IDDD => Some(DateTimeField::IsoDayOfYear),
326 iw | IW => Some(DateTimeField::IsoWeekOfYear),
327 j | J => Some(DateTimeField::JulianDay),
328 mi | MI => Some(DateTimeField::Minute),
329 mm | MM => Some(DateTimeField::MonthOfYear),
330 mon => Some(DateTimeField::MonthName {
331 abbrev: true,
332 caps: NoCaps,
333 }),
334 Mon => Some(DateTimeField::MonthName {
335 abbrev: true,
336 caps: FirstCaps,
337 }),
338 MON => Some(DateTimeField::MonthName {
339 abbrev: true,
340 caps: AllCaps,
341 }),
342 month => Some(DateTimeField::MonthName {
343 abbrev: false,
344 caps: NoCaps,
345 }),
346 Month => Some(DateTimeField::MonthName {
347 abbrev: false,
348 caps: FirstCaps,
349 }),
350 MONTH => Some(DateTimeField::MonthName {
351 abbrev: false,
352 caps: AllCaps,
353 }),
354 ms | MS => Some(DateTimeField::Millisecond),
355 OF => Some(DateTimeField::TimezoneOffset),
356 q | Q => Some(DateTimeField::Quarter),
357 rm => Some(DateTimeField::MonthInRomanNumerals { caps: false }),
358 RM => Some(DateTimeField::MonthInRomanNumerals { caps: true }),
359 ss | SS => Some(DateTimeField::Second),
360 ssss | SSSS | sssss | SSSSS => Some(DateTimeField::SecondsPastMidnight),
361 tz => Some(DateTimeField::Timezone { caps: false }),
362 TZ => Some(DateTimeField::Timezone { caps: true }),
363 TZH => Some(DateTimeField::TimezoneHours),
364 TZM => Some(DateTimeField::TimezoneMinutes),
365 us | US => Some(DateTimeField::Microsecond),
366 w | W => Some(DateTimeField::WeekOfMonth),
367 ww | WW => Some(DateTimeField::WeekOfYear),
368 y | Y => Some(DateTimeField::Year1),
369 yy | YY => Some(DateTimeField::Year2),
370 yyy | YYY => Some(DateTimeField::Year3),
371 yyyy | YYYY => Some(DateTimeField::Year4 { separator: false }),
372 y_yyy | Y_YYY => Some(DateTimeField::Year4 { separator: true }),
373 i | I => Some(DateTimeField::IsoYear1),
374 iy | IY => Some(DateTimeField::IsoYear2),
375 iyy | IYY => Some(DateTimeField::IsoYear3),
376 iyyy | IYYY => Some(DateTimeField::IsoYear4),
377 }
378 }
379
380 fn as_literal(&self) -> &'static str {
384 match self {
385 DateTimeToken::Quote => "",
386 DateTimeToken::EscQuote => "\"",
387 _ => self.pattern(),
388 }
389 }
390
391 fn is_fill_mode_toggle(&self) -> bool {
393 matches!(self, DateTimeToken::fm | DateTimeToken::FM)
394 }
395
396 fn ordinal_mode(&self) -> OrdinalMode {
398 match self {
399 DateTimeToken::th => OrdinalMode::Lower,
400 DateTimeToken::TH => OrdinalMode::Upper,
401 _ => OrdinalMode::None,
402 }
403 }
404}
405
406#[derive(
408 Debug,
409 Eq,
410 PartialEq,
411 PartialOrd,
412 Ord,
413 Copy,
414 Clone,
415 Hash,
416 Serialize,
417 Deserialize
418)]
419enum OrdinalMode {
420 None,
422 Lower,
424 Upper,
426}
427
428impl OrdinalMode {
429 fn render(self, out: &mut impl fmt::Write, n: impl Into<i64>) -> Result<(), fmt::Error> {
430 let n = n.into();
431 let n = match n % 100 {
434 10..=19 => 0,
435 _ => n % 10,
436 };
437 match self {
438 OrdinalMode::None => Ok(()),
439 OrdinalMode::Lower => match n {
440 1 => out.write_str("st"),
441 2 => out.write_str("nd"),
442 3 => out.write_str("rd"),
443 _ => out.write_str("th"),
444 },
445 OrdinalMode::Upper => match n {
446 1 => out.write_str("ST"),
447 2 => out.write_str("ND"),
448 3 => out.write_str("RD"),
449 _ => out.write_str("TH"),
450 },
451 }
452 }
453}
454
455#[allow(clippy::enum_variant_names)] #[derive(
458 Debug,
459 Eq,
460 PartialEq,
461 PartialOrd,
462 Ord,
463 Copy,
464 Clone,
465 Hash,
466 Serialize,
467 Deserialize
468)]
469enum WordCaps {
470 AllCaps,
472 FirstCaps,
474 NoCaps,
476}
477
478#[derive(
483 Debug,
484 Eq,
485 PartialEq,
486 PartialOrd,
487 Ord,
488 Clone,
489 Hash,
490 Serialize,
491 Deserialize
492)]
493enum DateTimeField {
494 Hour12,
495 Hour24,
496 Minute,
497 Second,
498 Millisecond,
499 Microsecond,
500 SecondsPastMidnight,
501 Meridiem { dots: bool, caps: bool },
502 Year1,
503 Year2,
504 Year3,
505 Year4 { separator: bool },
506 IsoYear1,
507 IsoYear2,
508 IsoYear3,
509 IsoYear4,
510 Era { dots: bool, caps: bool },
511 MonthName { abbrev: bool, caps: WordCaps },
512 MonthOfYear,
513 DayName { abbrev: bool, caps: WordCaps },
514 DayOfWeek,
515 IsoDayOfWeek,
516 DayOfMonth,
517 DayOfYear,
518 IsoDayOfYear,
519 WeekOfMonth,
520 WeekOfYear,
521 IsoWeekOfYear,
522 Century,
523 JulianDay,
524 Quarter,
525 MonthInRomanNumerals { caps: bool },
526 Timezone { caps: bool },
527 TimezoneHours,
528 TimezoneMinutes,
529 TimezoneOffset,
530}
531
532#[derive(
534 Clone,
535 Debug,
536 PartialEq,
537 Eq,
538 PartialOrd,
539 Ord,
540 Hash,
541 Serialize,
542 Deserialize
543)]
544enum DateTimeFormatNode {
545 Field {
547 field: DateTimeField,
549 fill: bool,
553 ordinal: OrdinalMode,
556 },
557 Literal(char),
559}
560
561const WEEKDAYS_ALL_CAPS: [&str; 7] = [
562 "SUNDAY",
563 "MONDAY",
564 "TUESDAY",
565 "WEDNESDAY",
566 "THURSDAY",
567 "FRIDAY",
568 "SATURDAY",
569];
570
571const WEEKDAYS_FIRST_CAPS: [&str; 7] = [
572 "Sunday",
573 "Monday",
574 "Tuesday",
575 "Wednesday",
576 "Thursday",
577 "Friday",
578 "Saturday",
579];
580
581const WEEKDAYS_NO_CAPS: [&str; 7] = [
582 "sunday",
583 "monday",
584 "tuesday",
585 "wednesday",
586 "thursday",
587 "friday",
588 "saturday",
589];
590
591const WEEKDAYS_ABBREV_ALL_CAPS: [&str; 7] = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
592
593const WEEKDAYS_ABBREV_FIRST_CAPS: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
594
595const WEEKDAYS_ABBREV_NO_CAPS: [&str; 7] = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
596
597const MONTHS_ALL_CAPS: [&str; 12] = [
598 "JANUARY",
599 "FEBRUARY",
600 "MARCH",
601 "APRIL",
602 "MAY",
603 "JUNE",
604 "JULY",
605 "AUGUST",
606 "SEPTEMBER",
607 "OCTOBER",
608 "NOVEMBER",
609 "DECEMBER",
610];
611
612const MONTHS_FIRST_CAPS: [&str; 12] = [
613 "January",
614 "February",
615 "March",
616 "April",
617 "May",
618 "June",
619 "July",
620 "August",
621 "September",
622 "October",
623 "November",
624 "December",
625];
626
627const MONTHS_NO_CAPS: [&str; 12] = [
628 "january",
629 "february",
630 "march",
631 "april",
632 "may",
633 "june",
634 "july",
635 "august",
636 "september",
637 "october",
638 "november",
639 "december",
640];
641
642const MONTHS_ABBREV_ALL_CAPS: [&str; 12] = [
643 "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC",
644];
645
646const MONTHS_ABBREV_FIRST_CAPS: [&str; 12] = [
647 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
648];
649
650const MONTHS_ABBREV_NO_CAPS: [&str; 12] = [
651 "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec",
652];
653
654const MONTHS_ROMAN_NO_CAPS: [&str; 12] = [
655 "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix", "x", "xi", "xii",
656];
657
658const MONTHS_ROMAN_CAPS: [&str; 12] = [
659 "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII",
660];
661
662impl DateTimeFormatNode {
663 fn render(&self, buf: &mut impl fmt::Write, ts: &impl TimestampLike) -> Result<(), fmt::Error> {
664 use WordCaps::*;
665 match self {
666 DateTimeFormatNode::Literal(ch) => buf.write_char(*ch),
667 DateTimeFormatNode::Field {
668 field,
669 fill,
670 ordinal,
671 } => {
672 macro_rules! write_num {
673 ($n:expr, $width:expr) => {{
674 write!(
675 buf,
676 "{:0width$}",
677 $n,
678 width = if *fill { $width } else { 0 }
679 )?;
680 ordinal.render(buf, $n)
681 }};
682 ($n:expr) => {
683 write_num!($n, 0)
684 };
685 }
686
687 macro_rules! write_str {
688 ($s:expr, $width:expr) => {{ write!(buf, "{:width$}", $s, width = if *fill { $width } else { 0 }) }};
689 ($s:expr) => {
690 write_str!($s, 0)
691 };
692 }
693
694 match field {
695 DateTimeField::Era {
696 dots: false,
697 caps: true,
698 } => write_str!(if ts.year_ce().0 { "AD" } else { "BC" }),
699 DateTimeField::Era {
700 dots: false,
701 caps: false,
702 } => write_str!(if ts.year_ce().0 { "ad" } else { "bc" }),
703 DateTimeField::Era {
704 dots: true,
705 caps: true,
706 } => write_str!(if ts.year_ce().0 { "A.D." } else { "B.C." }),
707 DateTimeField::Era {
708 dots: true,
709 caps: false,
710 } => write_str!(if ts.year_ce().0 { "a.d." } else { "b.c." }),
711 DateTimeField::Meridiem {
712 dots: false,
713 caps: true,
714 } => write_str!(if ts.hour12().0 { "PM" } else { "AM" }),
715 DateTimeField::Meridiem {
716 dots: false,
717 caps: false,
718 } => write_str!(if ts.hour12().0 { "pm" } else { "am" }),
719 DateTimeField::Meridiem {
720 dots: true,
721 caps: true,
722 } => write_str!(if ts.hour12().0 { "P.M." } else { "A.M." }),
723 DateTimeField::Meridiem {
724 dots: true,
725 caps: false,
726 } => write_str!(if ts.hour12().0 { "p.m." } else { "a.m." }),
727 DateTimeField::Century => {
728 let n = if ts.year() > 0 {
729 (ts.year() - 1) / 100 + 1
730 } else {
731 ts.year() / 100 - 1
732 };
733 write_num!(n, if n >= 0 { 2 } else { 3 })
734 }
735 DateTimeField::DayOfWeek => write_num!(ts.weekday().number_from_sunday(), 1),
736 DateTimeField::IsoDayOfWeek => write_num!(ts.weekday().number_from_monday(), 1),
737 DateTimeField::DayName {
738 abbrev: false,
739 caps: AllCaps,
740 } => write_str!(WEEKDAYS_ALL_CAPS[ts.weekday0()], 9),
741 DateTimeField::DayName {
742 abbrev: false,
743 caps: FirstCaps,
744 } => write_str!(WEEKDAYS_FIRST_CAPS[ts.weekday0()], 9),
745 DateTimeField::DayName {
746 abbrev: false,
747 caps: NoCaps,
748 } => write_str!(WEEKDAYS_NO_CAPS[ts.weekday0()], 9),
749 DateTimeField::DayName {
750 abbrev: true,
751 caps: AllCaps,
752 } => write_str!(WEEKDAYS_ABBREV_ALL_CAPS[ts.weekday0()]),
753 DateTimeField::DayName {
754 abbrev: true,
755 caps: FirstCaps,
756 } => write_str!(WEEKDAYS_ABBREV_FIRST_CAPS[ts.weekday0()]),
757 DateTimeField::DayName {
758 abbrev: true,
759 caps: NoCaps,
760 } => write_str!(WEEKDAYS_ABBREV_NO_CAPS[ts.weekday0()]),
761 DateTimeField::DayOfMonth => write_num!(ts.day(), 2),
762 DateTimeField::DayOfYear => write_num!(ts.ordinal(), 3),
763 DateTimeField::Hour12 => write_num!(ts.hour12().1, 2),
764 DateTimeField::Hour24 => write_num!(ts.hour(), 2),
765 DateTimeField::IsoYear1 => write_num!(ts.iso_year_ce() % 10, 1),
766 DateTimeField::IsoYear2 => write_num!(ts.iso_year_ce() % 100, 2),
767 DateTimeField::IsoYear3 => write_num!(ts.iso_year_ce() % 1000, 3),
768 DateTimeField::IsoYear4 => write_num!(ts.iso_year_ce(), 4),
769 DateTimeField::IsoDayOfYear => write_num!(
770 ts.iso_week().week0() * 7 + ts.weekday().number_from_monday(),
771 3
772 ),
773 DateTimeField::IsoWeekOfYear => write_num!(ts.iso_week().week(), 2),
774 DateTimeField::JulianDay => write_num!(ts.num_days_from_ce() + 1_721_425),
775 DateTimeField::Minute => write_num!(ts.minute(), 2),
776 DateTimeField::MonthOfYear => write_num!(ts.month(), 2),
777 DateTimeField::MonthName {
778 abbrev: true,
779 caps: AllCaps,
780 } => write_str!(MONTHS_ABBREV_ALL_CAPS[usize::cast_from(ts.month0())]),
781 DateTimeField::MonthName {
782 abbrev: true,
783 caps: FirstCaps,
784 } => write_str!(MONTHS_ABBREV_FIRST_CAPS[usize::cast_from(ts.month0())]),
785 DateTimeField::MonthName {
786 abbrev: true,
787 caps: NoCaps,
788 } => write_str!(MONTHS_ABBREV_NO_CAPS[usize::cast_from(ts.month0())]),
789 DateTimeField::MonthName {
790 abbrev: false,
791 caps: AllCaps,
792 } => write_str!(MONTHS_ALL_CAPS[usize::cast_from(ts.month0())], 9),
793 DateTimeField::MonthName {
794 abbrev: false,
795 caps: FirstCaps,
796 } => write_str!(MONTHS_FIRST_CAPS[usize::cast_from(ts.month0())], 9),
797 DateTimeField::MonthName {
798 abbrev: false,
799 caps: NoCaps,
800 } => write_str!(MONTHS_NO_CAPS[usize::cast_from(ts.month0())], 9),
801 DateTimeField::Millisecond => write_num!(ts.nanosecond() / 1_000_000, 3),
802 DateTimeField::Quarter => write_num!(ts.month0() / 3 + 1),
803 DateTimeField::MonthInRomanNumerals { caps: true } => {
804 write_str!(MONTHS_ROMAN_CAPS[usize::cast_from(ts.month0())], 4)
805 }
806 DateTimeField::MonthInRomanNumerals { caps: false } => {
807 write_str!(MONTHS_ROMAN_NO_CAPS[usize::cast_from(ts.month0())], 4)
808 }
809 DateTimeField::Second => write_num!(ts.second(), 2),
810 DateTimeField::SecondsPastMidnight => {
811 write_num!(ts.num_seconds_from_midnight())
812 }
813 DateTimeField::Timezone { caps } => write_str!(ts.timezone_name(*caps)),
814 DateTimeField::TimezoneOffset => write_str!(ts.timezone_offset()),
815 DateTimeField::TimezoneHours => write_str!(ts.timezone_hours()),
816 DateTimeField::TimezoneMinutes => write_str!(ts.timezone_minutes()),
817 DateTimeField::Microsecond => write_num!(ts.nanosecond() / 1_000, 6),
818 DateTimeField::WeekOfMonth => write_num!(ts.day0() / 7 + 1, 1),
819 DateTimeField::WeekOfYear => write_num!(ts.ordinal0() / 7 + 1, 2),
820 DateTimeField::Year1 => write_num!(ts.year_ce().1 % 10, 1),
821 DateTimeField::Year2 => write_num!(ts.year_ce().1 % 100, 2),
822 DateTimeField::Year3 => write_num!(ts.year_ce().1 % 1000, 3),
823 DateTimeField::Year4 { separator: false } => write_num!(ts.year_ce().1, 4),
824 DateTimeField::Year4 { separator: true } => {
825 let n = ts.year_ce().1;
826 write!(buf, "{},{:03}", n / 1000, n % 1000)?;
827 ordinal.render(buf, n)
828 }
829 }
830 }
831 }
832 }
833}
834
835#[derive(
837 Clone,
838 Debug,
839 PartialEq,
840 Eq,
841 PartialOrd,
842 Ord,
843 Hash,
844 Serialize,
845 Deserialize
846)]
847pub struct DateTimeFormat(Vec<DateTimeFormatNode>);
848
849impl DateTimeFormat {
850 pub fn compile(s: &str) -> DateTimeFormat {
852 struct Match {
861 start: usize,
862 end: usize,
863 token: DateTimeToken,
864 }
865
866 let matcher = AhoCorasickBuilder::new()
867 .match_kind(aho_corasick::MatchKind::LeftmostLongest)
868 .build(DateTimeToken::patterns())
869 .unwrap_or_else(|e| panic!("automaton build error: {e}"));
870
871 let matches: Vec<_> = matcher
872 .find_iter(&s)
873 .map(|m| Match {
874 start: m.start(),
875 end: m.end(),
876 token: DateTimeToken::try_from(
877 u8::try_from(m.pattern().as_u32()).expect("match index fits in a u8"),
878 )
879 .expect("match pattern missing"),
880 })
881 .collect();
882
883 let mut out = Vec::new();
884 let mut pos = 0;
885 let mut in_quotes = false;
886 for i in 0..matches.len() {
887 for c in s[pos..matches[i].start].chars() {
889 if !(in_quotes && c == '\\') {
890 out.push(DateTimeFormatNode::Literal(c));
892 }
893 }
894
895 if in_quotes {
896 for c in matches[i].token.as_literal().chars() {
899 out.push(DateTimeFormatNode::Literal(c))
900 }
901 } else if let Some(field) = matches[i].token.field() {
902 let fill = i == 0
906 || matches[i - 1].end != matches[i].start
907 || !matches[i - 1].token.is_fill_mode_toggle();
908 let ordinal = match matches.get(i + 1) {
909 Some(m) if m.start == matches[i].end => m.token.ordinal_mode(),
910 _ => OrdinalMode::None,
911 };
912 out.push(DateTimeFormatNode::Field {
913 field,
914 fill,
915 ordinal,
916 });
917 }
918
919 if matches[i].token == DateTimeToken::Quote {
920 in_quotes = !in_quotes;
921 }
922 pos = matches[i].end;
923 }
924 for c in s[pos..].chars() {
925 out.push(DateTimeFormatNode::Literal(c));
926 }
927 DateTimeFormat(out)
928 }
929
930 pub fn render(&self, ts: &impl TimestampLike) -> String {
934 let mut out = String::new();
935 for node in &self.0 {
936 node.render(&mut out, ts)
937 .expect("rendering to string cannot fail");
938 }
939 out
940 }
941}