1use jiff::civil::Weekday;
11
12pub fn first_day_of_week_for_locale(tag: &str) -> Weekday {
16 let normalized = tag.to_ascii_lowercase();
17
18 for &(prefix, weekday) in REGION_FIRST_DAY {
20 if normalized == prefix || normalized.starts_with(&format!("{prefix}-")) {
21 return weekday;
22 }
23 }
24 for &(prefix, weekday) in LANG_FIRST_DAY {
26 if normalized == prefix || normalized.starts_with(&format!("{prefix}-")) {
27 return weekday;
28 }
29 }
30 Weekday::Monday
32}
33
34pub fn prefers_12_hour_clock(tag: &str) -> bool {
44 let normalized = tag.to_ascii_lowercase();
45 for &prefix in REGION_12H {
46 if normalized == prefix || normalized.starts_with(&format!("{prefix}-")) {
47 return true;
48 }
49 }
50 false
51}
52
53pub fn format_pattern_for_locale(tag: &str) -> &'static str {
56 let normalized = tag.to_ascii_lowercase();
57
58 for &(prefix, pat) in REGION_PATTERNS {
59 if normalized == prefix || normalized.starts_with(&format!("{prefix}-")) {
60 return pat;
61 }
62 }
63 for &(prefix, pat) in LANG_PATTERNS {
64 if normalized == prefix || normalized.starts_with(&format!("{prefix}-")) {
65 return pat;
66 }
67 }
68 "%Y-%m-%d"
70}
71
72const REGION_FIRST_DAY: &[(&str, Weekday)] = &[
81 ("en-us", Weekday::Sunday),
82 ("en-ca", Weekday::Sunday),
83 ("en-au", Weekday::Sunday),
84 ("ja-jp", Weekday::Sunday),
85 ("zh-cn", Weekday::Sunday),
86 ("zh-tw", Weekday::Sunday),
87 ("ko-kr", Weekday::Sunday),
88 ("he-il", Weekday::Sunday),
89 ("ar-eg", Weekday::Saturday),
90 ("ar-sa", Weekday::Sunday),
91 ("ar-ae", Weekday::Saturday),
92 ("ar-dz", Weekday::Saturday),
93 ("ar-ma", Weekday::Saturday),
94 ("fa-ir", Weekday::Saturday),
95];
96
97const LANG_FIRST_DAY: &[(&str, Weekday)] = &[
99 ("en", Weekday::Monday),
101 ("fr", Weekday::Monday),
102 ("de", Weekday::Monday),
103 ("es", Weekday::Monday),
104 ("it", Weekday::Monday),
105 ("pt", Weekday::Monday),
106 ("nl", Weekday::Monday),
107 ("sv", Weekday::Monday),
108 ("nb", Weekday::Monday),
109 ("nn", Weekday::Monday),
110 ("da", Weekday::Monday),
111 ("fi", Weekday::Monday),
112 ("pl", Weekday::Monday),
113 ("ru", Weekday::Monday),
114 ("uk", Weekday::Monday),
115 ("cs", Weekday::Monday),
116 ("sk", Weekday::Monday),
117 ("hu", Weekday::Monday),
118 ("ro", Weekday::Monday),
119 ("bg", Weekday::Monday),
120 ("hr", Weekday::Monday),
121 ("sr", Weekday::Monday),
122 ("sl", Weekday::Monday),
123 ("el", Weekday::Monday),
124 ("tr", Weekday::Monday),
125];
126
127const REGION_12H: &[&str] = &[
130 "en-us", "en-ca", "en-au", "en-nz", "en-ph", "en-in", "en-pk",
131];
132
133const REGION_PATTERNS: &[(&str, &str)] = &[
135 ("en-us", "%m/%d/%Y"),
136 ("en-ca", "%Y-%m-%d"),
137 ("en-gb", "%d/%m/%Y"),
138 ("en-au", "%d/%m/%Y"),
139 ("en-ie", "%d/%m/%Y"),
140 ("en-nz", "%d/%m/%Y"),
141 ("ja-jp", "%Y/%m/%d"),
142 ("zh-cn", "%Y/%m/%d"),
143 ("zh-tw", "%Y/%m/%d"),
144 ("ko-kr", "%Y. %m. %d."),
145 ("de-de", "%d.%m.%Y"),
146 ("de-at", "%d.%m.%Y"),
147 ("de-ch", "%d.%m.%Y"),
148 ("fr-ca", "%Y-%m-%d"),
149];
150
151const LANG_PATTERNS: &[(&str, &str)] = &[
153 ("en", "%Y-%m-%d"),
154 ("fr", "%d/%m/%Y"),
155 ("es", "%d/%m/%Y"),
156 ("it", "%d/%m/%Y"),
157 ("pt", "%d/%m/%Y"),
158 ("nl", "%d-%m-%Y"),
159 ("de", "%d.%m.%Y"),
160 ("sv", "%Y-%m-%d"),
161 ("nb", "%d.%m.%Y"),
162 ("nn", "%d.%m.%Y"),
163 ("da", "%d-%m-%Y"),
164 ("fi", "%d.%m.%Y"),
165 ("pl", "%d.%m.%Y"),
166 ("ru", "%d.%m.%Y"),
167 ("uk", "%d.%m.%Y"),
168 ("cs", "%d.%m.%Y"),
169 ("sk", "%d.%m.%Y"),
170 ("hu", "%Y. %m. %d."),
171 ("ro", "%d.%m.%Y"),
172 ("bg", "%d.%m.%Y"),
173 ("hr", "%d.%m.%Y"),
174 ("sr", "%d.%m.%Y"),
175 ("sl", "%d.%m.%Y"),
176 ("el", "%d/%m/%Y"),
177 ("tr", "%d.%m.%Y"),
178 ("ja", "%Y/%m/%d"),
179 ("zh", "%Y/%m/%d"),
180 ("ko", "%Y. %m. %d."),
181 ("ar", "%d/%m/%Y"),
182 ("he", "%d/%m/%Y"),
183 ("fa", "%Y/%m/%d"),
184];
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn first_day_us_is_sunday_eu_is_monday() {
192 assert_eq!(first_day_of_week_for_locale("en-US"), Weekday::Sunday);
193 assert_eq!(first_day_of_week_for_locale("fr-FR"), Weekday::Monday);
194 assert_eq!(first_day_of_week_for_locale("de-DE"), Weekday::Monday);
195 assert_eq!(first_day_of_week_for_locale("ja-JP"), Weekday::Sunday);
196 }
197
198 #[test]
199 fn first_day_unknown_locale_falls_back_to_iso() {
200 assert_eq!(first_day_of_week_for_locale("xx-XX"), Weekday::Monday);
201 }
202
203 #[test]
204 fn pattern_us_is_mdy_eu_is_dmy() {
205 assert_eq!(format_pattern_for_locale("en-US"), "%m/%d/%Y");
206 assert_eq!(format_pattern_for_locale("en-GB"), "%d/%m/%Y");
207 assert_eq!(format_pattern_for_locale("fr-FR"), "%d/%m/%Y");
208 assert_eq!(format_pattern_for_locale("de-DE"), "%d.%m.%Y");
209 assert_eq!(format_pattern_for_locale("ja-JP"), "%Y/%m/%d");
210 assert_eq!(format_pattern_for_locale("sv-SE"), "%Y-%m-%d");
211 }
212
213 #[test]
214 fn region_overrides_language() {
215 assert_eq!(format_pattern_for_locale("en"), "%Y-%m-%d");
217 assert_eq!(format_pattern_for_locale("en-US"), "%m/%d/%Y");
218 }
219
220 #[test]
221 fn time_format_us_australia_are_12h() {
222 assert!(prefers_12_hour_clock("en-US"));
223 assert!(prefers_12_hour_clock("en-AU"));
224 assert!(prefers_12_hour_clock("en-CA"));
225 assert!(prefers_12_hour_clock("en-PH"));
226 }
227
228 #[test]
229 fn time_format_eu_asia_are_24h() {
230 assert!(!prefers_12_hour_clock("fr-FR"));
231 assert!(!prefers_12_hour_clock("de-DE"));
232 assert!(!prefers_12_hour_clock("ja-JP"));
233 assert!(!prefers_12_hour_clock("en-GB"));
234 assert!(!prefers_12_hour_clock("zh-CN"));
235 }
236
237 #[test]
238 fn time_format_unknown_locale_defaults_to_24h() {
239 assert!(!prefers_12_hour_clock("xx-XX"));
240 assert!(!prefers_12_hour_clock(""));
241 }
242}