1use crate::trie::{new_trie_from_keys, Trie, TrieResult};
10use std::collections::{HashMap, HashSet};
11
12pub fn format_time(
41 input: &str,
42 mapping: &HashMap<&str, &str>,
43 trie: Option<&Trie<()>>,
44) -> Option<String> {
45 if input.is_empty() {
46 return None;
47 }
48
49 let owned_trie;
51 let trie = match trie {
52 Some(t) => t,
53 None => {
54 owned_trie = build_format_trie(mapping);
55 &owned_trie
56 }
57 };
58
59 let chars: Vec<char> = input.chars().collect();
60 let size = chars.len();
61 let mut start = 0;
62 let mut end = 1;
63 let mut current = trie;
64 let mut chunks = Vec::new();
65 let mut sym: Option<String> = None;
66
67 while end <= size {
68 let ch = chars[end - 1];
69 let (result, subtrie) = current.in_trie_char(ch);
70
71 match result {
72 TrieResult::Failed => {
73 if let Some(ref matched) = sym {
74 end -= 1;
76 chunks.push(matched.clone());
77 start += matched.chars().count();
78 sym = None;
79 } else {
80 chunks.push(chars[start].to_string());
82 end = start + 1;
83 start += 1;
84 }
85 current = trie;
86 }
87 TrieResult::Exists => {
88 let matched: String = chars[start..end].iter().collect();
90 sym = Some(matched);
91 current = subtrie.unwrap_or(trie);
92 }
93 TrieResult::Prefix => {
94 current = subtrie.unwrap_or(trie);
96 }
97 }
98
99 end += 1;
100
101 if result != TrieResult::Failed && end > size {
103 let matched: String = chars[start..end - 1].iter().collect();
104 chunks.push(matched);
105 }
106 }
107
108 let result: String = chunks
110 .iter()
111 .map(|chunk| {
112 mapping
113 .get(chunk.as_str())
114 .map(|s| s.to_string())
115 .unwrap_or_else(|| chunk.clone())
116 })
117 .collect();
118
119 Some(result)
120}
121
122pub fn build_format_trie(mapping: &HashMap<&str, &str>) -> Trie<()> {
127 new_trie_from_keys(mapping.keys().copied())
128}
129
130pub fn subsecond_precision(timestamp_literal: &str) -> u8 {
151 let dot_pos = match timestamp_literal.find('.') {
153 Some(pos) => pos,
154 None => return 0,
155 };
156
157 let frac_end = timestamp_literal[dot_pos + 1..]
159 .find(|c: char| !c.is_ascii_digit())
160 .map(|pos| pos + dot_pos + 1)
161 .unwrap_or(timestamp_literal.len());
162
163 let frac_len = frac_end - dot_pos - 1;
164
165 let frac_part = ×tamp_literal[dot_pos + 1..frac_end];
167 let significant = frac_part.trim_end_matches('0').len();
168
169 if significant > 3 {
170 6
171 } else if significant > 0 {
172 3
173 } else if frac_len > 0 {
174 if frac_len > 3 {
176 6
177 } else {
178 3
179 }
180 } else {
181 0
182 }
183}
184
185pub static TIMEZONES: std::sync::LazyLock<HashSet<&'static str>> =
192 std::sync::LazyLock::new(|| {
193 let tzs = [
194 "africa/abidjan",
196 "africa/accra",
197 "africa/addis_ababa",
198 "africa/algiers",
199 "africa/asmara",
200 "africa/bamako",
201 "africa/bangui",
202 "africa/banjul",
203 "africa/bissau",
204 "africa/blantyre",
205 "africa/brazzaville",
206 "africa/bujumbura",
207 "africa/cairo",
208 "africa/casablanca",
209 "africa/ceuta",
210 "africa/conakry",
211 "africa/dakar",
212 "africa/dar_es_salaam",
213 "africa/djibouti",
214 "africa/douala",
215 "africa/el_aaiun",
216 "africa/freetown",
217 "africa/gaborone",
218 "africa/harare",
219 "africa/johannesburg",
220 "africa/juba",
221 "africa/kampala",
222 "africa/khartoum",
223 "africa/kigali",
224 "africa/kinshasa",
225 "africa/lagos",
226 "africa/libreville",
227 "africa/lome",
228 "africa/luanda",
229 "africa/lubumbashi",
230 "africa/lusaka",
231 "africa/malabo",
232 "africa/maputo",
233 "africa/maseru",
234 "africa/mbabane",
235 "africa/mogadishu",
236 "africa/monrovia",
237 "africa/nairobi",
238 "africa/ndjamena",
239 "africa/niamey",
240 "africa/nouakchott",
241 "africa/ouagadougou",
242 "africa/porto-novo",
243 "africa/sao_tome",
244 "africa/tripoli",
245 "africa/tunis",
246 "africa/windhoek",
247 "america/adak",
249 "america/anchorage",
250 "america/anguilla",
251 "america/antigua",
252 "america/araguaina",
253 "america/argentina/buenos_aires",
254 "america/argentina/catamarca",
255 "america/argentina/cordoba",
256 "america/argentina/jujuy",
257 "america/argentina/la_rioja",
258 "america/argentina/mendoza",
259 "america/argentina/rio_gallegos",
260 "america/argentina/salta",
261 "america/argentina/san_juan",
262 "america/argentina/san_luis",
263 "america/argentina/tucuman",
264 "america/argentina/ushuaia",
265 "america/aruba",
266 "america/asuncion",
267 "america/atikokan",
268 "america/bahia",
269 "america/bahia_banderas",
270 "america/barbados",
271 "america/belem",
272 "america/belize",
273 "america/blanc-sablon",
274 "america/boa_vista",
275 "america/bogota",
276 "america/boise",
277 "america/cambridge_bay",
278 "america/campo_grande",
279 "america/cancun",
280 "america/caracas",
281 "america/cayenne",
282 "america/cayman",
283 "america/chicago",
284 "america/chihuahua",
285 "america/ciudad_juarez",
286 "america/costa_rica",
287 "america/creston",
288 "america/cuiaba",
289 "america/curacao",
290 "america/danmarkshavn",
291 "america/dawson",
292 "america/dawson_creek",
293 "america/denver",
294 "america/detroit",
295 "america/dominica",
296 "america/edmonton",
297 "america/eirunepe",
298 "america/el_salvador",
299 "america/fort_nelson",
300 "america/fortaleza",
301 "america/glace_bay",
302 "america/goose_bay",
303 "america/grand_turk",
304 "america/grenada",
305 "america/guadeloupe",
306 "america/guatemala",
307 "america/guayaquil",
308 "america/guyana",
309 "america/halifax",
310 "america/havana",
311 "america/hermosillo",
312 "america/indiana/indianapolis",
313 "america/indiana/knox",
314 "america/indiana/marengo",
315 "america/indiana/petersburg",
316 "america/indiana/tell_city",
317 "america/indiana/vevay",
318 "america/indiana/vincennes",
319 "america/indiana/winamac",
320 "america/inuvik",
321 "america/iqaluit",
322 "america/jamaica",
323 "america/juneau",
324 "america/kentucky/louisville",
325 "america/kentucky/monticello",
326 "america/kralendijk",
327 "america/la_paz",
328 "america/lima",
329 "america/los_angeles",
330 "america/lower_princes",
331 "america/maceio",
332 "america/managua",
333 "america/manaus",
334 "america/marigot",
335 "america/martinique",
336 "america/matamoros",
337 "america/mazatlan",
338 "america/menominee",
339 "america/merida",
340 "america/metlakatla",
341 "america/mexico_city",
342 "america/miquelon",
343 "america/moncton",
344 "america/monterrey",
345 "america/montevideo",
346 "america/montserrat",
347 "america/nassau",
348 "america/new_york",
349 "america/nipigon",
350 "america/nome",
351 "america/noronha",
352 "america/north_dakota/beulah",
353 "america/north_dakota/center",
354 "america/north_dakota/new_salem",
355 "america/nuuk",
356 "america/ojinaga",
357 "america/panama",
358 "america/pangnirtung",
359 "america/paramaribo",
360 "america/phoenix",
361 "america/port-au-prince",
362 "america/port_of_spain",
363 "america/porto_velho",
364 "america/puerto_rico",
365 "america/punta_arenas",
366 "america/rainy_river",
367 "america/rankin_inlet",
368 "america/recife",
369 "america/regina",
370 "america/resolute",
371 "america/rio_branco",
372 "america/santarem",
373 "america/santiago",
374 "america/santo_domingo",
375 "america/sao_paulo",
376 "america/scoresbysund",
377 "america/sitka",
378 "america/st_barthelemy",
379 "america/st_johns",
380 "america/st_kitts",
381 "america/st_lucia",
382 "america/st_thomas",
383 "america/st_vincent",
384 "america/swift_current",
385 "america/tegucigalpa",
386 "america/thule",
387 "america/thunder_bay",
388 "america/tijuana",
389 "america/toronto",
390 "america/tortola",
391 "america/vancouver",
392 "america/whitehorse",
393 "america/winnipeg",
394 "america/yakutat",
395 "america/yellowknife",
396 "antarctica/casey",
398 "antarctica/davis",
399 "antarctica/dumontdurville",
400 "antarctica/macquarie",
401 "antarctica/mawson",
402 "antarctica/mcmurdo",
403 "antarctica/palmer",
404 "antarctica/rothera",
405 "antarctica/syowa",
406 "antarctica/troll",
407 "antarctica/vostok",
408 "arctic/longyearbyen",
410 "asia/aden",
412 "asia/almaty",
413 "asia/amman",
414 "asia/anadyr",
415 "asia/aqtau",
416 "asia/aqtobe",
417 "asia/ashgabat",
418 "asia/atyrau",
419 "asia/baghdad",
420 "asia/bahrain",
421 "asia/baku",
422 "asia/bangkok",
423 "asia/barnaul",
424 "asia/beirut",
425 "asia/bishkek",
426 "asia/brunei",
427 "asia/chita",
428 "asia/choibalsan",
429 "asia/colombo",
430 "asia/damascus",
431 "asia/dhaka",
432 "asia/dili",
433 "asia/dubai",
434 "asia/dushanbe",
435 "asia/famagusta",
436 "asia/gaza",
437 "asia/hebron",
438 "asia/ho_chi_minh",
439 "asia/hong_kong",
440 "asia/hovd",
441 "asia/irkutsk",
442 "asia/jakarta",
443 "asia/jayapura",
444 "asia/jerusalem",
445 "asia/kabul",
446 "asia/kamchatka",
447 "asia/karachi",
448 "asia/kathmandu",
449 "asia/khandyga",
450 "asia/kolkata",
451 "asia/krasnoyarsk",
452 "asia/kuala_lumpur",
453 "asia/kuching",
454 "asia/kuwait",
455 "asia/macau",
456 "asia/magadan",
457 "asia/makassar",
458 "asia/manila",
459 "asia/muscat",
460 "asia/nicosia",
461 "asia/novokuznetsk",
462 "asia/novosibirsk",
463 "asia/omsk",
464 "asia/oral",
465 "asia/phnom_penh",
466 "asia/pontianak",
467 "asia/pyongyang",
468 "asia/qatar",
469 "asia/qostanay",
470 "asia/qyzylorda",
471 "asia/riyadh",
472 "asia/sakhalin",
473 "asia/samarkand",
474 "asia/seoul",
475 "asia/shanghai",
476 "asia/singapore",
477 "asia/srednekolymsk",
478 "asia/taipei",
479 "asia/tashkent",
480 "asia/tbilisi",
481 "asia/tehran",
482 "asia/thimphu",
483 "asia/tokyo",
484 "asia/tomsk",
485 "asia/ulaanbaatar",
486 "asia/urumqi",
487 "asia/ust-nera",
488 "asia/vientiane",
489 "asia/vladivostok",
490 "asia/yakutsk",
491 "asia/yangon",
492 "asia/yekaterinburg",
493 "asia/yerevan",
494 "atlantic/azores",
496 "atlantic/bermuda",
497 "atlantic/canary",
498 "atlantic/cape_verde",
499 "atlantic/faroe",
500 "atlantic/madeira",
501 "atlantic/reykjavik",
502 "atlantic/south_georgia",
503 "atlantic/st_helena",
504 "atlantic/stanley",
505 "australia/adelaide",
507 "australia/brisbane",
508 "australia/broken_hill",
509 "australia/darwin",
510 "australia/eucla",
511 "australia/hobart",
512 "australia/lindeman",
513 "australia/lord_howe",
514 "australia/melbourne",
515 "australia/perth",
516 "australia/sydney",
517 "europe/amsterdam",
519 "europe/andorra",
520 "europe/astrakhan",
521 "europe/athens",
522 "europe/belgrade",
523 "europe/berlin",
524 "europe/bratislava",
525 "europe/brussels",
526 "europe/bucharest",
527 "europe/budapest",
528 "europe/busingen",
529 "europe/chisinau",
530 "europe/copenhagen",
531 "europe/dublin",
532 "europe/gibraltar",
533 "europe/guernsey",
534 "europe/helsinki",
535 "europe/isle_of_man",
536 "europe/istanbul",
537 "europe/jersey",
538 "europe/kaliningrad",
539 "europe/kiev",
540 "europe/kirov",
541 "europe/kyiv",
542 "europe/lisbon",
543 "europe/ljubljana",
544 "europe/london",
545 "europe/luxembourg",
546 "europe/madrid",
547 "europe/malta",
548 "europe/mariehamn",
549 "europe/minsk",
550 "europe/monaco",
551 "europe/moscow",
552 "europe/oslo",
553 "europe/paris",
554 "europe/podgorica",
555 "europe/prague",
556 "europe/riga",
557 "europe/rome",
558 "europe/samara",
559 "europe/san_marino",
560 "europe/sarajevo",
561 "europe/saratov",
562 "europe/simferopol",
563 "europe/skopje",
564 "europe/sofia",
565 "europe/stockholm",
566 "europe/tallinn",
567 "europe/tirane",
568 "europe/ulyanovsk",
569 "europe/uzhgorod",
570 "europe/vaduz",
571 "europe/vatican",
572 "europe/vienna",
573 "europe/vilnius",
574 "europe/volgograd",
575 "europe/warsaw",
576 "europe/zagreb",
577 "europe/zaporozhye",
578 "europe/zurich",
579 "indian/antananarivo",
581 "indian/chagos",
582 "indian/christmas",
583 "indian/cocos",
584 "indian/comoro",
585 "indian/kerguelen",
586 "indian/mahe",
587 "indian/maldives",
588 "indian/mauritius",
589 "indian/mayotte",
590 "indian/reunion",
591 "pacific/apia",
593 "pacific/auckland",
594 "pacific/bougainville",
595 "pacific/chatham",
596 "pacific/chuuk",
597 "pacific/easter",
598 "pacific/efate",
599 "pacific/fakaofo",
600 "pacific/fiji",
601 "pacific/funafuti",
602 "pacific/galapagos",
603 "pacific/gambier",
604 "pacific/guadalcanal",
605 "pacific/guam",
606 "pacific/honolulu",
607 "pacific/kanton",
608 "pacific/kiritimati",
609 "pacific/kosrae",
610 "pacific/kwajalein",
611 "pacific/majuro",
612 "pacific/marquesas",
613 "pacific/midway",
614 "pacific/nauru",
615 "pacific/niue",
616 "pacific/norfolk",
617 "pacific/noumea",
618 "pacific/pago_pago",
619 "pacific/palau",
620 "pacific/pitcairn",
621 "pacific/pohnpei",
622 "pacific/port_moresby",
623 "pacific/rarotonga",
624 "pacific/saipan",
625 "pacific/tahiti",
626 "pacific/tarawa",
627 "pacific/tongatapu",
628 "pacific/wake",
629 "pacific/wallis",
630 "utc",
632 "gmt",
633 "est",
634 "edt",
635 "cst",
636 "cdt",
637 "mst",
638 "mdt",
639 "pst",
640 "pdt",
641 "cet",
642 "cest",
643 "wet",
644 "west",
645 "eet",
646 "eest",
647 "gmt+0",
648 "gmt-0",
649 "gmt0",
650 "etc/gmt",
651 "etc/utc",
652 "etc/gmt+0",
653 "etc/gmt-0",
654 "etc/gmt+1",
655 "etc/gmt+2",
656 "etc/gmt+3",
657 "etc/gmt+4",
658 "etc/gmt+5",
659 "etc/gmt+6",
660 "etc/gmt+7",
661 "etc/gmt+8",
662 "etc/gmt+9",
663 "etc/gmt+10",
664 "etc/gmt+11",
665 "etc/gmt+12",
666 "etc/gmt-1",
667 "etc/gmt-2",
668 "etc/gmt-3",
669 "etc/gmt-4",
670 "etc/gmt-5",
671 "etc/gmt-6",
672 "etc/gmt-7",
673 "etc/gmt-8",
674 "etc/gmt-9",
675 "etc/gmt-10",
676 "etc/gmt-11",
677 "etc/gmt-12",
678 "etc/gmt-13",
679 "etc/gmt-14",
680 ];
681 tzs.into_iter().collect()
682 });
683
684pub fn is_valid_timezone(tz: &str) -> bool {
696 TIMEZONES.contains(tz.to_lowercase().as_str())
697}
698
699pub mod format_mappings {
701 use std::collections::HashMap;
702
703 pub fn python_to_snowflake() -> HashMap<&'static str, &'static str> {
705 let mut m = HashMap::new();
706 m.insert("%Y", "YYYY");
707 m.insert("%y", "YY");
708 m.insert("%m", "MM");
709 m.insert("%d", "DD");
710 m.insert("%H", "HH24");
711 m.insert("%I", "HH12");
712 m.insert("%M", "MI");
713 m.insert("%S", "SS");
714 m.insert("%f", "FF6");
715 m.insert("%p", "AM");
716 m.insert("%j", "DDD");
717 m.insert("%W", "WW");
718 m.insert("%w", "D");
719 m.insert("%b", "MON");
720 m.insert("%B", "MONTH");
721 m.insert("%a", "DY");
722 m.insert("%A", "DAY");
723 m.insert("%z", "TZH:TZM");
724 m.insert("%Z", "TZR");
725 m
726 }
727
728 pub fn python_to_bigquery() -> HashMap<&'static str, &'static str> {
730 let mut m = HashMap::new();
731 m.insert("%Y", "%Y");
732 m.insert("%y", "%y");
733 m.insert("%m", "%m");
734 m.insert("%d", "%d");
735 m.insert("%H", "%H");
736 m.insert("%I", "%I");
737 m.insert("%M", "%M");
738 m.insert("%S", "%S");
739 m.insert("%f", "%E6S");
740 m.insert("%p", "%p");
741 m.insert("%j", "%j");
742 m.insert("%W", "%W");
743 m.insert("%w", "%w");
744 m.insert("%b", "%b");
745 m.insert("%B", "%B");
746 m.insert("%a", "%a");
747 m.insert("%A", "%A");
748 m.insert("%z", "%z");
749 m.insert("%Z", "%Z");
750 m
751 }
752
753 pub fn python_to_mysql() -> HashMap<&'static str, &'static str> {
755 let mut m = HashMap::new();
756 m.insert("%Y", "%Y");
757 m.insert("%y", "%y");
758 m.insert("%m", "%m");
759 m.insert("%d", "%d");
760 m.insert("%H", "%H");
761 m.insert("%I", "%h");
762 m.insert("%M", "%i");
763 m.insert("%S", "%s");
764 m.insert("%f", "%f");
765 m.insert("%p", "%p");
766 m.insert("%j", "%j");
767 m.insert("%W", "%U");
768 m.insert("%w", "%w");
769 m.insert("%b", "%b");
770 m.insert("%B", "%M");
771 m.insert("%a", "%a");
772 m.insert("%A", "%W");
773 m
774 }
775
776 pub fn python_to_postgres() -> HashMap<&'static str, &'static str> {
778 let mut m = HashMap::new();
779 m.insert("%Y", "YYYY");
780 m.insert("%y", "YY");
781 m.insert("%m", "MM");
782 m.insert("%d", "DD");
783 m.insert("%H", "HH24");
784 m.insert("%I", "HH12");
785 m.insert("%M", "MI");
786 m.insert("%S", "SS");
787 m.insert("%f", "US");
788 m.insert("%p", "AM");
789 m.insert("%j", "DDD");
790 m.insert("%W", "WW");
791 m.insert("%w", "D");
792 m.insert("%b", "Mon");
793 m.insert("%B", "Month");
794 m.insert("%a", "Dy");
795 m.insert("%A", "Day");
796 m.insert("%z", "OF");
797 m.insert("%Z", "TZ");
798 m
799 }
800
801 pub fn python_to_oracle() -> HashMap<&'static str, &'static str> {
803 let mut m = HashMap::new();
804 m.insert("%Y", "YYYY");
805 m.insert("%y", "YY");
806 m.insert("%m", "MM");
807 m.insert("%d", "DD");
808 m.insert("%H", "HH24");
809 m.insert("%I", "HH");
810 m.insert("%M", "MI");
811 m.insert("%S", "SS");
812 m.insert("%f", "FF6");
813 m.insert("%p", "AM");
814 m.insert("%j", "DDD");
815 m.insert("%W", "WW");
816 m.insert("%w", "D");
817 m.insert("%b", "MON");
818 m.insert("%B", "MONTH");
819 m.insert("%a", "DY");
820 m.insert("%A", "DAY");
821 m.insert("%z", "TZH:TZM");
822 m.insert("%Z", "TZR");
823 m
824 }
825
826 pub fn python_to_spark() -> HashMap<&'static str, &'static str> {
828 let mut m = HashMap::new();
829 m.insert("%Y", "yyyy");
830 m.insert("%y", "yy");
831 m.insert("%m", "MM");
832 m.insert("%d", "dd");
833 m.insert("%H", "HH");
834 m.insert("%I", "hh");
835 m.insert("%M", "mm");
836 m.insert("%S", "ss");
837 m.insert("%f", "SSSSSS");
838 m.insert("%p", "a");
839 m.insert("%j", "D");
840 m.insert("%W", "w");
841 m.insert("%w", "u");
842 m.insert("%b", "MMM");
843 m.insert("%B", "MMMM");
844 m.insert("%a", "E");
845 m.insert("%A", "EEEE");
846 m.insert("%z", "XXX");
847 m.insert("%Z", "z");
848 m
849 }
850}
851
852#[cfg(test)]
853mod tests {
854 use super::*;
855
856 #[test]
857 fn test_format_time_basic() {
858 let mut mapping = HashMap::new();
859 mapping.insert("%Y", "YYYY");
860
861 let result = format_time("%Y", &mapping, None);
862 assert_eq!(result, Some("YYYY".to_string()));
863 }
864
865 #[test]
866 fn test_format_time_multiple() {
867 let mut mapping = HashMap::new();
868 mapping.insert("%Y", "YYYY");
869 mapping.insert("%m", "MM");
870 mapping.insert("%d", "DD");
871
872 let result = format_time("%Y-%m-%d", &mapping, None);
873 assert_eq!(result, Some("YYYY-MM-DD".to_string()));
874 }
875
876 #[test]
877 fn test_format_time_empty() {
878 let mapping = HashMap::new();
879 assert_eq!(format_time("", &mapping, None), None);
880 }
881
882 #[test]
883 fn test_format_time_no_mapping() {
884 let mapping = HashMap::new();
885 let result = format_time("hello", &mapping, None);
886 assert_eq!(result, Some("hello".to_string()));
887 }
888
889 #[test]
890 fn test_format_time_partial_match() {
891 let mut mapping = HashMap::new();
892 mapping.insert("%Y", "YYYY");
893 let result = format_time("%Y %y", &mapping, None);
896 assert_eq!(result, Some("YYYY %y".to_string()));
898 }
899
900 #[test]
901 fn test_subsecond_precision_none() {
902 assert_eq!(subsecond_precision("2023-01-01 12:13:14"), 0);
903 }
904
905 #[test]
906 fn test_subsecond_precision_milliseconds() {
907 assert_eq!(subsecond_precision("2023-01-01 12:13:14.123"), 3);
908 assert_eq!(subsecond_precision("2023-01-01 12:13:14.100"), 3);
909 }
910
911 #[test]
912 fn test_subsecond_precision_microseconds() {
913 assert_eq!(subsecond_precision("2023-01-01 12:13:14.123456"), 6);
914 assert_eq!(subsecond_precision("2023-01-01 12:13:14.123456+00:00"), 6);
915 }
916
917 #[test]
918 fn test_subsecond_precision_with_timezone() {
919 assert_eq!(
920 subsecond_precision("2023-01-01 12:13:14.123+00:00"),
921 3
922 );
923 assert_eq!(
924 subsecond_precision("2023-01-01T12:13:14.123456Z"),
925 6
926 );
927 }
928
929 #[test]
930 fn test_is_valid_timezone() {
931 assert!(is_valid_timezone("UTC"));
932 assert!(is_valid_timezone("utc"));
933 assert!(is_valid_timezone("America/New_York"));
934 assert!(is_valid_timezone("america/new_york"));
935 assert!(is_valid_timezone("Europe/London"));
936 assert!(!is_valid_timezone("Invalid/Timezone"));
937 assert!(!is_valid_timezone("NotATimezone"));
938 }
939
940 #[test]
941 fn test_python_to_snowflake() {
942 let mapping = format_mappings::python_to_snowflake();
943 let result = format_time("%Y-%m-%d %H:%M:%S", &mapping, None);
944 assert_eq!(result, Some("YYYY-MM-DD HH24:MI:SS".to_string()));
945 }
946
947 #[test]
948 fn test_python_to_spark() {
949 let mapping = format_mappings::python_to_spark();
950 let result = format_time("%Y-%m-%d %H:%M:%S", &mapping, None);
951 assert_eq!(result, Some("yyyy-MM-dd HH:mm:ss".to_string()));
952 }
953}