1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
use std::fs::{read_link, read_to_string};

pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
    etc_localtime().or_else(|_| etc_timezone())
}

fn etc_timezone() -> Result<String, crate::GetTimezoneError> {
    // see https://stackoverflow.com/a/12523283
    let mut contents = read_to_string("/etc/timezone")?;
    // Trim to the correct length without allocating.
    contents.truncate(contents.trim_end().len());
    Ok(contents)
}

fn etc_localtime() -> Result<String, crate::GetTimezoneError> {
    // Per <https://www.man7.org/linux/man-pages/man5/localtime.5.html>:
    // “ The /etc/localtime file configures the system-wide timezone of the local system that is
    //   used by applications for presentation to the user. It should be an absolute or relative
    //   symbolic link pointing to /usr/share/zoneinfo/, followed by a timezone identifier such as
    //   "Europe/Berlin" or "Etc/UTC". The resulting link should lead to the corresponding binary
    //   tzfile(5) timezone data for the configured timezone. ”

    // Systemd does not canonicalize the link, but only checks if it is prefixed by
    // "/usr/share/zoneinfo/" or "../usr/share/zoneinfo/". So we do the same.
    // <https://github.com/systemd/systemd/blob/9102c625a673a3246d7e73d8737f3494446bad4e/src/basic/time-util.c#L1493>

    const PREFIXES: &[&str] = &[
        "/usr/share/zoneinfo/",   // absolute path
        "../usr/share/zoneinfo/", // relative path
        "/etc/zoneinfo/",         // absolute path for NixOS
        "../etc/zoneinfo/",       // relative path for NixOS
    ];
    let mut s = read_link("/etc/localtime")?
        .into_os_string()
        .into_string()
        .map_err(|_| crate::GetTimezoneError::FailedParsingString)?;
    for &prefix in PREFIXES {
        if s.starts_with(prefix) {
            // Trim to the correct length without allocating.
            s.replace_range(..prefix.len(), "");
            return Ok(s);
        }
    }
    Err(crate::GetTimezoneError::FailedParsingString)
}