iana_time_zone/tz_linux.rs
1use std::fs::{read_link, read_to_string};
2
3pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
4 etc_localtime().or_else(|_| etc_timezone())
5}
6
7fn etc_timezone() -> Result<String, crate::GetTimezoneError> {
8 // see https://stackoverflow.com/a/12523283
9 let mut contents = read_to_string("/etc/timezone")?;
10 // Trim to the correct length without allocating.
11 contents.truncate(contents.trim_end().len());
12 Ok(contents)
13}
14
15fn etc_localtime() -> Result<String, crate::GetTimezoneError> {
16 // Per <https://www.man7.org/linux/man-pages/man5/localtime.5.html>:
17 // “ The /etc/localtime file configures the system-wide timezone of the local system that is
18 // used by applications for presentation to the user. It should be an absolute or relative
19 // symbolic link pointing to /usr/share/zoneinfo/, followed by a timezone identifier such as
20 // "Europe/Berlin" or "Etc/UTC". The resulting link should lead to the corresponding binary
21 // tzfile(5) timezone data for the configured timezone. ”
22
23 // Systemd does not canonicalize the link, but only checks if it is prefixed by
24 // "/usr/share/zoneinfo/" or "../usr/share/zoneinfo/". So we do the same.
25 // <https://github.com/systemd/systemd/blob/9102c625a673a3246d7e73d8737f3494446bad4e/src/basic/time-util.c#L1493>
26
27 const PREFIXES: &[&str] = &[
28 "/usr/share/zoneinfo/", // absolute path
29 "../usr/share/zoneinfo/", // relative path
30 ];
31 let mut s = read_link("/etc/localtime")?
32 .into_os_string()
33 .into_string()
34 .map_err(|_| crate::GetTimezoneError::FailedParsingString)?;
35 for &prefix in PREFIXES {
36 if s.starts_with(prefix) {
37 // Trim to the correct length without allocating.
38 s.replace_range(..prefix.len(), "");
39 return Ok(s);
40 }
41 }
42 Err(crate::GetTimezoneError::FailedParsingString)
43}