iana_time_zone/
ffi_utils.rs

1//! Cross platform FFI helpers.
2
3#[cfg(any(test, target_os = "android"))]
4use std::ffi::CStr;
5
6/// A buffer to store the timezone name when calling the C API.
7#[cfg(any(test, target_vendor = "apple", target_env = "ohos"))]
8pub(crate) mod buffer {
9    /// The longest name in the IANA time zone database is 32 ASCII characters long.
10    pub const MAX_LEN: usize = 64;
11
12    /// Return a buffer to store the timezone name.
13    ///
14    /// The buffer is used to store the timezone name when calling the C API.
15    pub const fn tzname_buf() -> [u8; MAX_LEN] {
16        [0; MAX_LEN]
17    }
18}
19
20// The system property named 'persist.sys.timezone' contains the name of the
21// current timezone.
22//
23// From https://android.googlesource.com/platform/bionic/+/gingerbread-release/libc/docs/OVERVIEW.TXT#79:
24//
25// > The name of the current timezone is taken from the TZ environment variable,
26// > if defined. Otherwise, the system property named 'persist.sys.timezone' is
27// > checked instead.
28//
29// TODO: Use a `c"..."` literal when MSRV is upgraded beyond 1.77.0.
30// https://doc.rust-lang.org/edition-guide/rust-2021/c-string-literals.html
31#[cfg(any(test, target_os = "android"))]
32const ANDROID_TIMEZONE_PROPERTY_NAME: &[u8] = b"persist.sys.timezone\0";
33
34/// Return a [`CStr`] to access the timezone from an Android system properties
35/// environment.
36#[cfg(any(test, target_os = "android"))]
37pub(crate) fn android_timezone_property_name() -> &'static CStr {
38    // In tests or debug mode, opt into extra runtime checks.
39    if cfg!(any(test, debug_assertions)) {
40        return CStr::from_bytes_with_nul(ANDROID_TIMEZONE_PROPERTY_NAME).unwrap();
41    }
42
43    // SAFETY: the key is NUL-terminated and there are no other NULs, this
44    // invariant is checked in tests.
45    unsafe { CStr::from_bytes_with_nul_unchecked(ANDROID_TIMEZONE_PROPERTY_NAME) }
46}
47
48#[cfg(test)]
49mod tests {
50    use core::mem::size_of_val;
51    use std::ffi::CStr;
52
53    use super::buffer::{tzname_buf, MAX_LEN};
54    use super::{android_timezone_property_name, ANDROID_TIMEZONE_PROPERTY_NAME};
55
56    #[test]
57    fn test_android_timezone_property_name_is_valid_cstr() {
58        CStr::from_bytes_with_nul(ANDROID_TIMEZONE_PROPERTY_NAME).unwrap();
59
60        let mut invalid_property_name = ANDROID_TIMEZONE_PROPERTY_NAME.to_owned();
61        invalid_property_name.push(b'\0');
62        CStr::from_bytes_with_nul(&invalid_property_name).unwrap_err();
63    }
64
65    #[test]
66    fn test_android_timezone_property_name_getter() {
67        let key = android_timezone_property_name().to_bytes_with_nul();
68        assert_eq!(key, ANDROID_TIMEZONE_PROPERTY_NAME);
69        std::str::from_utf8(key).unwrap();
70    }
71
72    /// An exhaustive set of IANA timezone names for testing.
73    ///
74    /// Pulled from Wikipedia as of Sat March 22, 2025:
75    ///
76    /// - <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>
77    /// - <https://en.wikipedia.org/w/index.php?title=List_of_tz_database_time_zones&oldid=1281103182>
78    static KNOWN_TIMEZONE_NAMES: &[&str] = &[
79        "Africa/Abidjan",
80        "Africa/Accra",
81        "Africa/Addis_Ababa",
82        "Africa/Algiers",
83        "Africa/Asmara",
84        "Africa/Asmera",
85        "Africa/Bamako",
86        "Africa/Bangui",
87        "Africa/Banjul",
88        "Africa/Bissau",
89        "Africa/Blantyre",
90        "Africa/Brazzaville",
91        "Africa/Bujumbura",
92        "Africa/Cairo",
93        "Africa/Casablanca",
94        "Africa/Conakry",
95        "Africa/Dakar",
96        "Africa/Dar_es_Salaam",
97        "Africa/Djibouti",
98        "Africa/Douala",
99        "Africa/El_Aaiun",
100        "Africa/Freetown",
101        "Africa/Gaborone",
102        "Africa/Harare",
103        "Africa/Johannesburg",
104        "Africa/Juba",
105        "Africa/Kampala",
106        "Africa/Khartoum",
107        "Africa/Kigali",
108        "Africa/Libreville",
109        "Africa/Lome",
110        "Africa/Luanda",
111        "Africa/Lusaka",
112        "Africa/Malabo",
113        "Africa/Maseru",
114        "Africa/Mbabane",
115        "Africa/Mogadishu",
116        "Africa/Monrovia",
117        "Africa/Nairobi",
118        "Africa/Ndjamena",
119        "Africa/Niamey",
120        "Africa/Nouakchott",
121        "Africa/Ouagadougou",
122        "Africa/Porto-Novo",
123        "Africa/Sao_Tome",
124        "Africa/Timbuktu",
125        "Africa/Tripoli",
126        "Africa/Tunis",
127        "Africa/Windhoek",
128        "America/Anguilla",
129        "America/Antigua",
130        "America/Argentina/ComodRivadavia",
131        "America/Aruba",
132        "America/Asuncion",
133        "America/Atka",
134        "America/Barbados",
135        "America/Belize",
136        "America/Bogota",
137        "America/Buenos_Aires",
138        "America/Caracas",
139        "America/Catamarca",
140        "America/Cayenne",
141        "America/Cayman",
142        "America/Coral_Harbour",
143        "America/Cordoba",
144        "America/Costa_Rica",
145        "America/Curacao",
146        "America/Dominica",
147        "America/El_Salvador",
148        "America/Ensenada",
149        "America/Fort_Wayne",
150        "America/Godthab",
151        "America/Grand_Turk",
152        "America/Grenada",
153        "America/Guadeloupe",
154        "America/Guatemala",
155        "America/Guyana",
156        "America/Havana",
157        "America/Indianapolis",
158        "America/Jamaica",
159        "America/Jujuy",
160        "America/Knox_IN",
161        "America/Kralendijk",
162        "America/La_Paz",
163        "America/Lima",
164        "America/Louisville",
165        "America/Lower_Princes",
166        "America/Managua",
167        "America/Marigot",
168        "America/Martinique",
169        "America/Mendoza",
170        "America/Miquelon",
171        "America/Montevideo",
172        "America/Montreal",
173        "America/Montserrat",
174        "America/Nassau",
175        "America/Nipigon",
176        "America/Pangnirtung",
177        "America/Paramaribo",
178        "America/Port-au-Prince",
179        "America/Port_of_Spain",
180        "America/Porto_Acre",
181        "America/Rainy_River",
182        "America/Rosario",
183        "America/Santa_Isabel",
184        "America/Santo_Domingo",
185        "America/Shiprock",
186        "America/St_Barthelemy",
187        "America/St_Kitts",
188        "America/St_Lucia",
189        "America/St_Thomas",
190        "America/St_Vincent",
191        "America/Tegucigalpa",
192        "America/Thunder_Bay",
193        "America/Tortola",
194        "America/Virgin",
195        "America/Yellowknife",
196        "Antarctica/South_Pole",
197        "Arctic/Longyearbyen",
198        "Asia/Aden",
199        "Asia/Amman",
200        "Asia/Ashgabat",
201        "Asia/Ashkhabad",
202        "Asia/Baghdad",
203        "Asia/Bahrain",
204        "Asia/Baku",
205        "Asia/Beirut",
206        "Asia/Bishkek",
207        "Asia/Brunei",
208        "Asia/Calcutta",
209        "Asia/Choibalsan",
210        "Asia/Chongqing",
211        "Asia/Chungking",
212        "Asia/Colombo",
213        "Asia/Dacca",
214        "Asia/Damascus",
215        "Asia/Dhaka",
216        "Asia/Dili",
217        "Asia/Dushanbe",
218        "Asia/Harbin",
219        "Asia/Hong_Kong",
220        "Asia/Istanbul",
221        "Asia/Jerusalem",
222        "Asia/Kabul",
223        "Asia/Karachi",
224        "Asia/Kashgar",
225        "Asia/Kathmandu",
226        "Asia/Katmandu",
227        "Asia/Kolkata",
228        "Asia/Kuwait",
229        "Asia/Macao",
230        "Asia/Macau",
231        "Asia/Manila",
232        "Asia/Muscat",
233        "Asia/Phnom_Penh",
234        "Asia/Pyongyang",
235        "Asia/Qatar",
236        "Asia/Rangoon",
237        "Asia/Saigon",
238        "Asia/Seoul",
239        "Asia/Taipei",
240        "Asia/Tbilisi",
241        "Asia/Tehran",
242        "Asia/Tel_Aviv",
243        "Asia/Thimbu",
244        "Asia/Thimphu",
245        "Asia/Ujung_Pandang",
246        "Asia/Ulan_Bator",
247        "Asia/Vientiane",
248        "Asia/Yangon",
249        "Asia/Yerevan",
250        "Atlantic/Bermuda",
251        "Atlantic/Cape_Verde",
252        "Atlantic/Faeroe",
253        "Atlantic/Faroe",
254        "Atlantic/Jan_Mayen",
255        "Atlantic/Reykjavik",
256        "Atlantic/South_Georgia",
257        "Atlantic/St_Helena",
258        "Atlantic/Stanley",
259        "Australia/ACT",
260        "Australia/Canberra",
261        "Australia/Currie",
262        "Australia/LHI",
263        "Australia/North",
264        "Australia/NSW",
265        "Australia/Queensland",
266        "Australia/South",
267        "Australia/Tasmania",
268        "Australia/Victoria",
269        "Australia/West",
270        "Australia/Yancowinna",
271        "Brazil/Acre",
272        "Brazil/DeNoronha",
273        "Brazil/East",
274        "Brazil/West",
275        "Canada/Atlantic",
276        "Canada/Central",
277        "Canada/Eastern",
278        "Canada/Mountain",
279        "Canada/Newfoundland",
280        "Canada/Pacific",
281        "Canada/Saskatchewan",
282        "Canada/Yukon",
283        "CET",
284        "Chile/Continental",
285        "Chile/EasterIsland",
286        "CST6CDT",
287        "Cuba",
288        "EET",
289        "Egypt",
290        "Eire",
291        "EST",
292        "EST5EDT",
293        "Etc/GMT",
294        "Etc/GMT+0",
295        "Etc/GMT+1",
296        "Etc/GMT+10",
297        "Etc/GMT+11",
298        "Etc/GMT+12",
299        "Etc/GMT+2",
300        "Etc/GMT+3",
301        "Etc/GMT+4",
302        "Etc/GMT+5",
303        "Etc/GMT+6",
304        "Etc/GMT+7",
305        "Etc/GMT+8",
306        "Etc/GMT+9",
307        "Etc/GMT-0",
308        "Etc/GMT-1",
309        "Etc/GMT-10",
310        "Etc/GMT-11",
311        "Etc/GMT-12",
312        "Etc/GMT-13",
313        "Etc/GMT-14",
314        "Etc/GMT-2",
315        "Etc/GMT-3",
316        "Etc/GMT-4",
317        "Etc/GMT-5",
318        "Etc/GMT-6",
319        "Etc/GMT-7",
320        "Etc/GMT-8",
321        "Etc/GMT-9",
322        "Etc/GMT0",
323        "Etc/Greenwich",
324        "Etc/UCT",
325        "Etc/Universal",
326        "Etc/UTC",
327        "Etc/Zulu",
328        "Europe/Amsterdam",
329        "Europe/Andorra",
330        "Europe/Athens",
331        "Europe/Belfast",
332        "Europe/Belgrade",
333        "Europe/Bratislava",
334        "Europe/Brussels",
335        "Europe/Bucharest",
336        "Europe/Budapest",
337        "Europe/Chisinau",
338        "Europe/Copenhagen",
339        "Europe/Dublin",
340        "Europe/Gibraltar",
341        "Europe/Guernsey",
342        "Europe/Helsinki",
343        "Europe/Isle_of_Man",
344        "Europe/Istanbul",
345        "Europe/Jersey",
346        "Europe/Kiev",
347        "Europe/Ljubljana",
348        "Europe/London",
349        "Europe/Luxembourg",
350        "Europe/Malta",
351        "Europe/Mariehamn",
352        "Europe/Minsk",
353        "Europe/Monaco",
354        "Europe/Nicosia",
355        "Europe/Oslo",
356        "Europe/Paris",
357        "Europe/Podgorica",
358        "Europe/Prague",
359        "Europe/Riga",
360        "Europe/Rome",
361        "Europe/San_Marino",
362        "Europe/Sarajevo",
363        "Europe/Skopje",
364        "Europe/Sofia",
365        "Europe/Stockholm",
366        "Europe/Tallinn",
367        "Europe/Tirane",
368        "Europe/Tiraspol",
369        "Europe/Uzhgorod",
370        "Europe/Vaduz",
371        "Europe/Vatican",
372        "Europe/Vienna",
373        "Europe/Vilnius",
374        "Europe/Warsaw",
375        "Europe/Zagreb",
376        "Europe/Zaporozhye",
377        "Factory",
378        "GB",
379        "GB-Eire",
380        "GMT",
381        "GMT+0",
382        "GMT-0",
383        "GMT0",
384        "Greenwich",
385        "Hongkong",
386        "HST",
387        "Iceland",
388        "Indian/Antananarivo",
389        "Indian/Chagos",
390        "Indian/Christmas",
391        "Indian/Cocos",
392        "Indian/Comoro",
393        "Indian/Kerguelen",
394        "Indian/Mahe",
395        "Indian/Mauritius",
396        "Indian/Mayotte",
397        "Indian/Reunion",
398        "Iran",
399        "Israel",
400        "Jamaica",
401        "Japan",
402        "Kwajalein",
403        "Libya",
404        "MET",
405        "Mexico/BajaNorte",
406        "Mexico/BajaSur",
407        "Mexico/General",
408        "MST",
409        "MST7MDT",
410        "Navajo",
411        "NZ",
412        "NZ-CHAT",
413        "Pacific/Apia",
414        "Pacific/Efate",
415        "Pacific/Enderbury",
416        "Pacific/Fakaofo",
417        "Pacific/Fiji",
418        "Pacific/Funafuti",
419        "Pacific/Guam",
420        "Pacific/Johnston",
421        "Pacific/Nauru",
422        "Pacific/Niue",
423        "Pacific/Norfolk",
424        "Pacific/Noumea",
425        "Pacific/Palau",
426        "Pacific/Pitcairn",
427        "Pacific/Ponape",
428        "Pacific/Rarotonga",
429        "Pacific/Saipan",
430        "Pacific/Samoa",
431        "Pacific/Tongatapu",
432        "Pacific/Truk",
433        "Pacific/Wallis",
434        "Pacific/Yap",
435        "Poland",
436        "Portugal",
437        "PRC",
438        "PST8PDT",
439        "ROC",
440        "ROK",
441        "Singapore",
442        "Turkey",
443        "UCT",
444        "Universal",
445        "US/Alaska",
446        "US/Aleutian",
447        "US/Arizona",
448        "US/Central",
449        "US/East-Indiana",
450        "US/Eastern",
451        "US/Hawaii",
452        "US/Indiana-Starke",
453        "US/Michigan",
454        "US/Mountain",
455        "US/Pacific",
456        "US/Samoa",
457        "UTC",
458        "W-SU",
459        "WET",
460        "Zulu",
461        "America/Rio_Branco",
462        "America/Maceio",
463        "America/Metlakatla",
464        "America/Juneau",
465        "America/Sitka",
466        "America/Adak",
467        "America/Yakutat",
468        "America/Anchorage",
469        "America/Nome",
470        "America/Manaus",
471        "America/Eirunepe",
472        "Asia/Aqtobe",
473        "America/Blanc-Sablon",
474        "America/Puerto_Rico",
475        "America/Goose_Bay",
476        "America/Moncton",
477        "America/Glace_Bay",
478        "America/Halifax",
479        "America/Noronha",
480        "Asia/Atyrau",
481        "Atlantic/Azores",
482        "America/Bahia",
483        "America/Bahia_Banderas",
484        "America/Tijuana",
485        "America/Mazatlan",
486        "Asia/Hovd",
487        "Asia/Shanghai",
488        "Asia/Makassar",
489        "Asia/Pontianak",
490        "Pacific/Bougainville",
491        "America/Fortaleza",
492        "America/Sao_Paulo",
493        "America/Argentina/Buenos_Aires",
494        "Europe/Busingen",
495        "Europe/Zurich",
496        "America/Merida",
497        "Atlantic/Canary",
498        "Antarctica/Casey",
499        "America/Argentina/Catamarca",
500        "America/Indiana/Tell_City",
501        "America/Indiana/Knox",
502        "America/Menominee",
503        "America/North_Dakota/Beulah",
504        "America/North_Dakota/New_Salem",
505        "America/North_Dakota/Center",
506        "America/Rankin_Inlet",
507        "America/Resolute",
508        "America/Winnipeg",
509        "America/Chicago",
510        "Africa/Maputo",
511        "America/Mexico_City",
512        "Africa/Ceuta",
513        "Pacific/Chatham",
514        "America/Chihuahua",
515        "America/Ojinaga",
516        "America/Ciudad_Juarez",
517        "Pacific/Chuuk",
518        "America/Matamoros",
519        "Europe/Simferopol",
520        "Asia/Dubai",
521        "America/Swift_Current",
522        "America/Regina",
523        "Antarctica/Davis",
524        "Africa/Lubumbashi",
525        "Africa/Kinshasa",
526        "Antarctica/DumontDUrville",
527        "America/Monterrey",
528        "Pacific/Easter",
529        "America/Indiana/Marengo",
530        "America/Indiana/Vincennes",
531        "America/Indiana/Indianapolis",
532        "America/Indiana/Petersburg",
533        "America/Indiana/Winamac",
534        "America/Indiana/Vevay",
535        "America/Kentucky/Louisville",
536        "America/Kentucky/Monticello",
537        "America/Detroit",
538        "America/Iqaluit",
539        "America/Toronto",
540        "America/New_York",
541        "America/Guayaquil",
542        "America/Atikokan",
543        "America/Panama",
544        "Asia/Tokyo",
545        "Pacific/Galapagos",
546        "Pacific/Gambier",
547        "Asia/Gaza",
548        "Pacific/Tarawa",
549        "Pacific/Honolulu",
550        "Asia/Jakarta",
551        "America/Argentina/Jujuy",
552        "Indian/Maldives",
553        "Pacific/Kosrae",
554        "Pacific/Kwajalein",
555        "America/Argentina/La_Rioja",
556        "Pacific/Kiritimati",
557        "Australia/Lord_Howe",
558        "Antarctica/Macquarie",
559        "Atlantic/Madeira",
560        "Asia/Kuala_Lumpur",
561        "Asia/Aqtau",
562        "Pacific/Marquesas",
563        "America/Cuiaba",
564        "America/Campo_Grande",
565        "Antarctica/Mawson",
566        "America/Argentina/Mendoza",
567        "Pacific/Pago_Pago",
568        "Pacific/Midway",
569        "America/Argentina/Cordoba",
570        "America/Santiago",
571        "Asia/Nicosia",
572        "Europe/Berlin",
573        "America/Nuuk",
574        "Asia/Almaty",
575        "Pacific/Majuro",
576        "Asia/Ulaanbaatar",
577        "Europe/Kyiv",
578        "America/Edmonton",
579        "America/Boise",
580        "America/Inuvik",
581        "America/Cambridge_Bay",
582        "America/Denver",
583        "Europe/Kaliningrad",
584        "Europe/Kirov",
585        "Europe/Moscow",
586        "Europe/Volgograd",
587        "Europe/Astrakhan",
588        "Europe/Samara",
589        "Europe/Saratov",
590        "Europe/Ulyanovsk",
591        "Asia/Yekaterinburg",
592        "Asia/Omsk",
593        "Asia/Barnaul",
594        "Asia/Novokuznetsk",
595        "Asia/Krasnoyarsk",
596        "Asia/Novosibirsk",
597        "Asia/Tomsk",
598        "Asia/Irkutsk",
599        "Asia/Yakutsk",
600        "Asia/Khandyga",
601        "Asia/Chita",
602        "Asia/Vladivostok",
603        "Asia/Ust-Nera",
604        "Asia/Magadan",
605        "Asia/Srednekolymsk",
606        "Asia/Sakhalin",
607        "Asia/Anadyr",
608        "Asia/Kamchatka",
609        "America/Phoenix",
610        "America/Creston",
611        "America/Dawson_Creek",
612        "America/Fort_Nelson",
613        "America/Whitehorse",
614        "America/Dawson",
615        "America/Danmarkshavn",
616        "Asia/Jayapura",
617        "Australia/Sydney",
618        "Australia/Broken_Hill",
619        "Pacific/Auckland",
620        "Antarctica/McMurdo",
621        "America/St_Johns",
622        "Asia/Bangkok",
623        "Asia/Famagusta",
624        "Australia/Darwin",
625        "America/Los_Angeles",
626        "America/Vancouver",
627        "Antarctica/Palmer",
628        "Pacific/Port_Moresby",
629        "America/Belem",
630        "America/Santarem",
631        "Asia/Singapore",
632        "America/Recife",
633        "Pacific/Kanton",
634        "Pacific/Guadalcanal",
635        "Pacific/Pohnpei",
636        "Europe/Lisbon",
637        "Asia/Qostanay",
638        "Australia/Brisbane",
639        "Australia/Lindeman",
640        "America/Cancun",
641        "Asia/Qyzylorda",
642        "America/Punta_Arenas",
643        "America/Porto_Velho",
644        "America/Boa_Vista",
645        "Antarctica/Rothera",
646        "Asia/Kuching",
647        "America/Argentina/Salta",
648        "America/Argentina/San_Juan",
649        "America/Argentina/San_Luis",
650        "America/Argentina/Rio_Gallegos",
651        "America/Scoresbysund",
652        "Pacific/Tahiti",
653        "America/Hermosillo",
654        "Australia/Adelaide",
655        "Asia/Ho_Chi_Minh",
656        "Europe/Madrid",
657        "Antarctica/Syowa",
658        "Asia/Riyadh",
659        "Australia/Hobart",
660        "America/Thule",
661        "America/Argentina/Ushuaia",
662        "America/Araguaina",
663        "Antarctica/Troll",
664        "America/Argentina/Tucuman",
665        "Asia/Tashkent",
666        "Asia/Samarkand",
667        "Australia/Melbourne",
668        "Antarctica/Vostok",
669        "Pacific/Wake",
670        "Africa/Lagos",
671        "Asia/Hebron",
672        "Asia/Oral",
673        "Australia/Eucla",
674        "Australia/Perth",
675        "Asia/Urumqi",
676    ];
677
678    #[test]
679    fn test_tzname_buffer_fits_all_iana_names() {
680        let buf = tzname_buf();
681        let max_len = buf.len();
682
683        let mut failed_tz_names = vec![];
684
685        for &tz in KNOWN_TIMEZONE_NAMES {
686            // Require max_len + 1 to account for an optional NUL terminator.
687            if tz.len() >= max_len {
688                failed_tz_names.push(tz);
689            }
690        }
691
692        assert!(
693            failed_tz_names.is_empty(),
694            "One or more timezone names exceed the buffer length of {}. Max length of found timezone: {}\n{:?}",
695            max_len,
696            failed_tz_names.iter().map(|s| s.len()).max().unwrap(),
697            failed_tz_names
698        );
699    }
700
701    #[test]
702    fn test_tzname_buffer_correct_size() {
703        assert_eq!(
704            MAX_LEN, 64,
705            "Buffer length changed unexpectedly, ensure consistency with documented limit."
706        );
707        assert_eq!(
708            tzname_buf().len(),
709            MAX_LEN,
710            "Buffer length changed unexpectedly, ensure consistency with documented limit."
711        );
712        assert_eq!(
713            size_of_val(&tzname_buf()),
714            MAX_LEN,
715            "Buffer length changed unexpectedly, ensure consistency with documented limit."
716        );
717    }
718}