#[macro_use]
extern crate uucore;
use chrono::{DateTime, FixedOffset, Local, Offset, Utc};
#[cfg(windows)]
use chrono::{Datelike, Timelike};
use clap::{App, Arg};
#[cfg(all(unix, not(target_os = "macos")))]
use libc::{clock_settime, timespec, CLOCK_REALTIME};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
#[cfg(windows)]
use winapi::{
shared::minwindef::WORD,
um::{minwinbase::SYSTEMTIME, sysinfoapi::SetSystemTime},
};
const DATE: &str = "date";
const HOURS: &str = "hours";
const MINUTES: &str = "minutes";
const SECONDS: &str = "seconds";
const HOUR: &str = "hour";
const MINUTE: &str = "minute";
const SECOND: &str = "second";
const NS: &str = "ns";
const NAME: &str = "date";
const VERSION: &str = env!("CARGO_PKG_VERSION");
const ABOUT: &str = "print or set the system date and time";
const OPT_DATE: &str = "date";
const OPT_FORMAT: &str = "format";
const OPT_FILE: &str = "file";
const OPT_DEBUG: &str = "debug";
const OPT_ISO_8601: &str = "iso-8601";
const OPT_RFC_EMAIL: &str = "rfc-email";
const OPT_RFC_3339: &str = "rfc-3339";
const OPT_SET: &str = "set";
const OPT_REFERENCE: &str = "reference";
const OPT_UNIVERSAL: &str = "universal";
const OPT_UNIVERSAL_2: &str = "utc";
static ISO_8601_HELP_STRING: &str = "output date/time in ISO 8601 format.
FMT='date' for date only (the default),
'hours', 'minutes', 'seconds', or 'ns'
for date and time to the indicated precision.
Example: 2006-08-14T02:34:56-06:00";
static RFC_5322_HELP_STRING: &str = "output date and time in RFC 5322 format.
Example: Mon, 14 Aug 2006 02:34:56 -0600";
static RFC_3339_HELP_STRING: &str = "output date/time in RFC 3339 format.
FMT='date', 'seconds', or 'ns'
for date and time to the indicated precision.
Example: 2006-08-14 02:34:56-06:00";
#[cfg(not(target_os = "macos"))]
static OPT_SET_HELP_STRING: &str = "set time described by STRING";
#[cfg(target_os = "macos")]
static OPT_SET_HELP_STRING: &str = "set time described by STRING (not available on mac yet)";
struct Settings {
utc: bool,
format: Format,
date_source: DateSource,
set_to: Option<DateTime<FixedOffset>>,
}
enum Format {
Iso8601(Iso8601Format),
Rfc5322,
Rfc3339(Rfc3339Format),
Custom(String),
Default,
}
enum DateSource {
Now,
Custom(String),
File(PathBuf),
}
enum Iso8601Format {
Date,
Hours,
Minutes,
Seconds,
Ns,
}
impl<'a> From<&'a str> for Iso8601Format {
fn from(s: &str) -> Self {
match s {
HOURS | HOUR => Iso8601Format::Hours,
MINUTES | MINUTE => Iso8601Format::Minutes,
SECONDS | SECOND => Iso8601Format::Seconds,
NS => Iso8601Format::Ns,
DATE => Iso8601Format::Date,
_ => panic!("Invalid format: {}", s),
}
}
}
enum Rfc3339Format {
Date,
Seconds,
Ns,
}
impl<'a> From<&'a str> for Rfc3339Format {
fn from(s: &str) -> Self {
match s {
DATE => Rfc3339Format::Date,
SECONDS | SECOND => Rfc3339Format::Seconds,
NS => Rfc3339Format::Ns,
_ => panic!("Invalid format: {}", s),
}
}
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let syntax = format!(
"{0} [OPTION]... [+FORMAT]...
{0} [OPTION]... [MMDDhhmm[[CC]YY][.ss]]",
NAME
);
let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(&syntax[..])
.arg(
Arg::with_name(OPT_DATE)
.short("d")
.long(OPT_DATE)
.takes_value(true)
.help("display time described by STRING, not 'now'"),
)
.arg(
Arg::with_name(OPT_FILE)
.short("f")
.long(OPT_FILE)
.takes_value(true)
.help("like --date; once for each line of DATEFILE"),
)
.arg(
Arg::with_name(OPT_ISO_8601)
.short("I")
.long(OPT_ISO_8601)
.takes_value(true)
.help(ISO_8601_HELP_STRING),
)
.arg(
Arg::with_name(OPT_RFC_EMAIL)
.short("R")
.long(OPT_RFC_EMAIL)
.help(RFC_5322_HELP_STRING),
)
.arg(
Arg::with_name(OPT_RFC_3339)
.long(OPT_RFC_3339)
.takes_value(true)
.help(RFC_3339_HELP_STRING),
)
.arg(
Arg::with_name(OPT_DEBUG)
.long(OPT_DEBUG)
.help("annotate the parsed date, and warn about questionable usage to stderr"),
)
.arg(
Arg::with_name(OPT_REFERENCE)
.short("r")
.long(OPT_REFERENCE)
.takes_value(true)
.help("display the last modification time of FILE"),
)
.arg(
Arg::with_name(OPT_SET)
.short("s")
.long(OPT_SET)
.takes_value(true)
.help(OPT_SET_HELP_STRING),
)
.arg(
Arg::with_name(OPT_UNIVERSAL)
.short("u")
.long(OPT_UNIVERSAL)
.alias(OPT_UNIVERSAL_2)
.help("print or set Coordinated Universal Time (UTC)"),
)
.arg(Arg::with_name(OPT_FORMAT).multiple(true))
.get_matches_from(args);
let format = if let Some(form) = matches.value_of(OPT_FORMAT) {
let form = form[1..].into();
Format::Custom(form)
} else if let Some(fmt) = matches
.values_of(OPT_ISO_8601)
.map(|mut iter| iter.next().unwrap_or(DATE).into())
{
Format::Iso8601(fmt)
} else if matches.is_present(OPT_RFC_EMAIL) {
Format::Rfc5322
} else if let Some(fmt) = matches.value_of(OPT_RFC_3339).map(Into::into) {
Format::Rfc3339(fmt)
} else {
Format::Default
};
let date_source = if let Some(date) = matches.value_of(OPT_DATE) {
DateSource::Custom(date.into())
} else if let Some(file) = matches.value_of(OPT_FILE) {
DateSource::File(file.into())
} else {
DateSource::Now
};
let set_to = match matches.value_of(OPT_SET).map(parse_date) {
None => None,
Some(Err((input, _err))) => {
eprintln!("date: invalid date '{}'", input);
return 1;
}
Some(Ok(date)) => Some(date),
};
let settings = Settings {
utc: matches.is_present(OPT_UNIVERSAL),
format,
date_source,
set_to,
};
if let Some(date) = settings.set_to {
let date: DateTime<Utc> = if settings.utc {
date.with_timezone(&Utc)
} else {
date.into()
};
return set_system_datetime(date);
} else {
let file: File;
let now: DateTime<FixedOffset> = if settings.utc {
let now = Utc::now();
now.with_timezone(&now.offset().fix())
} else {
let now = Local::now();
now.with_timezone(now.offset())
};
let dates: Box<dyn Iterator<Item = _>> = match settings.date_source {
DateSource::Custom(ref input) => {
let date = parse_date(input.clone());
let iter = std::iter::once(date);
Box::new(iter)
}
DateSource::File(ref path) => {
file = File::open(path).unwrap();
let lines = BufReader::new(file).lines();
let iter = lines.filter_map(Result::ok).map(parse_date);
Box::new(iter)
}
DateSource::Now => {
let iter = std::iter::once(Ok(now));
Box::new(iter)
}
};
let format_string = make_format_string(&settings);
for date in dates {
match date {
Ok(date) => {
let formatted = date.format(format_string);
println!("{}", formatted);
}
Err((input, _err)) => {
println!("date: invalid date '{}'", input);
}
}
}
}
0
}
fn make_format_string(settings: &Settings) -> &str {
match settings.format {
Format::Iso8601(ref fmt) => match *fmt {
Iso8601Format::Date => "%F",
Iso8601Format::Hours => "%FT%H%:z",
Iso8601Format::Minutes => "%FT%H:%M%:z",
Iso8601Format::Seconds => "%FT%T%:z",
Iso8601Format::Ns => "%FT%T,%f%:z",
},
Format::Rfc5322 => "%a, %d %h %Y %T %z",
Format::Rfc3339(ref fmt) => match *fmt {
Rfc3339Format::Date => "%F",
Rfc3339Format::Seconds => "%F %T%:z",
Rfc3339Format::Ns => "%F %T.%f%:z",
},
Format::Custom(ref fmt) => fmt,
Format::Default => "%c",
}
}
fn parse_date<S: AsRef<str> + Clone>(
s: S,
) -> Result<DateTime<FixedOffset>, (String, chrono::format::ParseError)> {
s.as_ref().parse().map_err(|e| (s.as_ref().into(), e))
}
#[cfg(not(any(unix, windows)))]
fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
unimplemented!("setting date not implemented (unsupported target)");
}
#[cfg(target_os = "macos")]
fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
eprintln!("date: setting the date is not supported by macOS");
return 1;
}
#[cfg(all(unix, not(target_os = "macos")))]
fn set_system_datetime(date: DateTime<Utc>) -> i32 {
let timespec = timespec {
tv_sec: date.timestamp() as _,
tv_nsec: date.timestamp_subsec_nanos() as _,
};
let result = unsafe { clock_settime(CLOCK_REALTIME, ×pec) };
if result != 0 {
let error = std::io::Error::last_os_error();
eprintln!("date: cannot set date: {}", error);
error.raw_os_error().unwrap()
} else {
0
}
}
#[cfg(windows)]
fn set_system_datetime(date: DateTime<Utc>) -> i32 {
let system_time = SYSTEMTIME {
wYear: date.year() as WORD,
wMonth: date.month() as WORD,
wDayOfWeek: 0,
wDay: date.day() as WORD,
wHour: date.hour() as WORD,
wMinute: date.minute() as WORD,
wSecond: date.second() as WORD,
wMilliseconds: ((date.nanosecond() / 1_000_000) % 1000) as WORD,
};
let result = unsafe { SetSystemTime(&system_time) };
if result == 0 {
let error = std::io::Error::last_os_error();
eprintln!("date: cannot set date: {}", error);
error.raw_os_error().unwrap()
} else {
0
}
}