#![cfg(target_os = "windows")]
use serde_json::{json, Map, Value};
use windows::Win32::Foundation::SYSTEMTIME;
use windows::Win32::System::Time::{
GetDynamicTimeZoneInformation, GetTimeZoneInformation, DYNAMIC_TIME_ZONE_INFORMATION,
TIME_ZONE_ID_INVALID, TIME_ZONE_INFORMATION,
};
#[must_use]
pub fn host_timezone_information() -> Option<Value> {
let mut dtzi = DYNAMIC_TIME_ZONE_INFORMATION::default();
let dyn_ret = unsafe { GetDynamicTimeZoneInformation(&mut dtzi) };
if dyn_ret != TIME_ZONE_ID_INVALID {
return Some(tzi_to_hcs_schema(
dtzi.Bias,
&dtzi.StandardName,
&dtzi.StandardDate,
dtzi.StandardBias,
&dtzi.DaylightName,
&dtzi.DaylightDate,
dtzi.DaylightBias,
));
}
let mut tzi = TIME_ZONE_INFORMATION::default();
let ret = unsafe { GetTimeZoneInformation(&mut tzi) };
if ret == TIME_ZONE_ID_INVALID {
return None;
}
Some(tzi_to_hcs_schema(
tzi.Bias,
&tzi.StandardName,
&tzi.StandardDate,
tzi.StandardBias,
&tzi.DaylightName,
&tzi.DaylightDate,
tzi.DaylightBias,
))
}
fn tzi_to_hcs_schema(
bias: i32,
standard_name: &[u16; 32],
standard_date: &SYSTEMTIME,
standard_bias: i32,
daylight_name: &[u16; 32],
daylight_date: &SYSTEMTIME,
daylight_bias: i32,
) -> Value {
let mut map = Map::new();
insert_nonzero_i32(&mut map, "Bias", bias);
insert_nonempty_str(&mut map, "StandardName", decode_utf16_name(standard_name));
if let Some(date) = systemtime_to_value(standard_date) {
map.insert("StandardDate".to_string(), date);
}
insert_nonzero_i32(&mut map, "StandardBias", standard_bias);
insert_nonempty_str(&mut map, "DaylightName", decode_utf16_name(daylight_name));
if let Some(date) = systemtime_to_value(daylight_date) {
map.insert("DaylightDate".to_string(), date);
}
insert_nonzero_i32(&mut map, "DaylightBias", daylight_bias);
Value::Object(map)
}
fn insert_nonzero_i32(map: &mut Map<String, Value>, key: &str, value: i32) {
if value != 0 {
map.insert(key.to_string(), json!(value));
}
}
fn insert_nonempty_str(map: &mut Map<String, Value>, key: &str, value: String) {
if !value.is_empty() {
map.insert(key.to_string(), Value::String(value));
}
}
fn decode_utf16_name(buf: &[u16; 32]) -> String {
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
String::from_utf16_lossy(&buf[..len])
}
fn systemtime_to_value(st: &SYSTEMTIME) -> Option<Value> {
let mut map = Map::new();
insert_nonzero_i32(&mut map, "Year", i32::from(st.wYear));
insert_nonzero_i32(&mut map, "Month", i32::from(st.wMonth));
insert_nonzero_i32(&mut map, "DayOfWeek", i32::from(st.wDayOfWeek));
insert_nonzero_i32(&mut map, "Day", i32::from(st.wDay));
insert_nonzero_i32(&mut map, "Hour", i32::from(st.wHour));
insert_nonzero_i32(&mut map, "Minute", i32::from(st.wMinute));
insert_nonzero_i32(&mut map, "Second", i32::from(st.wSecond));
insert_nonzero_i32(&mut map, "Milliseconds", i32::from(st.wMilliseconds));
if map.is_empty() {
None
} else {
Some(Value::Object(map))
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn systemtime(values: [u16; 8]) -> SYSTEMTIME {
SYSTEMTIME {
wYear: values[0],
wMonth: values[1],
wDayOfWeek: values[2],
wDay: values[3],
wHour: values[4],
wMinute: values[5],
wSecond: values[6],
wMilliseconds: values[7],
}
}
fn name_buf(s: &str) -> [u16; 32] {
let mut buf = [0u16; 32];
for (slot, ch) in buf.iter_mut().zip(s.encode_utf16()) {
*slot = ch;
}
buf
}
#[test]
fn all_zero_systemtime_is_omitted() {
assert!(systemtime_to_value(&systemtime([0; 8])).is_none());
}
#[test]
fn populated_systemtime_maps_w_prefixed_fields() {
let st = systemtime([0, 3, 0, 2, 2, 0, 0, 0]);
let v = systemtime_to_value(&st).expect("non-zero date");
assert_eq!(
v,
json!({ "Month": 3, "Day": 2, "Hour": 2 }),
"zero-valued fields (Year/DayOfWeek/Minute/Second/Ms) must be omitted"
);
}
#[test]
fn name_decodes_and_trims_at_nul() {
assert_eq!(
decode_utf16_name(&name_buf("Pacific Standard Time")),
"Pacific Standard Time"
);
}
#[test]
fn dst_timezone_carries_transition_dates_and_omits_zero_ints() {
let v = tzi_to_hcs_schema(
480,
&name_buf("Pacific Standard Time"),
&systemtime([0, 11, 0, 1, 2, 0, 0, 0]),
0,
&name_buf("Pacific Daylight Time"),
&systemtime([0, 3, 0, 2, 2, 0, 0, 0]),
-60,
);
assert_eq!(
v,
json!({
"Bias": 480,
"StandardName": "Pacific Standard Time",
"StandardDate": { "Month": 11, "Day": 1, "Hour": 2 },
"DaylightName": "Pacific Daylight Time",
"DaylightDate": { "Month": 3, "Day": 2, "Hour": 2 },
"DaylightBias": -60,
})
);
}
#[test]
fn no_dst_timezone_omits_transition_dates() {
let v = tzi_to_hcs_schema(
0,
&name_buf("UTC"),
&systemtime([0; 8]),
0,
&name_buf("UTC"),
&systemtime([0; 8]),
0,
);
assert_eq!(v, json!({ "StandardName": "UTC", "DaylightName": "UTC" }));
}
}