1use crate::tz::{TimeZone, TimeZoneDatabase};
2
3static UNIX_LOCALTIME_PATH: &str = "/etc/localtime";
4
5pub(super) fn get(db: &TimeZoneDatabase) -> Option<TimeZone> {
19 read(db, UNIX_LOCALTIME_PATH)
20}
21
22pub(super) fn read(db: &TimeZoneDatabase, path: &str) -> Option<TimeZone> {
29 if let Some(tz) = read_link_to_zoneinfo(db, path) {
30 return Some(tz);
31 }
32 trace!(
33 "failed to find time zone name using Unix-specific heuristics, \
34 attempting to read {UNIX_LOCALTIME_PATH} as unnamed time zone",
35 );
36 match super::read_unnamed_tzif_file(path) {
37 Ok(tz) => Some(tz),
38 Err(_err) => {
39 trace!("failed to read {path} as unnamed time zone: {_err}");
40 None
41 }
42 }
43}
44
45fn read_link_to_zoneinfo(
51 db: &TimeZoneDatabase,
52 path: &str,
53) -> Option<TimeZone> {
54 let target = match std::fs::read_link(path) {
55 Ok(target) => target,
56 Err(_err) => {
57 trace!("failed to read {path} as symbolic link: {_err}");
58 return None;
59 }
60 };
61 let Some(target) = target.to_str() else {
62 trace!("symlink target {target:?} for {path:?} is not valid UTF-8");
63 return None;
64 };
65 let needle = "zoneinfo/";
66 let Some(rpos) = target.rfind(needle) else {
67 trace!(
68 "could not find {needle:?} in symlink target {target:?} \
69 for path {path:?}, so could not determine time zone name \
70 from symlink",
71 );
72 return None;
73 };
74 let name = &target[rpos + needle.len()..];
75 trace!(
76 "extracted {name:?} from symlink target {target:?} \
77 for path {path:?} and assuming it is an IANA time zone name",
78 );
79 let tz = match db.get(&name) {
80 Ok(tz) => tz,
81 Err(_err) => {
82 trace!(
83 "using {name:?} symlink target {target:?} \
84 for path {path:?} as time zone name, \
85 but failed to find time zone with that name in \
86 zoneinfo database {db:?}",
87 );
88 return None;
89 }
90 };
91 Some(tz)
92}
93
94#[cfg(not(miri))]
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn get_time_zone_name_etc_localtime() {
101 let _ = crate::logging::Logger::init();
102
103 let db = crate::tz::db();
104 if crate::tz::db().is_definitively_empty() {
105 return;
106 }
107 let path = std::path::Path::new("/etc/localtime");
108 if !path.exists() {
109 return;
110 }
111 assert!(get(db).is_some());
116 }
117}