mod common;
use std::error::Error;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use escpos_vfd::{VfdWorker, fit_to_width};
fn run_date(args: &[&str]) -> Option<String> {
let out = std::process::Command::new("date")
.args(args)
.output()
.ok()?;
if !out.status.success() {
return None;
}
Some(String::from_utf8_lossy(&out.stdout).trim().to_string())
}
fn parse_datetime_and_weekday(value: &str) -> Option<(String, u8)> {
let (datetime, weekday) = value.split_once('|')?;
let weekday = weekday.parse::<u8>().ok()?;
(1..=7)
.contains(&weekday)
.then(|| (datetime.to_string(), weekday))
}
fn local_datetime_and_weekday() -> (String, u8) {
run_date(&["+%d.%m.%Y %H:%M:%S|%u"])
.as_deref()
.and_then(parse_datetime_and_weekday)
.unwrap_or_else(|| ("??.??.???? ??:??:??".to_string(), 1))
}
fn weekday_ru_full(n: u8) -> &'static str {
match n {
1 => "Понедельник",
2 => "Вторник",
3 => "Среда",
4 => "Четверг",
5 => "Пятница",
6 => "Суббота",
7 => "Воскресенье",
_ => "Понедельник",
}
}
fn weekday_ru_short(n: u8) -> &'static str {
match n {
1 => "Пн",
2 => "Вт",
3 => "Ср",
4 => "Чт",
5 => "Пт",
6 => "Сб",
7 => "Вс",
_ => "Пн",
}
}
fn time_of_day_ru(hour: u8) -> &'static str {
match hour {
5..=10 => "утро",
11..=16 => "день",
17..=22 => "вечер",
_ => "ночь",
}
}
fn extract_hour(datetime: &str) -> u8 {
datetime
.split_whitespace()
.nth(1)
.and_then(|t| t.get(0..2))
.and_then(|hh| hh.parse::<u8>().ok())
.unwrap_or(12)
}
fn main() -> Result<(), Box<dyn Error>> {
let mut args = common::ExampleArgs::from_env()?;
let brightness: u8 = args.parse_or(4);
let columns = args.columns;
let worker = VfdWorker::start(args.config)?;
let vfd = worker.handle();
vfd.clear()?;
vfd.set_brightness(brightness)?;
loop {
let (dt, wd) = local_datetime_and_weekday();
let hour = extract_hour(&dt);
let tod = time_of_day_ru(hour);
let line1 = fit_to_width(&dt, columns);
let full = format!("{} сейчас {}", weekday_ru_full(wd), tod);
let mut line2 = full;
if line2.chars().count() > columns {
line2 = format!("{} сейчас {}", weekday_ru_short(wd), tod);
}
line2 = fit_to_width(&line2, columns);
vfd.print_line_diff(1, line1)?;
vfd.print_line_diff(2, line2)?;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis();
let ms_to_next = 1000 - (now % 1000) as u64;
std::thread::sleep(Duration::from_millis(ms_to_next));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_date_and_weekday_from_one_process_result() {
assert_eq!(
parse_datetime_and_weekday("15.08.2026 23:59:58|6"),
Some(("15.08.2026 23:59:58".to_string(), 6))
);
assert_eq!(parse_datetime_and_weekday("invalid"), None);
assert_eq!(parse_datetime_and_weekday("15.08.2026 23:59:58|8"), None);
}
#[test]
fn classifies_hours_and_extracts_them_safely() {
assert_eq!(extract_hour("15.08.2026 05:00:00"), 5);
assert_eq!(extract_hour("invalid"), 12);
assert_eq!(time_of_day_ru(5), "утро");
assert_eq!(time_of_day_ru(23), "ночь");
}
}