Accessing _tzname and similar constants in windows

Currently I have the following code that works just fine on linux:

#[cfg(not(target_env = "msvc"))]
extern "C" {
    static daylight: std::ffi::c_int;
    static timezone: std::ffi::c_long;
    static tzname: [*const std::ffi::c_char; 2];
}

// ... functions that reference those constants

Now windows has similar constants to these, according to https://learn.microsoft.com/en-us/cpp/c-runtime-library/daylight-dstbias-timezone-and-tzname?view=msvc-170.

And yet the following code fails to link:

// lib.rs
unsafe extern "C" {
    #[link_name = "_daylight"]
    pub static c_daylight: core::ffi::c_int;
    #[link_name = "_timezone"]
    pub static c_timezone: core::ffi::c_long;
    #[link_name = "_tzname"]
    pub static c_tzname: [*const core::ffi::c_char; 2];
}

#[cfg(test)]
mod tests {
    #[test]
    fn test() {
        println!("daylight: {}", unsafe { super::c_daylight });
        println!("timezone: {}", unsafe { super::c_timezone });
        let tzname = unsafe { super::c_tzname };
        assert_eq!(tzname.len(), 2);
    }
}

(running cargo test is sufficient to cause the error).
Is there a way to access these constants?

Nope...

#define _daylight (*__daylight())

_daylight is not a global variable but a function named __daylight.

Yup. Not a global variable and the name is not correct.

It's difficult to tell but I believe it's tagged as deprecated. You probably should find an alternative.

That would probably be GetTimeZoneInformation, from kernel32.dll but best called using the windows or windows-sys crate. With this,

  • daylight is probably true if StandardBias and DaylightBias are different, or just always true.
  • timezone is (Bias + StandardBias) * 60.
  • tzname is StandardName and DaylightName, converted to Rust strings.

I’m not sure if daylight and timezone are really what you’d want to use on Linux, either; all they tell you is that there are daylight savings rules, not whether they’re in effect or what they are.

I'm trying to recreate python's tzname and related attributes in the python time module. But I'll probably try using the windows crate.

Remember that Python’s init_timezone() calls _tzset() first, too, otherwise everyone will get the time zone information for Seattle from the MSVC runtime.

I tried using the functions described in the MSVC documentation that you linked, and GetTimeZoneInformation is significantly easier to use if you can’t use global variables like Linux anyway:

#[derive(Debug)]
struct TimeInfo {
    daylight: i32,
    timezone: i32,
    altzone: i32,
    tzname: (String, String),
}

impl TimeInfo {
    fn get() -> Result<Self, windows::core::Error> {
        let mut tz = TIME_ZONE_INFORMATION::default();
        let res = unsafe { GetTimeZoneInformation(&mut tz) };
        if res == TIME_ZONE_ID_INVALID {
            return Err(windows::core::Error::from_win32());
        }
        Ok(TimeInfo {
            daylight: 1,
            timezone: (tz.Bias + tz.StandardBias) * 60,
            altzone: (tz.Bias + tz.DaylightBias) * 60,
            tzname: (
                String::from_utf16_lossy(&tz.StandardName).trim_end_matches('\0').to_string(),
                String::from_utf16_lossy(&tz.DaylightName).trim_end_matches('\0').to_string(),
            ),
        })
    }

    fn get_from_crt() -> Result<Self, anyhow::Error> {
        unsafe extern "C" {
            unsafe fn _tzset();
            unsafe fn _get_daylight(hours: *mut c_int) -> c_int;
            unsafe fn _get_timezone(seconds: *mut c_long) -> c_int;
            unsafe fn _get_dstbias(seconds: *mut c_long) -> c_int;
            unsafe fn _get_tzname(p_return_value: *mut usize, time_zone_name: *mut u8, size_in_bytes: usize, index: c_int) -> c_int;
        }

        let mut daylight: c_int = 0;
        let mut timezone: c_long = 0;
        let mut dstbias: c_long = 0;
        let mut standard_name = [0u8; 32];
        let mut standard_name_len = 0usize;
        let mut daylight_name = [0u8; 32];
        let mut daylight_name_len = 0usize;

        unsafe { _tzset() };
        let mut errno = unsafe { _get_daylight(&mut daylight) };
        if errno != 0 {
            return Err(std::io::Error::from_raw_os_error(errno).into());
        }
        errno = unsafe { _get_timezone(&mut timezone) };
        if errno != 0 {
            return Err(std::io::Error::from_raw_os_error(errno).into());
        }
        errno = unsafe { _get_dstbias(&mut dstbias) };
        if errno != 0 {
            return Err(std::io::Error::from_raw_os_error(errno).into());
        }
        errno = unsafe { _get_tzname(&mut standard_name_len, standard_name.as_mut_ptr(), standard_name.len(), 0) };
        if errno != 0 {
            return Err(std::io::Error::from_raw_os_error(errno).into());
        }
        errno = unsafe { _get_tzname(&mut daylight_name_len, daylight_name.as_mut_ptr(), daylight_name.len(), 1) };
        if errno != 0 {
            return Err(std::io::Error::from_raw_os_error(errno).into());
        }
        Ok(TimeInfo {
            daylight,
            timezone,
            altzone: timezone + dstbias,
            tzname: (
                CStr::from_bytes_until_nul(&standard_name)?.to_string_lossy().into_owned(),
                CStr::from_bytes_until_nul(&daylight_name)?.to_string_lossy().into_owned(),
            ),
        })
    }
}
2 Likes