about summary refs log tree commit diff stats
path: root/src/select/selection_file/duration.rs
blob: a38981cf67fdbf0f0ceff673cd389e2119905cad (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// yt - A fully featured command line YouTube client
//
// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This file is part of Yt.
//
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.

use std::str::FromStr;

use anyhow::{Context, Result};

#[derive(Copy, Clone, Debug)]
pub struct Duration {
    time: u32,
}

impl FromStr for Duration {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        fn parse_num(str: &str, suffix: char) -> Result<u32> {
            str.strip_suffix(suffix)
                .with_context(|| {
                    format!("Failed to strip suffix '{}' of number: '{}'", suffix, str)
                })?
                .parse::<u32>()
                .with_context(|| format!("Failed to parse '{}'", suffix))
        }

        if s == "[No Duration]" {
            return Ok(Self { time: 0 });
        }

        let buf: Vec<_> = s.split(' ').collect();

        let hours;
        let minutes;
        let seconds;

        assert_eq!(buf.len(), 2, "Other lengths should not happen");

        if buf[0].ends_with('h') {
            hours = parse_num(buf[0], 'h')?;
            minutes = parse_num(buf[1], 'm')?;
            seconds = 0;
        } else if buf[0].ends_with('m') {
            hours = 0;
            minutes = parse_num(buf[0], 'm')?;
            seconds = parse_num(buf[1], 's')?;
        } else {
            unreachable!(
                "The first part always ends with 'h' or 'm', but was: {:#?}",
                buf
            )
        }

        Ok(Self {
            time: (hours * 60 * 60) + (minutes * 60) + seconds,
        })
    }
}

impl From<Option<f64>> for Duration {
    fn from(value: Option<f64>) -> Self {
        Self {
            time: value.unwrap_or(0.0).ceil() as u32,
        }
    }
}

impl std::fmt::Display for Duration {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        const SECOND: u32 = 1;
        const MINUTE: u32 = 60 * SECOND;
        const HOUR: u32 = 60 * MINUTE;

        let base_hour = self.time - (self.time % HOUR);
        let base_min = (self.time % HOUR) - ((self.time % HOUR) % MINUTE);
        let base_sec = (self.time % HOUR) % MINUTE;

        let h = base_hour / HOUR;
        let m = base_min / MINUTE;
        let s = base_sec / SECOND;

        if self.time == 0 {
            write!(f, "[No Duration]")
        } else if h > 0 {
            write!(f, "{h}h {m}m")
        } else {
            write!(f, "{m}m {s}s")
        }
    }
}
#[cfg(test)]
mod test {
    use super::Duration;

    #[test]
    fn test_display_duration_1h() {
        let dur = Duration { time: 60 * 60 };
        assert_eq!("1h 0m".to_owned(), dur.to_string());
    }
    #[test]
    fn test_display_duration_30min() {
        let dur = Duration { time: 60 * 30 };
        assert_eq!("30m 0s".to_owned(), dur.to_string());
    }
}