Skip to main content

timelog/
task.rs

1//! Represention of a task event.
2//!
3//! # Examples
4//!
5//! ```rust
6//! use std::time::Duration;
7//! use timelog::{DateTime, TaskEvent};
8//!
9//! # fn main() {
10//! let timestamp = DateTime::new((2022, 03, 14), (10, 0, 0)).expect("Invalid date or time");
11//! let mut event = TaskEvent::new(
12//!     timestamp,
13//!     "project_1",
14//!     Duration::from_secs(300)
15//! );
16//! println!("{}", event.project());
17//! event.add_dur(Duration::from_secs(3000));
18//! println!("{:?}", event.duration());
19//! # }
20//! ```
21//!
22//! # Description
23//!
24//! The [`TaskEvent`] type represents a single task event containing a
25//! start time and duration.
26
27use std::time::Duration;
28
29#[doc(inline)]
30use crate::date::DateTime;
31#[doc(inline)]
32use crate::entry::Entry;
33
34/// Structure representing a single task event.
35#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq)]
36pub struct TaskEvent {
37    /// Timestamp of the start of this event
38    start: DateTime,
39    /// Optional [`String`] representing the project
40    proj:  Option<String>,
41    /// Calculated total [`Duration`] of the event
42    dur:   Duration
43}
44
45impl TaskEvent {
46    /// Create a [`TaskEvent`] with the supplied parameters.
47    pub fn new<'a, OS>(start: DateTime, proj: OS, dur: Duration) -> Self
48    where
49        OS: Into<Option<&'a str>>
50    {
51        Self { start, proj: proj.into().map(ToString::to_string), dur }
52    }
53
54    /// Create a [`TaskEvent`] from the supplied entry
55    pub fn from_entry(entry: &Entry) -> Self {
56        Self::new(entry.date_time(), entry.project(), Duration::default())
57    }
58
59    /// Return a tuple with two [`TaskEvent`]s, split at the supplied [`Duration`].
60    /// The first starts at the same starting point as this [`TaskEvent`] and
61    /// stopping at the supplied [`Duration`]. The second starts [`Duration`] after
62    /// the start, containg the rest of the time.
63    #[rustfmt::skip]
64    pub fn split(&self, dur: Duration) -> Option<(Self, Self)> {
65        Some((
66            Self {
67                start: self.start,
68                proj:  self.proj(),
69                dur
70            },
71            Self {
72                start: (self.start + dur).ok()?,
73                proj:  self.proj(),
74                dur:   self.dur - dur
75            }
76        ))
77    }
78
79    /// Return the project as a String if one exists.
80    pub fn proj(&self) -> Option<String> { self.proj.as_ref().cloned() }
81
82    /// Return the project as a String defaulting to an empty string.
83    pub fn project(&self) -> String { self.proj().unwrap_or_default() }
84
85    /// Return the hour of the start time.
86    pub fn hour(&self) -> usize { self.start.hour() as usize }
87
88    /// Return the starting [`DateTime`].
89    pub fn start(&self) -> &DateTime { &self.start }
90
91    /// Extend the [`TaskEvent`]'s duration by the supplied amount.
92    pub fn add_dur(&mut self, dur: Duration) { self.dur += dur; }
93
94    /// Return the [`Duration`] of this [`TaskEvent`].
95    pub fn duration(&self) -> Duration { self.dur }
96
97    /// Return the number of seconds this [`TaskEvent`] has lasted.
98    pub fn as_secs(&self) -> u64 { self.dur.as_secs() }
99
100    /// Return the number of seconds after the hour of the starting time.
101    pub fn second_offset(&self) -> u32 { self.start.second_offset() }
102}
103
104#[cfg(test)]
105mod tests {
106    use assert2::{assert, let_assert};
107
108    use super::*;
109    use crate::DateTime;
110
111    #[test]
112    fn test_new_with_project() {
113        let_assert!(Ok(datetime) = DateTime::new((2022, 3, 20), (12, 34, 56)));
114        let task = TaskEvent::new(
115            datetime,
116            "proja",
117            Duration::from_secs(305)
118        );
119        let_assert!(Some(proj) = task.proj());
120        assert!(proj == String::from("proja"));
121        assert!(task.project() == String::from("proja"));
122        let_assert!(Ok(expect_start) = DateTime::new((2022, 3, 20), (12, 34, 56)));
123        assert!(task.start() == &expect_start);
124        assert!(task.hour() == 12);
125        assert!(task.duration() == Duration::from_secs(305));
126    }
127
128    #[test]
129    fn test_new_without_project() {
130        let_assert!(Ok(datetime) = DateTime::new((2022, 3, 20), (13, 24, 56)));
131        let task = TaskEvent::new(
132            datetime,
133            None,
134            Duration::from_secs(255)
135        );
136        assert!(task.proj() == None);
137        assert!(task.project() == String::new());
138        let_assert!(Ok(expect_start) = DateTime::new((2022, 3, 20), (13, 24, 56)));
139        assert!(task.start() == &expect_start);
140        assert!(task.hour() == 13);
141        assert!(task.duration() == Duration::from_secs(255));
142    }
143
144    #[test]
145    fn test_add_dur() {
146        let_assert!(Ok(datetime) = DateTime::new((2022, 3, 20), (12, 34, 56)));
147        let mut task = TaskEvent::new(
148            datetime,
149            "proja",
150            Duration::from_secs(305)
151        );
152
153        assert!(task.as_secs() == 305, "Initial value");
154
155        task.add_dur(Duration::from_secs(3600));
156        assert!(task.as_secs() == 3905, "After add_dur");
157        assert!(task.duration() == Duration::from_secs(3905));
158    }
159
160    #[test]
161    fn test_second_offset() {
162        let_assert!(Ok(datetime) = DateTime::new((2022, 3, 20), (12, 34, 56)));
163        let task = TaskEvent::new(
164            datetime,
165            "proja",
166            Duration::from_secs(305)
167        );
168
169        assert!(task.second_offset() == 34 * 60 + 56);
170    }
171
172    #[test]
173    fn test_split() {
174        let_assert!(Ok(datetime) = DateTime::new((2022, 3, 20), (12, 34, 56)));
175        let task = TaskEvent::new(
176            datetime,
177            "proja",
178            Duration::from_secs(600)
179        );
180
181        let_assert!(Some((first, second)) = task.split(Duration::from_secs(250)));
182
183        let_assert!(Ok(expected) = DateTime::new((2022, 3, 20), (12, 34, 56)));
184        assert!(first.start() == &expected);
185        assert!(first.duration() == Duration::from_secs(250));
186
187        let_assert!(Ok(expected) = DateTime::new((2022, 3, 20), (12, 39, 6)));
188        assert!(second.start() == &expected);
189        assert!(second.duration() == Duration::from_secs(350));
190    }
191}