From d2f41e04e2f65dc7e71bf7811805ba8f3a9424bf Mon Sep 17 00:00:00 2001 From: Jens Krause <47693+sectore@users.noreply.github.com> Date: Mon, 13 Oct 2025 14:09:16 +0200 Subject: [PATCH] inrease `MAX_DURATION` to `9999y 364d 23:59:59.9` (#128) --- CHANGELOG.md | 1 + src/duration.rs | 10 +- src/widgets/clock.rs | 169 +++++++++++++++++++++++++++++++++- src/widgets/clock_elements.rs | 4 +- src/widgets/clock_test.rs | 18 ++++ 5 files changed, 193 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3470b00..cf3c13f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - (event) New `event` screen to count custom date times in the future or past. [#117](https://github.com/sectore/timr-tui/pull/117), [#120](https://github.com/sectore/timr-tui/pull/120), [#122](https://github.com/sectore/timr-tui/pull/122), [#123](https://github.com/sectore/timr-tui/pull/123), [#124](https://github.com/sectore/timr-tui/pull/124), [#125](https://github.com/sectore/timr-tui/pull/125) - (screens) switch by `←` or `→` keys [#127](https://github.com/sectore/timr-tui/pull/127) +- (duration) inrease `MAX_DURATION` to `9999y 364d 23:59:59.9` [#128](https://github.com/sectore/timr-tui/pull/128) ### Breaking change diff --git a/src/duration.rs b/src/duration.rs index 299b594..94d2693 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -31,9 +31,9 @@ pub const ONE_YEAR: Duration = // ^ https://www.math.net/days-in-a-year const DAYS_PER_YEAR: u64 = 365; // ignore leap year of 366 days -// max. 999y 364d 23:59:59.9 (1000 years - 1 decisecond) +// max. 9999y 364d 23:59:59.9 (10k years - 1 decisecond) pub const MAX_DURATION: Duration = ONE_YEAR - .saturating_mul(1000) + .saturating_mul(10000) .saturating_sub(ONE_DECI_SECOND); /// Trait for duration types that can be displayed in clock widgets. @@ -678,11 +678,11 @@ mod tests { ); // MAX_DURATION clamping - assert_eq!(parse_long_duration("1000y").unwrap(), MAX_DURATION); + assert_eq!(parse_long_duration("10000y").unwrap(), MAX_DURATION); assert_eq!( - parse_long_duration("999y 364d 23:59:59").unwrap(), + parse_long_duration("9999y 364d 23:59:59").unwrap(), Duration::from_secs( - 999 * YEAR_IN_SECONDS + 9999 * YEAR_IN_SECONDS + 364 * DAY_IN_SECONDS + 23 * HOUR_IN_SECONDS + 59 * MINUTE_IN_SECONDS diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 7bd9229..9cd57b2 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -11,6 +11,7 @@ use ratatui::{ widgets::{StatefulWidget, Widget}, }; +use crate::widgets::clock_elements::FOUR_DIGITS_WIDTH; use crate::{ common::{ClockTypeId, Style as DigitStyle}, duration::{ @@ -88,10 +89,19 @@ pub enum Format { YyyDHhMmSs, YyyDdHhMmSs, YyyDddHhMmSs, + YyyyDHhMmSs, + YyyyDdHhMmSs, + YyyyDddHhMmSs, } pub fn format_by_duration(d: &D) -> Format { - if d.years() >= 100 && d.days_mod() >= 100 { + if d.years() >= 1000 && d.days_mod() >= 100 { + Format::YyyyDddHhMmSs + } else if d.years() >= 1000 && d.days_mod() >= 10 { + Format::YyyyDdHhMmSs + } else if d.years() >= 1000 && d.days() >= 1 { + Format::YyyyDHhMmSs + } else if d.years() >= 100 && d.days_mod() >= 100 { Format::YyyDddHhMmSs } else if d.years() >= 100 && d.days_mod() >= 10 { Format::YyyDdHhMmSs @@ -140,7 +150,10 @@ pub fn time_by_format(format: &Format) -> Time { | Format::YyDHhMmSs | Format::YyyDddHhMmSs | Format::YyyDdHhMmSs - | Format::YyyDHhMmSs => Time::Years, + | Format::YyyDHhMmSs + | Format::YyyyDddHhMmSs + | Format::YyyyDdHhMmSs + | Format::YyyyDHhMmSs => Time::Years, Format::DddHhMmSs | Format::DdHhMmSs | Format::DHhMmSs => Time::Days, Format::HhMmSs | Format::HMmSs => Time::Hours, Format::MmSs | Format::MSs => Time::Minutes, @@ -677,6 +690,48 @@ pub fn clock_horizontal_lengths(format: &Format, with_decis: bool) -> Vec { const LABEL_WIDTH: u16 = DIGIT_LABEL_WIDTH + DIGIT_SPACE_WIDTH; match format { + Format::YyyyDddHhMmSs => add_decis( + vec![ + FOUR_DIGITS_WIDTH, // y_y_y_y + LABEL_WIDTH, // _l__ + THREE_DIGITS_WIDTH, // d_d_d + LABEL_WIDTH, // _l__ + TWO_DIGITS_WIDTH, // h_h + COLON_WIDTH, // : + TWO_DIGITS_WIDTH, // m_m + COLON_WIDTH, // : + TWO_DIGITS_WIDTH, // s_s + ], + with_decis, + ), + Format::YyyyDdHhMmSs => add_decis( + vec![ + FOUR_DIGITS_WIDTH, // y_y_y_y + LABEL_WIDTH, // _l__ + TWO_DIGITS_WIDTH, // d_d + LABEL_WIDTH, // _l__ + TWO_DIGITS_WIDTH, // h_h + COLON_WIDTH, // : + TWO_DIGITS_WIDTH, // m_m + COLON_WIDTH, // : + TWO_DIGITS_WIDTH, // s_s + ], + with_decis, + ), + Format::YyyyDHhMmSs => add_decis( + vec![ + FOUR_DIGITS_WIDTH, // y_y_y_y + LABEL_WIDTH, // _l__ + DIGIT_WIDTH, // d + LABEL_WIDTH, // _l__ + TWO_DIGITS_WIDTH, // h_h + COLON_WIDTH, // : + TWO_DIGITS_WIDTH, // m_m + COLON_WIDTH, // : + TWO_DIGITS_WIDTH, // s_s + ], + with_decis, + ), Format::YyyDddHhMmSs => add_decis( vec![ THREE_DIGITS_WIDTH, // y_y_y @@ -922,6 +977,20 @@ pub fn render_clock(area: Rect, buf: &mut Buffer, state: Rende let edit_secs = matches!(editable_time, Some(Time::Seconds)); let edit_decis = matches!(editable_time, Some(Time::Decis)); + let render_four_digits = |d1, d2, d3, d4, editable, area, buf: &mut Buffer| { + let [a1, a2, a3, a4] = Layout::horizontal(Constraint::from_lengths([ + DIGIT_WIDTH + DIGIT_SPACE_WIDTH, + DIGIT_WIDTH + DIGIT_SPACE_WIDTH, + DIGIT_WIDTH + DIGIT_SPACE_WIDTH, + DIGIT_WIDTH, + ])) + .areas(area); + Digit::new(d1, editable, symbol).render(a1, buf); + Digit::new(d2, editable, symbol).render(a2, buf); + Digit::new(d3, editable, symbol).render(a3, buf); + Digit::new(d4, editable, symbol).render(a4, buf); + }; + let render_three_digits = |d1, d2, d3, editable, area, buf: &mut Buffer| { let [a1, a2, a3] = Layout::horizontal(Constraint::from_lengths([ DIGIT_WIDTH + DIGIT_SPACE_WIDTH, @@ -952,6 +1021,18 @@ pub fn render_clock(area: Rect, buf: &mut Buffer, state: Rende Dot::new(symbol).render(area, buf); }; + let render_yyyy = |area, buf| { + render_four_digits( + (duration.years() / 1000) % 10, + (duration.years() / 100) % 10, + (duration.years() / 10) % 10, + duration.years() % 10, + edit_years, + area, + buf, + ); + }; + let render_yyy = |area, buf| { render_three_digits( (duration.years() / 100) % 10, @@ -1065,6 +1146,90 @@ pub fn render_clock(area: Rect, buf: &mut Buffer, state: Rende }; match format { + Format::YyyyDddHhMmSs if with_decis => { + let [y_y_y_y, ly, d_d_d, ld, h_h, c_hm, m_m, c_ms, s_s, dot, ds] = + Layout::horizontal(Constraint::from_lengths(widths)).areas(area); + render_yyyy(y_y_y_y, buf); + render_label_y(ly, buf); + render_ddd(d_d_d, buf); + render_label_d(ld, buf); + render_hh(h_h, buf); + render_colon(c_hm, buf); + render_mm(m_m, buf); + render_colon(c_ms, buf); + render_ss(s_s, buf); + render_dot(dot, buf); + render_ds(ds, buf); + } + Format::YyyyDddHhMmSs => { + let [y_y_y_y, ly, d_d_d, ld, h_h, c_hm, m_m, c_ms, s_s] = + Layout::horizontal(Constraint::from_lengths(widths)).areas(area); + render_yyyy(y_y_y_y, buf); + render_label_y(ly, buf); + render_ddd(d_d_d, buf); + render_label_d(ld, buf); + render_hh(h_h, buf); + render_colon(c_hm, buf); + render_mm(m_m, buf); + render_colon(c_ms, buf); + render_ss(s_s, buf); + } + Format::YyyyDdHhMmSs if with_decis => { + let [y_y_y_y, ly, d_d, ld, h_h, c_hm, m_m, c_ms, s_s, dot, ds] = + Layout::horizontal(Constraint::from_lengths(widths)).areas(area); + render_yyyy(y_y_y_y, buf); + render_label_y(ly, buf); + render_dd(d_d, buf); + render_label_d(ld, buf); + render_hh(h_h, buf); + render_colon(c_hm, buf); + render_mm(m_m, buf); + render_colon(c_ms, buf); + render_ss(s_s, buf); + render_dot(dot, buf); + render_ds(ds, buf); + } + Format::YyyyDdHhMmSs => { + let [y_y_y_y, ly, d_d, ld, h_h, c_hm, m_m, c_ms, s_s] = + Layout::horizontal(Constraint::from_lengths(widths)).areas(area); + render_yyyy(y_y_y_y, buf); + render_label_y(ly, buf); + render_dd(d_d, buf); + render_label_d(ld, buf); + render_hh(h_h, buf); + render_colon(c_hm, buf); + render_mm(m_m, buf); + render_colon(c_ms, buf); + render_ss(s_s, buf); + } + Format::YyyyDHhMmSs if with_decis => { + let [y_y_y_y, ly, d, ld, h_h, c_hm, m_m, c_ms, s_s, dot, ds] = + Layout::horizontal(Constraint::from_lengths(widths)).areas(area); + render_yyyy(y_y_y_y, buf); + render_label_y(ly, buf); + render_d(d, buf); + render_label_d(ld, buf); + render_hh(h_h, buf); + render_colon(c_hm, buf); + render_mm(m_m, buf); + render_colon(c_ms, buf); + render_ss(s_s, buf); + render_dot(dot, buf); + render_ds(ds, buf); + } + Format::YyyyDHhMmSs => { + let [y_y_y_y, ly, d, ld, h_h, c_hm, m_m, c_ms, s_s] = + Layout::horizontal(Constraint::from_lengths(widths)).areas(area); + render_yyyy(y_y_y_y, buf); + render_label_y(ly, buf); + render_d(d, buf); + render_label_d(ld, buf); + render_hh(h_h, buf); + render_colon(c_hm, buf); + render_mm(m_m, buf); + render_colon(c_ms, buf); + render_ss(s_s, buf); + } Format::YyyDddHhMmSs if with_decis => { let [y_y_y, ly, d_d_d, ld, h_h, c_hm, m_m, c_ms, s_s, dot, ds] = Layout::horizontal(Constraint::from_lengths(widths)).areas(area); diff --git a/src/widgets/clock_elements.rs b/src/widgets/clock_elements.rs index 95b0490..1ce52fd 100644 --- a/src/widgets/clock_elements.rs +++ b/src/widgets/clock_elements.rs @@ -8,8 +8,8 @@ pub const DIGIT_SIZE: usize = 5; pub const DIGIT_WIDTH: u16 = DIGIT_SIZE as u16; pub const DIGIT_HEIGHT: u16 = DIGIT_SIZE as u16 + 1 /* border height */; pub const TWO_DIGITS_WIDTH: u16 = DIGIT_WIDTH + DIGIT_SPACE_WIDTH + DIGIT_WIDTH; // digit-space-digit -pub const THREE_DIGITS_WIDTH: u16 = - DIGIT_WIDTH + DIGIT_SPACE_WIDTH + DIGIT_WIDTH + DIGIT_SPACE_WIDTH + DIGIT_WIDTH; // digit-space-digit-space-digit +pub const THREE_DIGITS_WIDTH: u16 = TWO_DIGITS_WIDTH + DIGIT_SPACE_WIDTH + DIGIT_WIDTH; // digit-space-digit-space-digit +pub const FOUR_DIGITS_WIDTH: u16 = THREE_DIGITS_WIDTH + DIGIT_SPACE_WIDTH + DIGIT_WIDTH; // digit-space-digit-space-digit-space-digit pub const COLON_WIDTH: u16 = 4; // incl. padding left + padding right pub const DOT_WIDTH: u16 = 4; // incl. padding left + padding right pub const DIGIT_SPACE_WIDTH: u16 = 1; // space between digits diff --git a/src/widgets/clock_test.rs b/src/widgets/clock_test.rs index 85aab98..a6b56aa 100644 --- a/src/widgets/clock_test.rs +++ b/src/widgets/clock_test.rs @@ -202,6 +202,24 @@ fn test_format_by_duration_boundaries() { format_by_duration::(&(100 * ONE_YEAR + 100 * ONE_DAY).into()), Format::YyyDddHhMmSs ); + + // YyyDdHhMmSs + assert_eq!( + format_by_duration::(&(1000 * ONE_YEAR + 10 * ONE_DAY).into()), + Format::YyyyDdHhMmSs + ); + // YyyyDdHhMmSs + assert_eq!( + format_by_duration::( + &(1000 * ONE_YEAR + (100 * ONE_DAY).saturating_sub(ONE_SECOND)).into() + ), + Format::YyyyDdHhMmSs + ); + // YyyyDddHhMmSs + assert_eq!( + format_by_duration::(&(1000 * ONE_YEAR + 100 * ONE_DAY).into()), + Format::YyyyDddHhMmSs + ); } #[test]