Skip to content

Commit 971f074

Browse files
authored
Merge pull request #42 from rustcoreutils/date
add util: date
2 parents f8db80b + 29f0a2f commit 971f074

File tree

4 files changed

+170
-1
lines changed

4 files changed

+170
-1
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ https://github.com/jgarzik/posixutils
6262
- [ ] ctags (Development)
6363
- [ ] cut
6464
- [ ] cxref (Development)
65-
- [ ] date
65+
- [x] date
6666
- [x] dd
6767
- [ ] delta (SCCS)
6868
- [x] df

datetime/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@ plib = { path = "../plib" }
1111
clap = { version = "4", features = ["derive"] }
1212
gettext-rs = { version = "0.7", features = ["gettext-system"] }
1313
chrono = "0.4"
14+
libc = "0.2"
1415

1516
[[bin]]
1617
name = "cal"
1718
path = "src/cal.rs"
1819

20+
[[bin]]
21+
name = "date"
22+
path = "src/date.rs"
23+
1924
[[bin]]
2025
name = "sleep"
2126
path = "src/sleep.rs"

datetime/src/date.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
//
2+
// Copyright (c) 2024 Jeff Garzik
3+
//
4+
// This file is part of the posixutils-rs project covered under
5+
// the MIT License. For the full license text, please see the LICENSE
6+
// file in the root directory of this project.
7+
// SPDX-License-Identifier: MIT
8+
//
9+
// TODO:
10+
// - add tests (how?)
11+
// - double-check that Rust stftime() is POSIX compliant
12+
//
13+
14+
extern crate clap;
15+
extern crate plib;
16+
17+
use chrono::{DateTime, Datelike, Local, LocalResult, TimeZone, Utc};
18+
use clap::Parser;
19+
use gettextrs::{bind_textdomain_codeset, textdomain};
20+
use plib::PROJECT_NAME;
21+
22+
const DEF_TIMESTR: &str = "%a %b %e %H:%M:%S %Z %Y";
23+
24+
/// date - write the date and time
25+
#[derive(Parser, Debug)]
26+
#[command(author, version, about, long_about)]
27+
struct Args {
28+
/// Perform operations as if the TZ env var was set to the string "UTC0"
29+
#[arg(short, long)]
30+
utc: bool,
31+
32+
/// If prefixed with '+', Display the current time in the given FORMAT,
33+
/// as in strftime(3). Otherwise, set the current time to the given
34+
/// string.
35+
timestr: Option<String>,
36+
}
37+
38+
fn show_time_local(formatstr: &str) -> String {
39+
let now = chrono::Local::now();
40+
now.format(formatstr).to_string()
41+
}
42+
43+
fn show_time_utc(formatstr: &str) -> String {
44+
let now = chrono::Utc::now();
45+
now.format(formatstr).to_string()
46+
}
47+
48+
fn show_time(utc: bool, formatstr: &str) {
49+
let timestr = {
50+
if utc {
51+
show_time_utc(formatstr)
52+
} else {
53+
show_time_local(formatstr)
54+
}
55+
};
56+
57+
println!("{}", timestr);
58+
}
59+
60+
fn set_time(utc: bool, timestr: &str) -> Result<(), &'static str> {
61+
for ch in timestr.chars() {
62+
if !ch.is_ascii_digit() {
63+
return Err("date: invalid date");
64+
}
65+
}
66+
67+
let cur_year = {
68+
if utc {
69+
let now = chrono::Utc::now();
70+
now.year()
71+
} else {
72+
let now = chrono::Local::now();
73+
now.year()
74+
}
75+
};
76+
77+
let (year, month, day, hour, minute) = match timestr.len() {
78+
8 => {
79+
let month = timestr[0..2].parse::<u32>().unwrap();
80+
let day = timestr[2..4].parse::<u32>().unwrap();
81+
let hour = timestr[4..6].parse::<u32>().unwrap();
82+
let minute = timestr[6..8].parse::<u32>().unwrap();
83+
(cur_year, month, day, hour, minute)
84+
}
85+
10 => {
86+
let month = timestr[0..2].parse::<u32>().unwrap();
87+
let day = timestr[2..4].parse::<u32>().unwrap();
88+
let hour = timestr[4..6].parse::<u32>().unwrap();
89+
let minute = timestr[6..8].parse::<u32>().unwrap();
90+
let year = timestr[8..10].parse::<i32>().unwrap();
91+
if year < 70 {
92+
(year + 2000, month, day, hour, minute)
93+
} else {
94+
(year + 1900, month, day, hour, minute)
95+
}
96+
}
97+
12 => {
98+
let month = timestr[0..2].parse::<u32>().unwrap();
99+
let day = timestr[2..4].parse::<u32>().unwrap();
100+
let hour = timestr[4..6].parse::<u32>().unwrap();
101+
let minute = timestr[6..8].parse::<u32>().unwrap();
102+
let year = timestr[8..12].parse::<i32>().unwrap();
103+
(year, month, day, hour, minute)
104+
}
105+
_ => {
106+
return Err("date: invalid date");
107+
}
108+
};
109+
110+
// calculate system time
111+
let new_time = {
112+
if utc {
113+
match chrono::Utc.with_ymd_and_hms(year, month, day, hour, minute, 0) {
114+
LocalResult::<DateTime<Utc>>::Single(t) => t.timestamp(),
115+
_ => {
116+
return Err("date: invalid date");
117+
}
118+
}
119+
} else {
120+
match chrono::Local.with_ymd_and_hms(year, month, day, hour, minute, 0) {
121+
LocalResult::<DateTime<Local>>::Single(t) => t.timestamp(),
122+
_ => {
123+
return Err("date: invalid date");
124+
}
125+
}
126+
}
127+
};
128+
129+
let new_time = libc::timespec {
130+
tv_sec: new_time,
131+
tv_nsec: 0,
132+
};
133+
134+
// set system time
135+
unsafe {
136+
if libc::clock_settime(libc::CLOCK_REALTIME, &new_time) != 0 {
137+
return Err("date: failed to set time");
138+
}
139+
}
140+
141+
Ok(())
142+
}
143+
144+
fn main() -> Result<(), Box<dyn std::error::Error>> {
145+
// parse command line arguments
146+
let args = Args::parse();
147+
148+
textdomain(PROJECT_NAME)?;
149+
bind_textdomain_codeset(PROJECT_NAME, "UTF-8")?;
150+
151+
match &args.timestr {
152+
None => show_time(args.utc, DEF_TIMESTR),
153+
Some(timestr) => {
154+
if timestr.starts_with("+") {
155+
show_time(args.utc, &timestr[1..]);
156+
} else {
157+
set_time(args.utc, &timestr)?;
158+
}
159+
}
160+
}
161+
162+
Ok(())
163+
}

0 commit comments

Comments
 (0)