1use std::thread; 2use std::time::Duration; 3use time::{Date, Month, Weekday}; 4 5/// Read the time since the Unix epoch in nanoseconds. Will return 0 if the system-reported time is 6/// somehow before the Unix epoch. 7/// 8/// Uses `SystemTime` on native platforms and `Date` in WASM. 9#[must_use] 10pub fn current_time_nanos() -> u128 { 11 cfg_select! { 12 target_arch = "wasm32" => { 13 let current_time_ms = js_sys::Date::now(); 14 (current_time_ms * 1_000_000.0) as u128 15 } 16 _ => { 17 use std::time::SystemTime; 18 19 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_nanos() 20 } 21 } 22} 23 24/// Sleep until at least the specified time. Returns the current time in nanoseconds after sleeping. 25/// 26/// This implementation will try to sleep until 1ms before the target time and then it will busy 27/// wait until the target time. This is to work around the fact that `thread::sleep()` only guarantees 28/// that it will sleep _at least_ the specified duration, and it could sleep longer. Sleeping longer 29/// seems to be particularly common on Windows. 30/// 31/// If the current time is already past the target time, this function will return immediately 32/// without sleeping. 33#[inline] 34#[allow(clippy::must_use_candidate)] 35pub fn sleep_until(time_nanos: u128) -> u128 { 36 loop { 37 let now = current_time_nanos(); 38 if now >= time_nanos { 39 return now; 40 } 41 42 let duration = Duration::from_nanos((time_nanos - now) as u64); 43 if duration > Duration::from_millis(1) { 44 thread::sleep(duration - Duration::from_millis(1)); 45 } 46 } 47} 48 49/// Determine the number of days in the given month+year. 50/// 51/// Leap years are accounted for, but only in that February is assumed to be 29 days in every 4th 52/// year. Other leap year rules are intentionally not applied. 53#[must_use] 54pub fn days_in_month(month: u8, year: u8) -> u8 { 55 match month { 56 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, 57 4 | 6 | 9 | 11 => 30, 58 2 => { 59 // This is not strictly accurate (it doesn't account for not divisible by 100 or divisible 60 // by 400) but it matches what the RTC chips do 61 if year.is_multiple_of(4) { 29 } else { 28 } 62 } 63 _ => { 64 log::error!("Invalid month: {month}, defaulting to 31 days in month"); 65 31 66 } 67 } 68} 69 70/// Determine the weekday of a given date. Day and month should both start at 1, not 0. 71#[must_use] 72pub fn day_of_week(day: u8, month: u8, year: u16) -> Weekday { 73 match Date::from_calendar_date(year.into(), convert_month(month), day) { 74 Ok(date) => date.weekday(), 75 Err(err) => { 76 log::error!("Invalid date (day={day}, month={month}, year={year}): {err}"); 77 Weekday::Sunday 78 } 79 } 80} 81 82fn convert_month(month: u8) -> Month { 83 use Month::*; 84 85 match month { 86 1 => January, 87 2 => February, 88 3 => March, 89 4 => April, 90 5 => May, 91 6 => June, 92 7 => July, 93 8 => August, 94 9 => September, 95 10 => October, 96 11 => November, 97 12 => December, 98 _ => { 99 log::error!("Invalid month: {month} defaulting to January"); 100 January 101 } 102 } 103}