1use super::Dialect;
27use std::collections::HashMap;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum TimeFormatStyle {
32 Strftime,
34 Mysql,
36 Postgres,
38 Snowflake,
40 Java,
42 Tsql,
44 ClickHouse,
46}
47
48impl TimeFormatStyle {
49 #[must_use]
51 pub fn for_dialect(dialect: Dialect) -> Self {
52 match dialect {
53 Dialect::Ansi | Dialect::Sqlite | Dialect::BigQuery | Dialect::DuckDb => {
55 TimeFormatStyle::Strftime
56 }
57
58 Dialect::Mysql | Dialect::Doris | Dialect::SingleStore | Dialect::StarRocks => {
60 TimeFormatStyle::Mysql
61 }
62
63 Dialect::Postgres
65 | Dialect::Oracle
66 | Dialect::Redshift
67 | Dialect::Materialize
68 | Dialect::RisingWave
69 | Dialect::Exasol
70 | Dialect::Teradata => TimeFormatStyle::Postgres,
71
72 Dialect::Snowflake => TimeFormatStyle::Snowflake,
74
75 Dialect::Hive | Dialect::Spark | Dialect::Databricks => TimeFormatStyle::Java,
77
78 Dialect::Tsql | Dialect::Fabric => TimeFormatStyle::Tsql,
80
81 Dialect::Presto | Dialect::Trino | Dialect::Athena => TimeFormatStyle::Java,
83
84 Dialect::ClickHouse => TimeFormatStyle::ClickHouse,
86
87 Dialect::Dremio
89 | Dialect::Drill
90 | Dialect::Druid
91 | Dialect::Tableau
92 | Dialect::Prql => TimeFormatStyle::Strftime,
93 }
94 }
95}
96
97#[derive(Debug, Clone)]
103struct FormatMapping {
104 strftime: &'static str,
105 mysql: &'static str,
106 postgres: &'static str,
107 snowflake: &'static str,
108 java: &'static str,
109 tsql: &'static str,
110 clickhouse: &'static str,
111}
112
113impl FormatMapping {
114 fn get(&self, style: TimeFormatStyle) -> &'static str {
116 match style {
117 TimeFormatStyle::Strftime => self.strftime,
118 TimeFormatStyle::Mysql => self.mysql,
119 TimeFormatStyle::Postgres => self.postgres,
120 TimeFormatStyle::Snowflake => self.snowflake,
121 TimeFormatStyle::Java => self.java,
122 TimeFormatStyle::Tsql => self.tsql,
123 TimeFormatStyle::ClickHouse => self.clickhouse,
124 }
125 }
126}
127
128fn build_format_mappings() -> Vec<FormatMapping> {
131 vec![
132 FormatMapping {
134 strftime: "%Y", mysql: "%Y",
136 postgres: "YYYY",
137 snowflake: "YYYY",
138 java: "yyyy",
139 tsql: "yyyy",
140 clickhouse: "%Y",
141 },
142 FormatMapping {
143 strftime: "%y", mysql: "%y",
145 postgres: "YY",
146 snowflake: "YY",
147 java: "yy",
148 tsql: "yy",
149 clickhouse: "%y",
150 },
151 FormatMapping {
153 strftime: "%m", mysql: "%m",
155 postgres: "MM",
156 snowflake: "MM",
157 java: "MM",
158 tsql: "MM",
159 clickhouse: "%m",
160 },
161 FormatMapping {
162 strftime: "%b", mysql: "%b",
164 postgres: "Mon",
165 snowflake: "MON",
166 java: "MMM",
167 tsql: "MMM",
168 clickhouse: "%b",
169 },
170 FormatMapping {
171 strftime: "%B", mysql: "%M",
173 postgres: "Month",
174 snowflake: "MMMM",
175 java: "MMMM",
176 tsql: "MMMM",
177 clickhouse: "%B",
178 },
179 FormatMapping {
181 strftime: "%d", mysql: "%d",
183 postgres: "DD",
184 snowflake: "DD",
185 java: "dd",
186 tsql: "dd",
187 clickhouse: "%d",
188 },
189 FormatMapping {
190 strftime: "%e", mysql: "%e",
192 postgres: "FMDD",
193 snowflake: "DD", java: "d",
195 tsql: "d",
196 clickhouse: "%e",
197 },
198 FormatMapping {
199 strftime: "%j", mysql: "%j",
201 postgres: "DDD",
202 snowflake: "DDD",
203 java: "DDD",
204 tsql: "", clickhouse: "%j",
206 },
207 FormatMapping {
209 strftime: "%a", mysql: "%a",
211 postgres: "Dy",
212 snowflake: "DY",
213 java: "EEE",
214 tsql: "ddd",
215 clickhouse: "%a",
216 },
217 FormatMapping {
218 strftime: "%A", mysql: "%W",
220 postgres: "Day",
221 snowflake: "DY", java: "EEEE",
223 tsql: "dddd",
224 clickhouse: "%A",
225 },
226 FormatMapping {
227 strftime: "%w", mysql: "%w",
229 postgres: "D",
230 snowflake: "D",
231 java: "e",
232 tsql: "",
233 clickhouse: "%w",
234 },
235 FormatMapping {
236 strftime: "%u", mysql: "%u",
238 postgres: "ID",
239 snowflake: "ID",
240 java: "u",
241 tsql: "",
242 clickhouse: "%u",
243 },
244 FormatMapping {
246 strftime: "%W", mysql: "%v", postgres: "IW",
249 snowflake: "WW",
250 java: "ww",
251 tsql: "ww",
252 clickhouse: "%V",
253 },
254 FormatMapping {
255 strftime: "%U", mysql: "%U",
257 postgres: "WW",
258 snowflake: "WW",
259 java: "ww",
260 tsql: "ww",
261 clickhouse: "%U",
262 },
263 FormatMapping {
265 strftime: "%H", mysql: "%H",
267 postgres: "HH24",
268 snowflake: "HH24",
269 java: "HH",
270 tsql: "HH",
271 clickhouse: "%H",
272 },
273 FormatMapping {
274 strftime: "%I", mysql: "%h",
276 postgres: "HH12",
277 snowflake: "HH12",
278 java: "hh",
279 tsql: "hh",
280 clickhouse: "%I",
281 },
282 FormatMapping {
284 strftime: "%M", mysql: "%i", postgres: "MI",
287 snowflake: "MI",
288 java: "mm",
289 tsql: "mm",
290 clickhouse: "%M",
291 },
292 FormatMapping {
294 strftime: "%S", mysql: "%s",
296 postgres: "SS",
297 snowflake: "SS",
298 java: "ss",
299 tsql: "ss",
300 clickhouse: "%S",
301 },
302 FormatMapping {
304 strftime: "%f", mysql: "%f",
306 postgres: "US", snowflake: "FF6",
308 java: "SSSSSS",
309 tsql: "ffffff",
310 clickhouse: "%f",
311 },
312 FormatMapping {
314 strftime: "%p", mysql: "%p",
316 postgres: "AM",
317 snowflake: "AM",
318 java: "a",
319 tsql: "tt",
320 clickhouse: "%p",
321 },
322 FormatMapping {
324 strftime: "%z", mysql: "", postgres: "OF",
327 snowflake: "TZH:TZM",
328 java: "Z",
329 tsql: "zzz",
330 clickhouse: "%z",
331 },
332 FormatMapping {
333 strftime: "%Z", mysql: "",
335 postgres: "TZ",
336 snowflake: "TZR",
337 java: "z",
338 tsql: "",
339 clickhouse: "%Z",
340 },
341 FormatMapping {
343 strftime: "%%", mysql: "%%",
345 postgres: "", snowflake: "",
347 java: "",
348 tsql: "",
349 clickhouse: "%%",
350 },
351 ]
352}
353
354fn get_format_mappings() -> &'static Vec<FormatMapping> {
356 use std::sync::OnceLock;
357 static MAPPINGS: OnceLock<Vec<FormatMapping>> = OnceLock::new();
358 MAPPINGS.get_or_init(build_format_mappings)
359}
360
361#[allow(dead_code)]
366fn build_style_lookup(style: TimeFormatStyle) -> HashMap<&'static str, usize> {
367 let mappings = get_format_mappings();
368 let mut lookup = HashMap::new();
369 for (i, mapping) in mappings.iter().enumerate() {
370 let spec = mapping.get(style);
371 if !spec.is_empty() {
372 lookup.insert(spec, i);
373 }
374 }
375 lookup
376}
377
378#[must_use]
404pub fn format_time(format_str: &str, source: TimeFormatStyle, target: TimeFormatStyle) -> String {
405 if source == target {
406 return format_str.to_string();
407 }
408
409 match source {
411 TimeFormatStyle::Strftime | TimeFormatStyle::Mysql | TimeFormatStyle::ClickHouse => {
412 convert_strftime_style(format_str, source, target)
413 }
414 TimeFormatStyle::Postgres => convert_postgres_style(format_str, target),
415 TimeFormatStyle::Snowflake => convert_snowflake_style(format_str, target),
416 TimeFormatStyle::Java | TimeFormatStyle::Tsql => {
417 convert_java_style(format_str, source, target)
418 }
419 }
420}
421
422fn convert_strftime_style(
424 format_str: &str,
425 source: TimeFormatStyle,
426 target: TimeFormatStyle,
427) -> String {
428 let mappings = get_format_mappings();
429 let mut result = String::with_capacity(format_str.len() * 2);
430 let mut chars = format_str.chars().peekable();
431
432 while let Some(ch) = chars.next() {
433 if ch == '%' {
434 if let Some(&next) = chars.peek() {
435 chars.next();
436 let spec = format!("%{}", next);
437
438 let mapped = mappings.iter().find(|m| m.get(source) == spec);
440
441 if let Some(mapping) = mapped {
442 let target_spec = mapping.get(target);
443 if target_spec.is_empty() {
444 result.push_str(&spec);
446 } else {
447 result.push_str(target_spec);
448 }
449 } else {
450 result.push_str(&spec);
452 }
453 } else {
454 result.push('%');
456 }
457 } else {
458 result.push(ch);
459 }
460 }
461
462 result
463}
464
465fn convert_postgres_style(format_str: &str, target: TimeFormatStyle) -> String {
467 let mappings = get_format_mappings();
468 let mut result = String::with_capacity(format_str.len() * 2);
469 let chars: Vec<char> = format_str.chars().collect();
470 let mut i = 0;
471
472 let pg_specifiers: &[&str] = &[
474 "YYYY", "MMMM", "Month", "Mon", "MM", "DDD", "DD", "Day", "Dy", "D", "HH24", "HH12", "HH",
475 "MI", "SS", "US", "AM", "PM", "TZH:TZM", "TZR", "TZ", "OF", "IW", "WW", "YY", "ID", "FMDD",
476 ];
477
478 while i < chars.len() {
479 let remaining: String = chars[i..].iter().collect();
480 let mut matched = false;
481
482 for spec in pg_specifiers {
484 if remaining.starts_with(spec)
485 || remaining.to_uppercase().starts_with(&spec.to_uppercase())
486 {
487 let mapping = mappings
489 .iter()
490 .find(|m| m.postgres.eq_ignore_ascii_case(spec));
491
492 if let Some(m) = mapping {
493 let target_spec = m.get(target);
494 if !target_spec.is_empty() {
495 result.push_str(target_spec);
496 } else {
497 result.push_str(spec);
498 }
499 } else {
500 result.push_str(spec);
501 }
502 i += spec.len();
503 matched = true;
504 break;
505 }
506 }
507
508 if !matched {
509 if chars[i] == '"' {
511 result.push(chars[i]);
512 i += 1;
513 while i < chars.len() && chars[i] != '"' {
514 result.push(chars[i]);
515 i += 1;
516 }
517 if i < chars.len() {
518 result.push(chars[i]); i += 1;
520 }
521 } else {
522 result.push(chars[i]);
523 i += 1;
524 }
525 }
526 }
527
528 result
529}
530
531fn convert_snowflake_style(format_str: &str, target: TimeFormatStyle) -> String {
533 let mappings = get_format_mappings();
534 let mut result = String::with_capacity(format_str.len() * 2);
535 let chars: Vec<char> = format_str.chars().collect();
536 let mut i = 0;
537
538 let sf_specifiers: &[&str] = &[
540 "YYYY", "MMMM", "MON", "MM", "DDD", "DD", "DY", "D", "HH24", "HH12", "HH", "MI", "SS",
541 "FF6", "FF3", "FF", "AM", "PM", "TZH:TZM", "TZR", "WW", "YY", "ID",
542 ];
543
544 while i < chars.len() {
545 let remaining: String = chars[i..].iter().collect();
546 let mut matched = false;
547
548 for spec in sf_specifiers {
549 if remaining.starts_with(spec)
550 || remaining.to_uppercase().starts_with(&spec.to_uppercase())
551 {
552 let mapping = mappings
553 .iter()
554 .find(|m| m.snowflake.eq_ignore_ascii_case(spec));
555
556 if let Some(m) = mapping {
557 let target_spec = m.get(target);
558 if !target_spec.is_empty() {
559 result.push_str(target_spec);
560 } else {
561 result.push_str(spec);
562 }
563 } else {
564 result.push_str(spec);
565 }
566 i += spec.len();
567 matched = true;
568 break;
569 }
570 }
571
572 if !matched {
573 if chars[i] == '"' {
575 result.push(chars[i]);
576 i += 1;
577 while i < chars.len() && chars[i] != '"' {
578 result.push(chars[i]);
579 i += 1;
580 }
581 if i < chars.len() {
582 result.push(chars[i]);
583 i += 1;
584 }
585 } else {
586 result.push(chars[i]);
587 i += 1;
588 }
589 }
590 }
591
592 result
593}
594
595fn convert_java_style(
597 format_str: &str,
598 source: TimeFormatStyle,
599 target: TimeFormatStyle,
600) -> String {
601 let mappings = get_format_mappings();
602 let mut result = String::with_capacity(format_str.len() * 2);
603 let chars: Vec<char> = format_str.chars().collect();
604 let mut i = 0;
605
606 let java_specifiers: &[&str] = &[
608 "yyyy", "YYYY", "yy", "YY", "MMMM", "MMM", "MM", "M", "dd", "d", "DDD", "EEEE", "EEE", "e",
609 "u", "HH", "hh", "H", "h", "mm", "m", "ss", "s", "SSSSSS", "SSS", "SS", "S", "a", "Z", "z",
610 "ww",
611 ];
612
613 while i < chars.len() {
614 let remaining: String = chars[i..].iter().collect();
615 let mut matched = false;
616
617 if chars[i] == '\'' {
619 result.push(chars[i]);
620 i += 1;
621 while i < chars.len() && chars[i] != '\'' {
622 result.push(chars[i]);
623 i += 1;
624 }
625 if i < chars.len() {
626 result.push(chars[i]);
627 i += 1;
628 }
629 continue;
630 }
631
632 for spec in java_specifiers {
633 if remaining.starts_with(spec) {
634 let mapping = mappings.iter().find(|m| {
635 let src_spec = m.get(source);
636 src_spec == *spec
637 });
638
639 if let Some(m) = mapping {
640 let target_spec = m.get(target);
641 if !target_spec.is_empty() {
642 result.push_str(target_spec);
643 } else {
644 result.push_str(spec);
645 }
646 } else {
647 result.push_str(spec);
648 }
649 i += spec.len();
650 matched = true;
651 break;
652 }
653 }
654
655 if !matched {
656 result.push(chars[i]);
657 i += 1;
658 }
659 }
660
661 result
662}
663
664#[must_use]
693pub fn format_time_dialect(
694 format_str: &str,
695 source_dialect: Dialect,
696 target_dialect: Dialect,
697) -> String {
698 let source_style = TimeFormatStyle::for_dialect(source_dialect);
699 let target_style = TimeFormatStyle::for_dialect(target_dialect);
700 format_time(format_str, source_style, target_style)
701}
702
703#[derive(Debug, Clone, Copy, PartialEq, Eq)]
712pub enum TsqlStyleCode {
713 Default100 = 100,
715 UsaDate = 101,
717 AnsiDate = 102,
719 BritishDate = 103,
721 GermanDate = 104,
723 ItalianDate = 105,
725 DayMonYear = 106,
727 MonDayYear = 107,
729 TimeOnly = 108,
731 UsaDashes = 110,
733 JapanDate = 111,
735 IsoBasic = 112,
737 TimeWithMs = 114,
739 OdbcCanonical = 120,
741 OdbcWithMs = 121,
743 Iso8601 = 126,
745 Iso8601Tz = 127,
747}
748
749impl TsqlStyleCode {
750 #[must_use]
754 pub fn to_format_pattern(&self) -> &'static str {
755 match self {
756 TsqlStyleCode::Default100 => "%b %d %Y %I:%M%p",
757 TsqlStyleCode::UsaDate => "%m/%d/%Y",
758 TsqlStyleCode::AnsiDate => "%Y.%m.%d",
759 TsqlStyleCode::BritishDate => "%d/%m/%Y",
760 TsqlStyleCode::GermanDate => "%d.%m.%Y",
761 TsqlStyleCode::ItalianDate => "%d-%m-%Y",
762 TsqlStyleCode::DayMonYear => "%d %b %Y",
763 TsqlStyleCode::MonDayYear => "%b %d, %Y",
764 TsqlStyleCode::TimeOnly => "%H:%M:%S",
765 TsqlStyleCode::UsaDashes => "%m-%d-%Y",
766 TsqlStyleCode::JapanDate => "%Y/%m/%d",
767 TsqlStyleCode::IsoBasic => "%Y%m%d",
768 TsqlStyleCode::TimeWithMs => "%H:%M:%S:%f",
769 TsqlStyleCode::OdbcCanonical => "%Y-%m-%d %H:%M:%S",
770 TsqlStyleCode::OdbcWithMs => "%Y-%m-%d %H:%M:%S.%f",
771 TsqlStyleCode::Iso8601 => "%Y-%m-%dT%H:%M:%S.%f",
772 TsqlStyleCode::Iso8601Tz => "%Y-%m-%dT%H:%M:%S.%fZ",
773 }
774 }
775
776 pub fn from_code(code: i32) -> Option<Self> {
778 match code {
779 100 => Some(TsqlStyleCode::Default100),
780 101 => Some(TsqlStyleCode::UsaDate),
781 102 => Some(TsqlStyleCode::AnsiDate),
782 103 => Some(TsqlStyleCode::BritishDate),
783 104 => Some(TsqlStyleCode::GermanDate),
784 105 => Some(TsqlStyleCode::ItalianDate),
785 106 => Some(TsqlStyleCode::DayMonYear),
786 107 => Some(TsqlStyleCode::MonDayYear),
787 108 => Some(TsqlStyleCode::TimeOnly),
788 110 => Some(TsqlStyleCode::UsaDashes),
789 111 => Some(TsqlStyleCode::JapanDate),
790 112 => Some(TsqlStyleCode::IsoBasic),
791 114 => Some(TsqlStyleCode::TimeWithMs),
792 120 => Some(TsqlStyleCode::OdbcCanonical),
793 121 => Some(TsqlStyleCode::OdbcWithMs),
794 126 => Some(TsqlStyleCode::Iso8601),
795 127 => Some(TsqlStyleCode::Iso8601Tz),
796 _ => None,
797 }
798 }
799
800 pub fn code(&self) -> i32 {
802 *self as i32
803 }
804}
805
806#[derive(Debug, Clone)]
812pub struct FormatConversionResult {
813 pub format: String,
815 pub warnings: Vec<String>,
817}
818
819#[must_use]
824pub fn format_time_with_warnings(
825 format_str: &str,
826 source: TimeFormatStyle,
827 target: TimeFormatStyle,
828) -> FormatConversionResult {
829 let mut warnings = Vec::new();
830 let mappings = get_format_mappings();
831
832 match source {
834 TimeFormatStyle::Strftime | TimeFormatStyle::Mysql | TimeFormatStyle::ClickHouse => {
835 let mut chars = format_str.chars().peekable();
836 while let Some(ch) = chars.next() {
837 if ch == '%'
838 && let Some(&next) = chars.peek()
839 {
840 chars.next();
841 let spec = format!("%{}", next);
842 let mapping = mappings.iter().find(|m| m.get(source) == spec);
843 if let Some(m) = mapping
844 && m.get(target).is_empty()
845 {
846 warnings.push(format!(
847 "Format specifier '{}' has no equivalent in target format",
848 spec
849 ));
850 }
851 }
852 }
853 }
854 _ => {
855 }
858 }
859
860 let format = format_time(format_str, source, target);
861 FormatConversionResult { format, warnings }
862}
863
864#[cfg(test)]
865mod tests {
866 use super::*;
867
868 #[test]
869 fn test_strftime_to_postgres() {
870 assert_eq!(
871 format_time(
872 "%Y-%m-%d",
873 TimeFormatStyle::Strftime,
874 TimeFormatStyle::Postgres
875 ),
876 "YYYY-MM-DD"
877 );
878 assert_eq!(
879 format_time(
880 "%H:%M:%S",
881 TimeFormatStyle::Strftime,
882 TimeFormatStyle::Postgres
883 ),
884 "HH24:MI:SS"
885 );
886 assert_eq!(
887 format_time(
888 "%Y-%m-%d %H:%M:%S",
889 TimeFormatStyle::Strftime,
890 TimeFormatStyle::Postgres
891 ),
892 "YYYY-MM-DD HH24:MI:SS"
893 );
894 }
895
896 #[test]
897 fn test_mysql_to_postgres() {
898 assert_eq!(
900 format_time(
901 "%Y-%m-%d %H:%i:%s",
902 TimeFormatStyle::Mysql,
903 TimeFormatStyle::Postgres
904 ),
905 "YYYY-MM-DD HH24:MI:SS"
906 );
907 }
908
909 #[test]
910 fn test_postgres_to_mysql() {
911 assert_eq!(
912 format_time(
913 "YYYY-MM-DD HH24:MI:SS",
914 TimeFormatStyle::Postgres,
915 TimeFormatStyle::Mysql
916 ),
917 "%Y-%m-%d %H:%i:%s"
918 );
919 }
920
921 #[test]
922 fn test_postgres_to_strftime() {
923 assert_eq!(
924 format_time(
925 "YYYY-MM-DD",
926 TimeFormatStyle::Postgres,
927 TimeFormatStyle::Strftime
928 ),
929 "%Y-%m-%d"
930 );
931 }
932
933 #[test]
934 fn test_strftime_to_java() {
935 assert_eq!(
936 format_time("%Y-%m-%d", TimeFormatStyle::Strftime, TimeFormatStyle::Java),
937 "yyyy-MM-dd"
938 );
939 assert_eq!(
940 format_time("%H:%M:%S", TimeFormatStyle::Strftime, TimeFormatStyle::Java),
941 "HH:mm:ss"
942 );
943 }
944
945 #[test]
946 fn test_java_to_strftime() {
947 assert_eq!(
948 format_time(
949 "yyyy-MM-dd",
950 TimeFormatStyle::Java,
951 TimeFormatStyle::Strftime
952 ),
953 "%Y-%m-%d"
954 );
955 assert_eq!(
956 format_time("HH:mm:ss", TimeFormatStyle::Java, TimeFormatStyle::Strftime),
957 "%H:%M:%S"
958 );
959 }
960
961 #[test]
962 fn test_strftime_to_snowflake() {
963 assert_eq!(
964 format_time(
965 "%Y-%m-%d",
966 TimeFormatStyle::Strftime,
967 TimeFormatStyle::Snowflake
968 ),
969 "YYYY-MM-DD"
970 );
971 }
972
973 #[test]
974 fn test_same_style_noop() {
975 let format = "%Y-%m-%d %H:%M:%S";
976 assert_eq!(
977 format_time(format, TimeFormatStyle::Strftime, TimeFormatStyle::Strftime),
978 format
979 );
980 }
981
982 #[test]
983 fn test_dialect_conversion() {
984 assert_eq!(
985 format_time_dialect("%Y-%m-%d %H:%i:%s", Dialect::Mysql, Dialect::Postgres),
986 "YYYY-MM-DD HH24:MI:SS"
987 );
988 assert_eq!(
989 format_time_dialect("YYYY-MM-DD HH24:MI:SS", Dialect::Postgres, Dialect::Spark),
990 "yyyy-MM-dd HH:mm:ss"
991 );
992 }
993
994 #[test]
995 fn test_literal_preservation() {
996 assert_eq!(
998 format_time(
999 "%Y/%m/%d",
1000 TimeFormatStyle::Strftime,
1001 TimeFormatStyle::Postgres
1002 ),
1003 "YYYY/MM/DD"
1004 );
1005 assert_eq!(
1006 format_time(
1007 "%Y at %H:%M",
1008 TimeFormatStyle::Strftime,
1009 TimeFormatStyle::Postgres
1010 ),
1011 "YYYY at HH24:MI"
1012 );
1013 }
1014
1015 #[test]
1016 fn test_tsql_style_codes() {
1017 assert_eq!(
1018 TsqlStyleCode::OdbcCanonical.to_format_pattern(),
1019 "%Y-%m-%d %H:%M:%S"
1020 );
1021 assert_eq!(TsqlStyleCode::UsaDate.to_format_pattern(), "%m/%d/%Y");
1022 assert_eq!(
1023 TsqlStyleCode::from_code(120),
1024 Some(TsqlStyleCode::OdbcCanonical)
1025 );
1026 assert_eq!(TsqlStyleCode::from_code(999), None);
1027 }
1028
1029 #[test]
1030 fn test_12hour_format() {
1031 assert_eq!(
1032 format_time(
1033 "%I:%M %p",
1034 TimeFormatStyle::Strftime,
1035 TimeFormatStyle::Postgres
1036 ),
1037 "HH12:MI AM"
1038 );
1039 }
1040
1041 #[test]
1042 fn test_month_names() {
1043 assert_eq!(
1044 format_time(
1045 "%b %d, %Y",
1046 TimeFormatStyle::Strftime,
1047 TimeFormatStyle::Postgres
1048 ),
1049 "Mon DD, YYYY"
1050 );
1051 assert_eq!(
1052 format_time("%B", TimeFormatStyle::Strftime, TimeFormatStyle::Mysql),
1053 "%M"
1054 );
1055 }
1056
1057 #[test]
1058 fn test_format_style_for_dialect() {
1059 assert_eq!(
1060 TimeFormatStyle::for_dialect(Dialect::Mysql),
1061 TimeFormatStyle::Mysql
1062 );
1063 assert_eq!(
1064 TimeFormatStyle::for_dialect(Dialect::Postgres),
1065 TimeFormatStyle::Postgres
1066 );
1067 assert_eq!(
1068 TimeFormatStyle::for_dialect(Dialect::Spark),
1069 TimeFormatStyle::Java
1070 );
1071 assert_eq!(
1072 TimeFormatStyle::for_dialect(Dialect::Snowflake),
1073 TimeFormatStyle::Snowflake
1074 );
1075 assert_eq!(
1076 TimeFormatStyle::for_dialect(Dialect::BigQuery),
1077 TimeFormatStyle::Strftime
1078 );
1079 }
1080}