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}