Skip to main content

fiscal_core/
timezone.rs

1//! Timezone lookup by Brazilian state (UF).
2//!
3//! Maps each of the 27 Brazilian states (UFs) to its IANA timezone string,
4//! mirroring the PHP `NFePHP\Common\TimeZoneByUF` class.
5
6/// Returns the IANA timezone string for a given Brazilian state abbreviation (UF).
7///
8/// The input is case-insensitive. Returns `None` if the UF is not recognized.
9///
10/// # Examples
11///
12/// ```
13/// use fiscal_core::timezone::timezone_for_uf;
14///
15/// assert_eq!(timezone_for_uf("SP"), Some("America/Sao_Paulo"));
16/// assert_eq!(timezone_for_uf("am"), Some("America/Manaus"));
17/// assert_eq!(timezone_for_uf("XX"), None);
18/// ```
19pub fn timezone_for_uf(uf: &str) -> Option<&'static str> {
20    // Normalize to uppercase for case-insensitive matching.
21    // We avoid heap allocation by matching on the two-char slice directly.
22    if uf.len() != 2 {
23        return None;
24    }
25
26    let bytes = uf.as_bytes();
27    let upper = [bytes[0].to_ascii_uppercase(), bytes[1].to_ascii_uppercase()];
28
29    match &upper {
30        b"AC" => Some("America/Rio_Branco"),
31        b"AL" => Some("America/Maceio"),
32        b"AM" => Some("America/Manaus"),
33        b"AP" => Some("America/Belem"),
34        b"BA" => Some("America/Bahia"),
35        b"CE" => Some("America/Fortaleza"),
36        b"DF" => Some("America/Sao_Paulo"),
37        b"ES" => Some("America/Sao_Paulo"),
38        b"GO" => Some("America/Sao_Paulo"),
39        b"MA" => Some("America/Fortaleza"),
40        b"MG" => Some("America/Sao_Paulo"),
41        b"MS" => Some("America/Campo_Grande"),
42        b"MT" => Some("America/Cuiaba"),
43        b"PA" => Some("America/Belem"),
44        b"PB" => Some("America/Fortaleza"),
45        b"PE" => Some("America/Recife"),
46        b"PI" => Some("America/Fortaleza"),
47        b"PR" => Some("America/Sao_Paulo"),
48        b"RJ" => Some("America/Sao_Paulo"),
49        b"RN" => Some("America/Fortaleza"),
50        b"RO" => Some("America/Porto_Velho"),
51        b"RR" => Some("America/Boa_Vista"),
52        b"RS" => Some("America/Sao_Paulo"),
53        b"SC" => Some("America/Sao_Paulo"),
54        b"SE" => Some("America/Maceio"),
55        b"SP" => Some("America/Sao_Paulo"),
56        b"TO" => Some("America/Araguaina"),
57        _ => None,
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn test_all_ufs() {
67        assert_eq!(timezone_for_uf("AC"), Some("America/Rio_Branco"));
68        assert_eq!(timezone_for_uf("AL"), Some("America/Maceio"));
69        assert_eq!(timezone_for_uf("AM"), Some("America/Manaus"));
70        assert_eq!(timezone_for_uf("AP"), Some("America/Belem"));
71        assert_eq!(timezone_for_uf("BA"), Some("America/Bahia"));
72        assert_eq!(timezone_for_uf("CE"), Some("America/Fortaleza"));
73        assert_eq!(timezone_for_uf("DF"), Some("America/Sao_Paulo"));
74        assert_eq!(timezone_for_uf("ES"), Some("America/Sao_Paulo"));
75        assert_eq!(timezone_for_uf("GO"), Some("America/Sao_Paulo"));
76        assert_eq!(timezone_for_uf("MA"), Some("America/Fortaleza"));
77        assert_eq!(timezone_for_uf("MG"), Some("America/Sao_Paulo"));
78        assert_eq!(timezone_for_uf("MS"), Some("America/Campo_Grande"));
79        assert_eq!(timezone_for_uf("MT"), Some("America/Cuiaba"));
80        assert_eq!(timezone_for_uf("PA"), Some("America/Belem"));
81        assert_eq!(timezone_for_uf("PB"), Some("America/Fortaleza"));
82        assert_eq!(timezone_for_uf("PE"), Some("America/Recife"));
83        assert_eq!(timezone_for_uf("PI"), Some("America/Fortaleza"));
84        assert_eq!(timezone_for_uf("PR"), Some("America/Sao_Paulo"));
85        assert_eq!(timezone_for_uf("RJ"), Some("America/Sao_Paulo"));
86        assert_eq!(timezone_for_uf("RN"), Some("America/Fortaleza"));
87        assert_eq!(timezone_for_uf("RO"), Some("America/Porto_Velho"));
88        assert_eq!(timezone_for_uf("RR"), Some("America/Boa_Vista"));
89        assert_eq!(timezone_for_uf("RS"), Some("America/Sao_Paulo"));
90        assert_eq!(timezone_for_uf("SC"), Some("America/Sao_Paulo"));
91        assert_eq!(timezone_for_uf("SE"), Some("America/Maceio"));
92        assert_eq!(timezone_for_uf("SP"), Some("America/Sao_Paulo"));
93        assert_eq!(timezone_for_uf("TO"), Some("America/Araguaina"));
94    }
95
96    #[test]
97    fn test_case_insensitive() {
98        assert_eq!(timezone_for_uf("sp"), Some("America/Sao_Paulo"));
99        assert_eq!(timezone_for_uf("Sp"), Some("America/Sao_Paulo"));
100        assert_eq!(timezone_for_uf("am"), Some("America/Manaus"));
101        assert_eq!(timezone_for_uf("Am"), Some("America/Manaus"));
102    }
103
104    #[test]
105    fn test_invalid_uf() {
106        assert_eq!(timezone_for_uf("XX"), None);
107        assert_eq!(timezone_for_uf(""), None);
108        assert_eq!(timezone_for_uf("A"), None);
109        assert_eq!(timezone_for_uf("ABC"), None);
110    }
111
112    #[test]
113    fn test_all_27_ufs_covered() {
114        let all_ufs = [
115            "AC", "AL", "AM", "AP", "BA", "CE", "DF", "ES", "GO", "MA", "MG", "MS", "MT", "PA",
116            "PB", "PE", "PI", "PR", "RJ", "RN", "RO", "RR", "RS", "SC", "SE", "SP", "TO",
117        ];
118        assert_eq!(all_ufs.len(), 27);
119        for uf in &all_ufs {
120            assert!(
121                timezone_for_uf(uf).is_some(),
122                "UF {} should have a timezone mapping",
123                uf
124            );
125        }
126    }
127}