mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 11:07:44 +00:00
Merge pull request #3319 from g-k/2884-time-0.3
update time crate to 0.3
This commit is contained in:
commit
779eca4983
19 changed files with 518 additions and 135 deletions
|
@ -9,3 +9,12 @@ rustflags = [
|
|||
"-Wclippy::single_char_pattern",
|
||||
"-Wclippy::explicit_iter_loop",
|
||||
]
|
||||
|
||||
[build]
|
||||
# See https://github.com/time-rs/time/issues/293#issuecomment-1005002386. The
|
||||
# unsoundness here is not in the `time` library, but in the Rust stdlib, and as
|
||||
# such it needs to be fixed there.
|
||||
rustflags = "--cfg unsound_local_offset"
|
||||
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["--cfg", "unsound_local_offset"]
|
||||
|
|
4
.github/workflows/CICD.yml
vendored
4
.github/workflows/CICD.yml
vendored
|
@ -384,9 +384,9 @@ jobs:
|
|||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils
|
||||
args: -v ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils
|
||||
env:
|
||||
RUSTFLAGS: "-Awarnings"
|
||||
RUSTFLAGS: "-Awarnings --cfg unsound_local_offset"
|
||||
|
||||
deps:
|
||||
name: Dependencies
|
||||
|
|
53
Cargo.lock
generated
53
Cargo.lock
generated
|
@ -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",
|
||||
|
@ -871,7 +871,7 @@ checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
|
|||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"wasi 0.10.2+wasi-snapshot-preview1",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -985,6 +985,12 @@ dependencies = [
|
|||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||
|
||||
[[package]]
|
||||
name = "keccak"
|
||||
version = "0.1.0"
|
||||
|
@ -1213,6 +1219,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,14 +1959,33 @@ 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 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"libc",
|
||||
"num_threads",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.15.0"
|
||||
|
@ -2400,6 +2434,7 @@ dependencies = [
|
|||
"file_diff",
|
||||
"filetime",
|
||||
"libc",
|
||||
"time 0.3.9",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
|
@ -2875,7 +2910,7 @@ version = "0.0.13"
|
|||
dependencies = [
|
||||
"clap 3.1.15",
|
||||
"filetime",
|
||||
"time",
|
||||
"time 0.3.9",
|
||||
"uucore",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
@ -3042,7 +3077,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"os_display",
|
||||
"thiserror",
|
||||
"time",
|
||||
"time 0.3.9",
|
||||
"uucore_procs",
|
||||
"walkdir",
|
||||
"wild",
|
||||
|
@ -3090,9 +3125,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
|
|
|
@ -391,7 +391,7 @@ rand = "0.8"
|
|||
regex = "1.5"
|
||||
sha1 = { version="0.10", features=["std"] }
|
||||
tempfile = "3"
|
||||
time = "0.1"
|
||||
time = {version="0.3", features=["local-offset"]}
|
||||
unindent = "0.1"
|
||||
uucore = { version=">=0.0.11", package="uucore", path="src/uucore", features=["entries", "process"] }
|
||||
walkdir = "2.2"
|
||||
|
|
|
@ -11,6 +11,8 @@ unmaintained = "warn"
|
|||
yanked = "warn"
|
||||
notice = "warn"
|
||||
ignore = [
|
||||
"RUSTSEC-2020-0159",
|
||||
"RUSTSEC-2020-0071",
|
||||
#"RUSTSEC-0000-0000",
|
||||
]
|
||||
|
||||
|
@ -62,7 +64,7 @@ highlight = "all"
|
|||
# spell-checker: disable
|
||||
skip = [
|
||||
# getrandom
|
||||
{ name = "wasi", version="0.10.2+wasi-snapshot-preview1" },
|
||||
{ name = "wasi", version="0.10.0+wasi-snapshot-preview1" },
|
||||
# blake2d_simd
|
||||
{ name = "arrayvec", version = "=0.7.2" },
|
||||
# flimit/unix_socket
|
||||
|
@ -84,6 +86,8 @@ skip = [
|
|||
{ name = "memchr", version = "=1.0.2" },
|
||||
{ name = "quote", version = "=0.3.15" },
|
||||
{ name = "unicode-xid", version = "=0.0.4" },
|
||||
# chrono
|
||||
{ name = "time", version = "=0.1.44" },
|
||||
]
|
||||
# spell-checker: enable
|
||||
|
||||
|
|
|
@ -24,6 +24,9 @@ file_diff = "1.0.0"
|
|||
libc = ">= 0.2"
|
||||
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "mode", "perms", "entries"] }
|
||||
|
||||
[dev-dependencies]
|
||||
time = "0.3"
|
||||
|
||||
[[bin]]
|
||||
name = "install"
|
||||
path = "src/main.rs"
|
||||
|
|
|
@ -221,10 +221,10 @@ impl Capitalize for str {
|
|||
|
||||
fn idle_string(when: i64) -> 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::FormatItem> =
|
||||
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<String> {
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
// For the full copyright and license information, please view the LICENSE file
|
||||
// that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm clapv PWSTR lpszfilepath hresult
|
||||
|
||||
// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm clapv PWSTR lpszfilepath hresult mktime YYYYMMDDHHMM YYMMDDHHMM DATETIME YYYYMMDDHHMMS subsecond
|
||||
pub extern crate filetime;
|
||||
|
||||
#[macro_use]
|
||||
|
@ -17,6 +16,8 @@ use clap::{crate_version, Arg, ArgGroup, Command};
|
|||
use filetime::*;
|
||||
use std::fs::{self, File};
|
||||
use std::path::{Path, PathBuf};
|
||||
use time::macros::{format_description, offset, time};
|
||||
use time::Duration;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UError, UResult, USimpleError};
|
||||
use uucore::format_usage;
|
||||
|
@ -41,14 +42,27 @@ 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
|
||||
// Convert a date/time to a date with a TZ offset
|
||||
fn to_local(tm: time::PrimitiveDateTime) -> time::OffsetDateTime {
|
||||
let offset = match time::OffsetDateTime::now_local() {
|
||||
Ok(lo) => lo.offset(),
|
||||
Err(e) => {
|
||||
panic!("error: {}", e);
|
||||
}
|
||||
};
|
||||
tm.assume_offset(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)
|
||||
// Convert a date/time with a TZ offset into a FileTime
|
||||
fn local_dt_to_filetime(dt: time::OffsetDateTime) -> FileTime {
|
||||
FileTime::from_unix_time(dt.unix_timestamp(), dt.nanosecond())
|
||||
}
|
||||
|
||||
// Convert a date/time, considering that the input is in UTC time
|
||||
// Used for touch -d 1970-01-01 18:43:33.023456789 for example
|
||||
fn dt_to_filename(tm: time::PrimitiveDateTime) -> FileTime {
|
||||
let dt = tm.assume_offset(offset!(UTC));
|
||||
local_dt_to_filetime(dt)
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
|
@ -62,7 +76,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 +85,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 +261,129 @@ fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> {
|
|||
))
|
||||
}
|
||||
|
||||
fn parse_date(str: &str) -> UResult<FileTime> {
|
||||
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]");
|
||||
|
||||
// "%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:%S.%SS" 12 chars
|
||||
const YYYYMMDDHHMMSS_FORMAT: &[time::format_description::FormatItem] = format_description!(
|
||||
"[year repr:full]-[month repr:numerical padding:zero]-\
|
||||
[day] [hour]:[minute]:[second].[subsecond]"
|
||||
);
|
||||
|
||||
// "%Y-%m-%d %H:%M:%S" 12 chars
|
||||
const YYYYMMDDHHMMS_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
|
||||
// Used for example in tests/touch/no-rights.sh
|
||||
const YYYY_MM_DD_HH_MM_FORMAT: &[time::format_description::FormatItem] = format_description!(
|
||||
"[year repr:full]-[month repr:numerical padding:zero]-\
|
||||
[day] [hour]:[minute]"
|
||||
);
|
||||
|
||||
// "%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]"
|
||||
);
|
||||
|
||||
// "%Y-%m-%d %H:%M +offset"
|
||||
// Used for example in tests/touch/relative.sh
|
||||
const YYYYMMDDHHMM_OFFSET_FORMAT: &[time::format_description::FormatItem] = format_description!(
|
||||
"[year]-[month]-[day] [hour repr:24]:[minute] \
|
||||
[offset_hour sign:mandatory][offset_minute]"
|
||||
);
|
||||
|
||||
fn parse_date(s: &str) -> UResult<FileTime> {
|
||||
// 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)));
|
||||
}
|
||||
|
||||
// Also support other formats found in the GNU tests like
|
||||
// in tests/misc/stat-nanoseconds.sh
|
||||
// or tests/touch/no-rights.sh
|
||||
for fmt in [
|
||||
YYYYMMDDHHMMS_FORMAT,
|
||||
YYYYMMDDHHMMSS_FORMAT,
|
||||
YYYY_MM_DD_HH_MM_FORMAT,
|
||||
YYYYMMDDHHMM_OFFSET_FORMAT,
|
||||
] {
|
||||
if let Ok(parsed) = time::PrimitiveDateTime::parse(s, &fmt) {
|
||||
return Ok(dt_to_filename(parsed));
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(tm) = time::strptime(str, "@%s") {
|
||||
// "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::<i64>() {
|
||||
// Don't convert to local time in this case - seconds since epoch are not time-zone dependent
|
||||
return Ok(local_tm_to_filetime(tm));
|
||||
return Ok(local_dt_to_filetime(
|
||||
time::OffsetDateTime::from_unix_timestamp(*ts).unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Err(USimpleError::new(
|
||||
1,
|
||||
format!("Unable to parse date: {}", str),
|
||||
))
|
||||
Err(USimpleError::new(1, format!("Unable to parse date: {}", s)))
|
||||
}
|
||||
|
||||
fn parse_timestamp(s: &str) -> UResult<FileTime> {
|
||||
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 +391,53 @@ fn parse_timestamp(s: &str) -> UResult<FileTime> {
|
|||
))
|
||||
}
|
||||
};
|
||||
|
||||
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 leap_sec = if (format == YYYYMMDDHHMM_DOT_SS_FORMAT || format == YYMMDDHHMM_DOT_SS_FORMAT)
|
||||
&& ts.ends_with(".60")
|
||||
{
|
||||
// Work around to disable leap seconds
|
||||
// Used in gnu/tests/touch/60-seconds
|
||||
ts = ts.replace(".60", ".59");
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let tm = time::PrimitiveDateTime::parse(&ts, &format)
|
||||
.map_err(|_| USimpleError::new(1, format!("invalid date ts format {}", ts.quote())))?;
|
||||
let mut local = to_local(tm);
|
||||
if leap_sec {
|
||||
// We are dealing with a leap second, add it
|
||||
local = local.saturating_add(Duration::SECOND);
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -111,9 +111,9 @@ fn process_utmpx() -> (Option<time_t>, 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,
|
||||
|
|
|
@ -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::FormatItem> =
|
||||
time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]")
|
||||
.unwrap();
|
||||
ut.login_time().format(&time_format).unwrap() // LC_ALL=C
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -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", 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"]
|
||||
|
|
|
@ -9,9 +9,11 @@
|
|||
|
||||
//! Set of functions to manage file systems
|
||||
|
||||
// spell-checker:ignore (arch) bitrig ; (fs) cifs smbfs
|
||||
// spell-checker:ignore DATETIME subsecond (arch) bitrig ; (fs) cifs smbfs
|
||||
|
||||
extern crate time;
|
||||
use time::macros::format_description;
|
||||
use time::UtcOffset;
|
||||
|
||||
pub use crate::*; // import macros from `../../macros.rs`
|
||||
|
||||
|
@ -63,7 +65,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 +733,42 @@ 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 digits:9] \
|
||||
[offset_hour sign:mandatory][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();
|
||||
|
||||
// Return the date in UTC
|
||||
let tm = match time::OffsetDateTime::from_unix_timestamp_nanos(ts_nanos) {
|
||||
Ok(tm) => tm,
|
||||
Err(e) => {
|
||||
panic!("error: {}", e);
|
||||
}
|
||||
};
|
||||
|
||||
// Get the offset to convert to local time
|
||||
// Because of DST (daylight saving), we get the local time back when
|
||||
// the date was set
|
||||
let local_offset = match UtcOffset::local_offset_at(tm) {
|
||||
Ok(lo) => lo,
|
||||
Err(e) => {
|
||||
panic!("error: {}", e);
|
||||
}
|
||||
};
|
||||
|
||||
// Include the conversion to local time
|
||||
let res = tm
|
||||
.to_offset(local_offset)
|
||||
.format(&PRETTY_DATETIME_FORMAT)
|
||||
.unwrap();
|
||||
if res.ends_with(" -0000") {
|
||||
res.replace(" -0000", " +0000")
|
||||
} else {
|
||||
|
|
|
@ -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_i64
|
||||
+ self.inner.ut_tv.tv_usec as i64 * 1_000_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
|
||||
///
|
||||
|
|
|
@ -1032,8 +1032,8 @@ fn test_cp_no_deref_folder_to_folder() {
|
|||
#[cfg(target_os = "linux")]
|
||||
fn test_cp_archive() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let ts = time::now().to_timespec();
|
||||
let previous = FileTime::from_unix_time(ts.sec as i64 - 3600, ts.nsec as u32);
|
||||
let ts = time::OffsetDateTime::now_local().unwrap();
|
||||
let previous = FileTime::from_unix_time(ts.unix_timestamp() - 3600, ts.nanosecond() as u32);
|
||||
// set the file creation/modification an hour ago
|
||||
filetime::set_file_times(
|
||||
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
|
||||
|
@ -1135,8 +1135,8 @@ fn test_cp_archive_recursive() {
|
|||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fn test_cp_preserve_timestamps() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let ts = time::now().to_timespec();
|
||||
let previous = FileTime::from_unix_time(ts.sec as i64 - 3600, ts.nsec as u32);
|
||||
let ts = time::OffsetDateTime::now_local().unwrap();
|
||||
let previous = FileTime::from_unix_time(ts.unix_timestamp() - 3600, ts.nanosecond());
|
||||
// set the file creation/modification an hour ago
|
||||
filetime::set_file_times(
|
||||
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
|
||||
|
@ -1168,8 +1168,8 @@ fn test_cp_preserve_timestamps() {
|
|||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fn test_cp_no_preserve_timestamps() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let ts = time::now().to_timespec();
|
||||
let previous = FileTime::from_unix_time(ts.sec as i64 - 3600, ts.nsec as u32);
|
||||
let ts = time::OffsetDateTime::now_local().unwrap();
|
||||
let previous = FileTime::from_unix_time(ts.unix_timestamp() - 3600, ts.nanosecond());
|
||||
// set the file creation/modification an hour ago
|
||||
filetime::set_file_times(
|
||||
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -283,6 +283,40 @@ fn test_char() {
|
|||
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
|
||||
#[test]
|
||||
fn test_date() {
|
||||
// Just test the date for the time 0.3 change
|
||||
let args = [
|
||||
"-c",
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
"%z",
|
||||
#[cfg(target_os = "linux")]
|
||||
"/bin/sh",
|
||||
#[cfg(any(target_vendor = "apple"))]
|
||||
"%z",
|
||||
#[cfg(any(target_os = "android", target_vendor = "apple"))]
|
||||
"/bin/sh",
|
||||
];
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str();
|
||||
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
|
||||
// Just test the date for the time 0.3 change
|
||||
let args = [
|
||||
"-c",
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
"%z",
|
||||
#[cfg(target_os = "linux")]
|
||||
"/dev/ptmx",
|
||||
#[cfg(any(target_vendor = "apple"))]
|
||||
"%z",
|
||||
#[cfg(any(target_os = "android", target_vendor = "apple"))]
|
||||
"/dev/ptmx",
|
||||
];
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str();
|
||||
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
|
||||
}
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_multi_files() {
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
// spell-checker:ignore (formats) cymdhm cymdhms mdhm mdhms ymdhm ymdhms
|
||||
// spell-checker:ignore (formats) cymdhm cymdhms mdhm mdhms ymdhm ymdhms datetime mktime
|
||||
|
||||
// This test relies on
|
||||
// --cfg unsound_local_offset
|
||||
// https://github.com/time-rs/time/blob/deb8161b84f355b31e39ce09e40c4d6ce3fea837/src/sys/local_offset_at/unix.rs#L112-L120=
|
||||
// See https://github.com/time-rs/time/issues/293#issuecomment-946382614=
|
||||
// Defined in .cargo/config
|
||||
|
||||
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 +39,24 @@ 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 d = match time::OffsetDateTime::now_local() {
|
||||
Ok(now) => now,
|
||||
Err(e) => {
|
||||
panic!("Error {} retrieving the OffsetDateTime::now_local", e);
|
||||
}
|
||||
};
|
||||
let offset_dt = tm.assume_offset(d.offset());
|
||||
FileTime::from_unix_time(offset_dt.unix_timestamp(), tm.nanosecond())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -83,7 +103,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 +127,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 +146,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 +164,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);
|
||||
|
@ -404,6 +427,86 @@ fn test_touch_set_date3() {
|
|||
assert_eq!(mtime, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_set_date4() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "test_touch_set_date";
|
||||
|
||||
ucmd.args(&["-d", "1970-01-01 18:43:33", file])
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
|
||||
assert!(at.file_exists(file));
|
||||
|
||||
let expected = FileTime::from_unix_time(67413, 0);
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, mtime);
|
||||
assert_eq!(atime, expected);
|
||||
assert_eq!(mtime, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_set_date5() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "test_touch_set_date";
|
||||
|
||||
ucmd.args(&["-d", "1970-01-01 18:43:33.023456789", file])
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
|
||||
assert!(at.file_exists(file));
|
||||
|
||||
// Slightly different result on Windows for nano seconds
|
||||
// TODO: investigate
|
||||
#[cfg(windows)]
|
||||
let expected = FileTime::from_unix_time(67413, 23456700);
|
||||
#[cfg(not(windows))]
|
||||
let expected = FileTime::from_unix_time(67413, 23456789);
|
||||
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, mtime);
|
||||
assert_eq!(atime, expected);
|
||||
assert_eq!(mtime, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_set_date6() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "test_touch_set_date";
|
||||
|
||||
ucmd.args(&["-d", "2000-01-01 00:00", file])
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
|
||||
assert!(at.file_exists(file));
|
||||
|
||||
let expected = FileTime::from_unix_time(946684800, 0);
|
||||
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, mtime);
|
||||
assert_eq!(atime, expected);
|
||||
assert_eq!(mtime, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_set_date7() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "test_touch_set_date";
|
||||
|
||||
ucmd.args(&["-d", "2004-01-16 12:00 +0000", file])
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
|
||||
assert!(at.file_exists(file));
|
||||
|
||||
let expected = FileTime::from_unix_time(1074254400, 0);
|
||||
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, mtime);
|
||||
assert_eq!(atime, expected);
|
||||
assert_eq!(mtime, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_set_date_wrong_format() {
|
||||
let (_at, mut ucmd) = at_and_ucmd!();
|
||||
|
@ -430,18 +533,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 +553,30 @@ 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<String> {
|
||||
let now = time::now();
|
||||
//let now = time::OffsetDateTime::now_local().unwrap();
|
||||
let now = match time::OffsetDateTime::now_local() {
|
||||
Ok(now) => now,
|
||||
Err(e) => {
|
||||
panic!("Error {} retrieving the OffsetDateTime::now_local", e);
|
||||
}
|
||||
};
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
@ -573,3 +683,21 @@ fn test_no_dereference_no_file() {
|
|||
.stderr_contains("setting times of 'not-a-file-1': No such file or directory")
|
||||
.stderr_contains("setting times of 'not-a-file-2': No such file or directory");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_leap_second() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "test_touch_leap_sec";
|
||||
|
||||
ucmd.args(&["-t", "197001010000.60", file])
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
|
||||
assert!(at.file_exists(file));
|
||||
|
||||
let epoch = str_to_filetime("%Y%m%d%H%M", "197001010000");
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, mtime);
|
||||
assert_eq!(atime.unix_seconds() - epoch.unix_seconds(), 60);
|
||||
assert_eq!(mtime.unix_seconds() - epoch.unix_seconds(), 60);
|
||||
}
|
||||
|
|
|
@ -137,7 +137,8 @@ sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/tail-2/tail-n0f.s
|
|||
sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh
|
||||
sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh
|
||||
sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh
|
||||
sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh
|
||||
# tests/ls/abmon-align.sh - https://github.com/uutils/coreutils/issues/3505
|
||||
sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh tests/ls/abmon-align.sh
|
||||
sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh
|
||||
sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh
|
||||
sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue