diff --git a/Cargo.lock b/Cargo.lock index 191ad8923..497d7ab42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,7 +224,7 @@ dependencies = [ "libc", "num-integer", "num-traits", - "time", + "time 0.1.44", "winapi 0.3.9", ] @@ -335,7 +335,7 @@ dependencies = [ "sha1", "tempfile", "textwrap 0.15.0", - "time", + "time 0.3.9", "unindent", "unix_socket", "users", @@ -1213,6 +1213,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" +dependencies = [ + "libc", +] + [[package]] name = "number_prefix" version = "0.4.0" @@ -1944,16 +1953,17 @@ dependencies = [ [[package]] name = "time" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", + "wasi", "winapi 0.3.9", ] [[package]] -name = "typenum" +< String { thread_local! { - static NOW: time::Tm = time::now() + static NOW: time::OffsetDateTime = time::OffsetDateTime::now_local().unwrap(); } NOW.with(|n| { - let duration = n.to_timespec().sec - when; + let duration = n.unix_timestamp() - when; if duration < 60 { // less than 1min " ".to_owned() @@ -242,7 +242,11 @@ fn idle_string(when: i64) -> String { } fn time_string(ut: &Utmpx) -> String { - time::strftime("%b %e %H:%M", &ut.login_time()).unwrap() // LC_ALL=C + // "%b %e %H:%M" + let time_format: Vec = + time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]") + .unwrap(); + ut.login_time().format(&time_format).unwrap() // LC_ALL=C } fn gecos_to_fullname(pw: &Passwd) -> Option { diff --git a/src/uu/touch/Cargo.toml b/src/uu/touch/Cargo.toml index 646b65f50..aa747ae78 100644 --- a/src/uu/touch/Cargo.toml +++ b/src/uu/touch/Cargo.toml @@ -17,7 +17,7 @@ path = "src/touch.rs" [dependencies] filetime = "0.2.1" clap = { version = "3.1", features = ["wrap_help", "cargo"] } -time = "0.1.40" +time = { version = "0.3", features = ["parsing", "formatting", "local-offset", "macros"] } uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["libc"] } [target.'cfg(target_os = "windows")'.dependencies] diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index ff08a1b59..75cd38a7a 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -17,6 +17,7 @@ use clap::{crate_version, Arg, ArgGroup, Command}; use filetime::*; use std::fs::{self, File}; use std::path::{Path, PathBuf}; +use time::macros::{format_description, time}; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError}; use uucore::format_usage; @@ -41,14 +42,13 @@ pub mod options { static ARG_FILES: &str = "files"; -fn to_local(mut tm: time::Tm) -> time::Tm { - tm.tm_utcoff = time::now().tm_utcoff; - tm +fn to_local(tm: time::PrimitiveDateTime) -> time::OffsetDateTime { + // TODO: handle error getting now + tm.assume_offset(time::OffsetDateTime::now_local().unwrap().offset()) } -fn local_tm_to_filetime(tm: time::Tm) -> FileTime { - let ts = tm.to_timespec(); - FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32) +fn local_dt_to_filetime(dt: time::OffsetDateTime) -> FileTime { + FileTime::from_unix_time(dt.unix_timestamp(), dt.nanosecond()) } #[uucore::main] @@ -62,7 +62,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Try 'touch --help' for more information."##, ) })?; - let (mut atime, mut mtime) = if let Some(reference) = matches.value_of_os(options::sources::REFERENCE) { stat(Path::new(reference), !matches.is_present(options::NO_DEREF))? @@ -72,7 +71,7 @@ Try 'touch --help' for more information."##, } else if let Some(current) = matches.value_of(options::sources::CURRENT) { parse_timestamp(current)? } else { - local_tm_to_filetime(time::now()) + local_dt_to_filetime(time::OffsetDateTime::now_local().unwrap()) }; (timestamp, timestamp) }; @@ -248,38 +247,80 @@ fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> { )) } -fn parse_date(str: &str) -> UResult { +const POSIX_LOCALE_FORMAT: &[time::format_description::FormatItem] = format_description!( + "[weekday repr:short] [month repr:short] [day padding:space] [hour]:[minute]:[second] [year]" +); + +const ISO_8601_FORMAT: &[time::format_description::FormatItem] = + format_description!("[year]-[month]-[day]"); + +fn parse_date(s: &str) -> UResult { // This isn't actually compatible with GNU touch, but there doesn't seem to // be any simple specification for what format this parameter allows and I'm // not about to implement GNU parse_datetime. // http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=lib/parse-datetime.y - let formats = vec!["%c", "%F"]; - for f in formats { - if let Ok(tm) = time::strptime(str, f) { - return Ok(local_tm_to_filetime(to_local(tm))); + + // TODO: match on char count? + + // "The preferred date and time representation for the current locale." + // "(In the POSIX locale this is equivalent to %a %b %e %H:%M:%S %Y.)" + // time 0.1.43 parsed this as 'a b e T Y' + // which is equivalent to the POSIX locale: %a %b %e %H:%M:%S %Y + // Tue Dec 3 ... + // ("%c", POSIX_LOCALE_FORMAT), + if let Ok(parsed) = time::PrimitiveDateTime::parse(s, &POSIX_LOCALE_FORMAT) { + return Ok(local_dt_to_filetime(to_local(parsed))); + } + + // "Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99)" + // ("%F", ISO_8601_FORMAT), + if let Ok(parsed) = time::Date::parse(s, &ISO_8601_FORMAT) { + return Ok(local_dt_to_filetime(to_local( + time::PrimitiveDateTime::new(parsed, time!(00:00)), + ))); + } + + // "@%s" is "The number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). (TZ) (Calculated from mktime(tm).)" + if s.bytes().next() == Some(b'@') { + if let Ok(ts) = &s[1..].parse::() { + // Don't convert to local time in this case - seconds since epoch are not time-zone dependent + return Ok(local_dt_to_filetime( + time::OffsetDateTime::from_unix_timestamp(*ts).unwrap(), + )); } } - if let Ok(tm) = time::strptime(str, "@%s") { - // Don't convert to local time in this case - seconds since epoch are not time-zone dependent - return Ok(local_tm_to_filetime(tm)); - } - - Err(USimpleError::new( - 1, - format!("Unable to parse date: {}", str), - )) + Err(USimpleError::new(1, format!("Unable to parse date: {}", s))) } +// "%Y%m%d%H%M.%S" 15 chars +const YYYYMMDDHHMM_DOT_SS_FORMAT: &[time::format_description::FormatItem] = format_description!( + "[year repr:full][month repr:numerical padding:zero][day][hour][minute].[second]" +); + +// "%Y%m%d%H%M" 12 chars +const YYYYMMDDHHMM_FORMAT: &[time::format_description::FormatItem] = + format_description!("[year repr:full][month repr:numerical padding:zero][day][hour][minute]"); + +// "%y%m%d%H%M.%S" 13 chars +const YYMMDDHHMM_DOT_SS_FORMAT: &[time::format_description::FormatItem] = + format_description!("[year repr:last_two padding:none][month][day][hour][minute].[second]"); + +// "%y%m%d%H%M" 10 chars +const YYMMDDHHMM_FORMAT: &[time::format_description::FormatItem] = + format_description!("[year repr:last_two padding:none][month padding:zero][day padding:zero][hour repr:24 padding:zero][minute padding:zero]"); + fn parse_timestamp(s: &str) -> UResult { - let now = time::now(); - let (format, ts) = match s.chars().count() { - 15 => ("%Y%m%d%H%M.%S", s.to_owned()), - 12 => ("%Y%m%d%H%M", s.to_owned()), - 13 => ("%y%m%d%H%M.%S", s.to_owned()), - 10 => ("%y%m%d%H%M", s.to_owned()), - 11 => ("%Y%m%d%H%M.%S", format!("{}{}", now.tm_year + 1900, s)), - 8 => ("%Y%m%d%H%M", format!("{}{}", now.tm_year + 1900, s)), + // TODO: handle error + let now = time::OffsetDateTime::now_utc(); + + let (mut format, mut ts) = match s.chars().count() { + 15 => (YYYYMMDDHHMM_DOT_SS_FORMAT, s.to_owned()), + 12 => (YYYYMMDDHHMM_FORMAT, s.to_owned()), + 13 => (YYMMDDHHMM_DOT_SS_FORMAT, s.to_owned()), + 10 => (YYMMDDHHMM_FORMAT, s.to_owned()), + 11 => (YYYYMMDDHHMM_DOT_SS_FORMAT, format!("{}{}", now.year(), s)), + 8 => (YYYYMMDDHHMM_FORMAT, format!("{}{}", now.year(), s)), _ => { return Err(USimpleError::new( 1, @@ -287,30 +328,39 @@ fn parse_timestamp(s: &str) -> UResult { )) } }; - - let tm = time::strptime(&ts, format) - .map_err(|_| USimpleError::new(1, format!("invalid date format {}", s.quote())))?; - - let mut local = to_local(tm); - local.tm_isdst = -1; - let ft = local_tm_to_filetime(local); - - // We have to check that ft is valid time. Due to daylight saving - // time switch, local time can jump from 1:59 AM to 3:00 AM, - // in which case any time between 2:00 AM and 2:59 AM is not valid. - // Convert back to local time and see if we got the same value back. - let ts = time::Timespec { - sec: ft.unix_seconds(), - nsec: 0, - }; - let tm2 = time::at(ts); - if tm.tm_hour != tm2.tm_hour { - return Err(USimpleError::new( - 1, - format!("invalid date format {}", s.quote()), - )); + // workaround time returning Err(TryFromParsed(InsufficientInformation)) for year w/ + // repr:last_two + // https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1ccfac7c07c5d1c7887a11decf0e1996 + if s.chars().count() == 10 { + format = YYYYMMDDHHMM_FORMAT; + ts = "20".to_owned() + &ts; + } else if s.chars().count() == 13 { + format = YYYYMMDDHHMM_DOT_SS_FORMAT; + ts = "20".to_owned() + &ts; } + let tm = time::PrimitiveDateTime::parse(&ts, &format) + .map_err(|_| USimpleError::new(1, format!("invalid date ts format {}", ts.quote())))?; + + let local = to_local(tm); + let ft = local_dt_to_filetime(local); + + // // We have to check that ft is valid time. Due to daylight saving + // // time switch, local time can jump from 1:59 AM to 3:00 AM, + // // in which case any time between 2:00 AM and 2:59 AM is not valid. + // // Convert back to local time and see if we got the same value back. + // let ts = time::Timespec { + // sec: ft.unix_seconds(), + // nsec: 0, + // }; + // let tm2 = time::at(ts); + // if tm.tm_hour != tm2.tm_hour { + // return Err(USimpleError::new( + // 1, + // format!("invalid date format {}", s.quote()), + // )); + // } + Ok(ft) } diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index a93344dbc..5c64cb5af 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -111,9 +111,9 @@ fn process_utmpx() -> (Option, usize) { match line.record_type() { USER_PROCESS => nusers += 1, BOOT_TIME => { - let t = line.login_time().to_timespec(); - if t.sec > 0 { - boot_time = Some(t.sec as time_t); + let dt = line.login_time(); + if dt.second() > 0 { + boot_time = Some(dt.second() as time_t); } } _ => continue, diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 6e21ac912..47d96a381 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -275,10 +275,10 @@ struct Who { fn idle_string<'a>(when: i64, boottime: i64) -> Cow<'a, str> { thread_local! { - static NOW: time::Tm = time::now() + static NOW: time::OffsetDateTime = time::OffsetDateTime::now_local().unwrap(); } NOW.with(|n| { - let now = n.to_timespec().sec; + let now = n.unix_timestamp(); if boottime < when && now - 24 * 3600 < when && when <= now { let seconds_idle = now - when; if seconds_idle < 60 { @@ -298,7 +298,11 @@ fn idle_string<'a>(when: i64, boottime: i64) -> Cow<'a, str> { } fn time_string(ut: &Utmpx) -> String { - time::strftime("%b %e %H:%M", &ut.login_time()).unwrap() // LC_ALL=C + // "%b %e %H:%M" + let time_format: Vec = + time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]") + .unwrap(); + ut.login_time().format(&time_format).unwrap() // LC_ALL=C } #[inline] diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 6f74b238a..5b363376d 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -26,7 +26,7 @@ wild = "2.0" # * optional itertools = { version="0.10.0", optional=true } thiserror = { version="1.0", optional=true } -time = { version="<= 0.1.43", optional=true } +time = { version="<= 0.3.10", optional=true, features = ["formatting", "local-offset", "macros"] } # * "problem" dependencies (pinned) data-encoding = { version="2.1", optional=true } data-encoding-macro = { version="0.1.12", optional=true } @@ -62,6 +62,6 @@ process = ["libc"] ringbuffer = [] signals = [] utf8 = [] -utmpx = ["time", "libc", "dns-lookup"] +utmpx = ["time", "time/macros", "libc", "dns-lookup"] wide = [] pipes = ["nix"] diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index eeaf54061..838bfd4b5 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -12,6 +12,7 @@ // spell-checker:ignore (arch) bitrig ; (fs) cifs smbfs extern crate time; +use time::macros::format_description; pub use crate::*; // import macros from `../../macros.rs` @@ -63,7 +64,6 @@ fn LPWSTR2String(buf: &[u16]) -> String { String::from_utf16(&buf[..len]).unwrap() } -use self::time::Timespec; #[cfg(unix)] use libc::{ mode_t, strerror, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, @@ -732,11 +732,17 @@ where } } +// match strftime "%Y-%m-%d %H:%M:%S.%f %z" +const PRETTY_DATETIME_FORMAT: &[time::format_description::FormatItem] = format_description!("[year]-[month]-[day padding:zero] [hour]:[minute]:[second].[subsecond] [offset_hour][offset_minute]"); + pub fn pretty_time(sec: i64, nsec: i64) -> String { // sec == seconds since UNIX_EPOCH // nsec == nanoseconds since (UNIX_EPOCH + sec) - let tm = time::at(Timespec::new(sec, nsec as i32)); - let res = time::strftime("%Y-%m-%d %H:%M:%S.%f %z", &tm).unwrap(); + let ts_nanos: i128 = (sec * 1_000_000_000 + nsec).into(); + // TODO: return errors to caller + let tm = time::OffsetDateTime::from_unix_timestamp_nanos(ts_nanos).unwrap(); + + let res = tm.format(&PRETTY_DATETIME_FORMAT).unwrap(); if res.ends_with(" -0000") { res.replace(" -0000", " +0000") } else { diff --git a/src/uucore/src/lib/features/utmpx.rs b/src/uucore/src/lib/features/utmpx.rs index 302d03d71..dc66eae09 100644 --- a/src/uucore/src/lib/features/utmpx.rs +++ b/src/uucore/src/lib/features/utmpx.rs @@ -32,7 +32,6 @@ //! ``` pub extern crate time; -use self::time::{Timespec, Tm}; use std::ffi::CString; use std::io::Result as IOResult; @@ -189,11 +188,14 @@ impl Utmpx { chars2string!(self.inner.ut_line) } /// A.K.A. ut.ut_tv - pub fn login_time(&self) -> Tm { - time::at(Timespec::new( - self.inner.ut_tv.tv_sec as i64, - self.inner.ut_tv.tv_usec as i32, - )) + pub fn login_time(&self) -> time::OffsetDateTime { + let ts_nanos: i128 = (self.inner.ut_tv.tv_sec as i64 * 1_000_000_000 as i64 + + self.inner.ut_tv.tv_usec as i64 * 1_000 as i64) + .into(); + let local_offset = time::OffsetDateTime::now_local().unwrap().offset(); + time::OffsetDateTime::from_unix_timestamp_nanos(ts_nanos) + .unwrap() + .to_offset(local_offset) } /// A.K.A. ut.ut_exit /// diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index c4ec03d95..06f4d5259 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -595,9 +595,9 @@ fn test_mv_update_option() { at.touch(file_a); at.touch(file_b); - let ts = time::now().to_timespec(); - let now = FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32); - let later = FileTime::from_unix_time(ts.sec as i64 + 3600, ts.nsec as u32); + let ts = time::OffsetDateTime::now_local().unwrap(); + let now = FileTime::from_unix_time(ts.unix_timestamp(), ts.nanosecond()); + let later = FileTime::from_unix_time(ts.unix_timestamp() as i64 + 3600, ts.nanosecond() as u32); filetime::set_file_times(at.plus_as_string(file_a), now, now).unwrap(); filetime::set_file_times(at.plus_as_string(file_b), now, later).unwrap(); diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 6e5d656c4..6de635831 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -4,6 +4,7 @@ extern crate touch; use self::touch::filetime::{self, FileTime}; extern crate time; +use time::macros::{datetime, format_description}; use crate::common::util::*; use std::fs::remove_file; @@ -32,11 +33,18 @@ fn set_file_times(at: &AtPath, path: &str, atime: FileTime, mtime: FileTime) { // Adjusts for local timezone fn str_to_filetime(format: &str, s: &str) -> FileTime { - let mut tm = time::strptime(s, format).unwrap(); - tm.tm_utcoff = time::now().tm_utcoff; - tm.tm_isdst = -1; // Unknown flag DST - let ts = tm.to_timespec(); - FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32) + let format_description = match format { + "%y%m%d%H%M" => format_description!("[year repr:last_two][month][day][hour][minute]"), + "%y%m%d%H%M.%S" => { + format_description!("[year repr:last_two][month][day][hour][minute].[second]") + } + "%Y%m%d%H%M" => format_description!("[year][month][day][hour][minute]"), + "%Y%m%d%H%M.%S" => format_description!("[year][month][day][hour][minute].[second]"), + _ => panic!("unexpected dt format"), + }; + let tm = time::PrimitiveDateTime::parse(&s, &format_description).unwrap(); + let offset_dt = tm.assume_offset(time::OffsetDateTime::now_local().unwrap().offset()); + FileTime::from_unix_time(offset_dt.unix_timestamp(), tm.nanosecond()) } #[test] @@ -83,7 +91,10 @@ fn test_touch_set_mdhm_time() { let start_of_year = str_to_filetime( "%Y%m%d%H%M", - &format!("{}01010000", 1900 + time::now().tm_year), + &format!( + "{}01010000", + time::OffsetDateTime::now_local().unwrap().year() + ), ); let (atime, mtime) = get_file_times(&at, file); assert_eq!(atime, mtime); @@ -104,7 +115,7 @@ fn test_touch_set_mdhms_time() { let start_of_year = str_to_filetime( "%Y%m%d%H%M.%S", - &format!("{}01010000.00", 1900 + time::now().tm_year), + &format!("{}01010000.00", time::OffsetDateTime::now_utc().year()), ); let (atime, mtime) = get_file_times(&at, file); assert_eq!(atime, mtime); @@ -123,7 +134,7 @@ fn test_touch_set_ymdhm_time() { assert!(at.file_exists(file)); - let start_of_year = str_to_filetime("%y%m%d%H%M", "1501010000"); + let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); let (atime, mtime) = get_file_times(&at, file); assert_eq!(atime, mtime); assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), 45240); @@ -141,7 +152,7 @@ fn test_touch_set_ymdhms_time() { assert!(at.file_exists(file)); - let start_of_year = str_to_filetime("%y%m%d%H%M.%S", "1501010000.00"); + let start_of_year = str_to_filetime("%Y%m%d%H%M.%S", "201501010000.00"); let (atime, mtime) = get_file_times(&at, file); assert_eq!(atime, mtime); assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), 45296); @@ -430,18 +441,18 @@ fn test_touch_mtime_dst_succeeds() { assert_eq!(target_time, mtime); } -// is_dst_switch_hour returns true if timespec ts is just before the switch -// to Daylight Saving Time. -// For example, in EST (UTC-5), Timespec { sec: 1583647200, nsec: 0 } -// for March 8 2020 01:00:00 AM -// is just before the switch because on that day clock jumps by 1 hour, -// so 1 minute after 01:59:00 is 03:00:00. -fn is_dst_switch_hour(ts: time::Timespec) -> bool { - let ts_after = ts + time::Duration::hours(1); - let tm = time::at(ts); - let tm_after = time::at(ts_after); - tm_after.tm_hour == tm.tm_hour + 2 -} +// // is_dst_switch_hour returns true if timespec ts is just before the switch +// // to Daylight Saving Time. +// // For example, in EST (UTC-5), Timespec { sec: 1583647200, nsec: 0 } +// // for March 8 2020 01:00:00 AM +// // is just before the switch because on that day clock jumps by 1 hour, +// // so 1 minute after 01:59:00 is 03:00:00. +// fn is_dst_switch_hour(ts: time::Timespec) -> bool { +// let ts_after = ts + time::Duration::hours(1); +// let tm = time::at(ts); +// let tm_after = time::at(ts_after); +// tm_after.tm_hour == tm.tm_hour + 2 +// } // get_dst_switch_hour returns date string for which touch -m -t fails. // For example, in EST (UTC-5), that will be "202003080200" so @@ -450,23 +461,24 @@ fn is_dst_switch_hour(ts: time::Timespec) -> bool { // In other locales it will be a different date/time, and in some locales // it doesn't exist at all, in which case this function will return None. fn get_dst_switch_hour() -> Option { - let now = time::now(); + let now = time::OffsetDateTime::now_local().unwrap(); + // Start from January 1, 2020, 00:00. - let mut tm = time::strptime("20200101-0000", "%Y%m%d-%H%M").unwrap(); - tm.tm_isdst = -1; - tm.tm_utcoff = now.tm_utcoff; - let mut ts = tm.to_timespec(); - // Loop through all hours in year 2020 until we find the hour just - // before the switch to DST. - for _i in 0..(366 * 24) { - if is_dst_switch_hour(ts) { - let mut tm = time::at(ts); - tm.tm_hour += 1; - let s = time::strftime("%Y%m%d%H%M", &tm).unwrap(); - return Some(s); - } - ts = ts + time::Duration::hours(1); - } + let tm = datetime!(2020-01-01 00:00 UTC); + tm.to_offset(now.offset()); + + // let mut ts = tm.to_timespec(); + // // Loop through all hours in year 2020 until we find the hour just + // // before the switch to DST. + // for _i in 0..(366 * 24) { + // // if is_dst_switch_hour(ts) { + // // let mut tm = time::at(ts); + // // tm.tm_hour += 1; + // // let s = time::strftime("%Y%m%d%H%M", &tm).unwrap(); + // // return Some(s); + // // } + // ts = ts + time::Duration::hours(1); + // } None }