Skip to content

Commit 70e11f0

Browse files
committed
Add 'date' command.
Lets you get and set the current time/date.
1 parent 8dad0ec commit 70e11f0

File tree

4 files changed

+84
-5
lines changed

4 files changed

+84
-5
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,4 @@ r0 = "1.0"
4343
postcard = "0.5"
4444
serde = { version = "1.0", default-features = false }
4545
menu = "0.3"
46+
chrono = { version = "0.4", default-features = false }

src/commands/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ mod hardware;
99
mod input;
1010
mod ram;
1111
mod screen;
12+
mod timedate;
1213

1314
pub static OS_MENU: menu::Menu<Ctx> = menu::Menu {
1415
label: "root",
1516
items: &[
17+
&timedate::DATE_ITEM,
1618
&config::COMMAND_ITEM,
1719
&hardware::LSHW_ITEM,
1820
&ram::HEXDUMP_ITEM,

src/commands/timedate.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//! CLI commands for getting/setting time/date
2+
3+
use chrono::{Datelike, Timelike};
4+
5+
use crate::{println, Ctx, API};
6+
7+
pub static DATE_ITEM: menu::Item<Ctx> = menu::Item {
8+
item_type: menu::ItemType::Callback {
9+
function: date,
10+
parameters: &[menu::Parameter::Optional {
11+
parameter_name: "timestamp",
12+
help: Some("The new date/time, in ISO8601 format"),
13+
}],
14+
},
15+
command: "date",
16+
help: Some("Get/set the time and date"),
17+
};
18+
19+
/// Called when the "date" command is executed.
20+
fn date(_menu: &menu::Menu<Ctx>, item: &menu::Item<Ctx>, args: &[&str], _ctx: &mut Ctx) {
21+
if let Ok(Some(timestamp)) = menu::argument_finder(item, args, "timestamp") {
22+
println!("Setting date/time to {:?}", timestamp);
23+
static DATE_FMT: &str = "%Y-%m-%dT%H:%M:%S";
24+
let Ok(timestamp) = chrono::NaiveDateTime::parse_from_str(timestamp, DATE_FMT) else {
25+
println!("Unable to parse date/time");
26+
return;
27+
};
28+
API.set_time(timestamp);
29+
}
30+
31+
let time = API.get_time();
32+
// Ensure this matches `DATE_FMT`, for consistency
33+
println!(
34+
"The time is {:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
35+
time.year(),
36+
time.month(),
37+
time.day(),
38+
time.hour(),
39+
time.minute(),
40+
time.second(),
41+
time.nanosecond()
42+
);
43+
}

src/lib.rs

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ mod vgaconsole;
2323
/// The OS version string
2424
const OS_VERSION: &str = concat!("Neotron OS, version ", env!("OS_VERSION"));
2525

26+
/// Used to convert between POSIX epoch (for `chrono`) and Neotron epoch (for BIOS APIs).
27+
const SECONDS_BETWEEN_UNIX_AND_NEOTRON_EPOCH: i64 = 946684800;
28+
2629
/// We store the API object supplied by the BIOS here
2730
static API: Api = Api::new();
2831

@@ -72,25 +75,55 @@ macro_rules! println {
7275
// Local types
7376
// ===========================================================================
7477

78+
/// Represents the API supplied by the BIOS
79+
struct Api {
80+
bios: core::sync::atomic::AtomicPtr<bios::Api>,
81+
}
82+
7583
impl Api {
84+
/// Create a new object with a null pointer for the BIOS API.
7685
const fn new() -> Api {
7786
Api {
7887
bios: core::sync::atomic::AtomicPtr::new(core::ptr::null_mut()),
7988
}
8089
}
8190

82-
fn store(&self, api: *const bios::Api) {
91+
/// Change the stored BIOS API pointer.
92+
///
93+
/// The pointed-at object must have static lifetime.
94+
unsafe fn store(&self, api: *const bios::Api) {
8395
self.bios
8496
.store(api as *mut bios::Api, core::sync::atomic::Ordering::SeqCst)
8597
}
8698

99+
/// Get the BIOS API as a reference.
100+
///
101+
/// Will panic if the stored pointer is null.
87102
fn get(&self) -> &'static bios::Api {
88-
unsafe { &*(self.bios.load(core::sync::atomic::Ordering::SeqCst) as *const bios::Api) }
103+
let ptr = self.bios.load(core::sync::atomic::Ordering::SeqCst) as *const bios::Api;
104+
let api_ref = unsafe { ptr.as_ref() }.expect("BIOS API should be non-null");
105+
api_ref
89106
}
90-
}
91107

92-
struct Api {
93-
bios: core::sync::atomic::AtomicPtr<bios::Api>,
108+
/// Get the current time
109+
fn get_time(&self) -> chrono::NaiveDateTime {
110+
let api = self.get();
111+
let bios_time = (api.time_clock_get)();
112+
let secs = i64::from(bios_time.secs) + SECONDS_BETWEEN_UNIX_AND_NEOTRON_EPOCH;
113+
let nsecs = bios_time.nsecs;
114+
chrono::NaiveDateTime::from_timestamp_opt(secs, nsecs).unwrap()
115+
}
116+
117+
/// Set the current time
118+
fn set_time(&self, timestamp: chrono::NaiveDateTime) {
119+
let api = self.get();
120+
let nanos = timestamp.timestamp_nanos();
121+
let bios_time = bios::Time {
122+
secs: ((nanos / 1_000_000_000) - SECONDS_BETWEEN_UNIX_AND_NEOTRON_EPOCH) as u32,
123+
nsecs: (nanos % 1_000_000_000) as u32,
124+
};
125+
(api.time_clock_set)(bios_time);
126+
}
94127
}
95128

96129
/// Represents the serial port we can use as a text input/output device.

0 commit comments

Comments
 (0)