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