opening_hours_syntax/extended_time.rs
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 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
use std::convert::TryInto;
use std::fmt::{Debug, Display};
use chrono::{NaiveTime, Timelike};
/// An hour+minute struct that can go up to 48h.
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ExtendedTime {
hour: u8,
minute: u8,
}
impl ExtendedTime {
/// Create a new extended time, this may return `None` if input values are
/// out of range.
///
/// ```
/// use opening_hours_syntax::ExtendedTime;
///
/// assert!(ExtendedTime::new(28, 30).is_some());
/// assert!(ExtendedTime::new(72, 15).is_none()); // hours are out of bound
/// assert!(ExtendedTime::new(24, 60).is_none()); // minutes are out of bound
/// ```
#[inline]
pub const fn new(hour: u8, minute: u8) -> Option<Self> {
if hour > 48 || minute > 59 || (hour == 48 && minute > 0) {
None
} else {
Some(Self { hour, minute })
}
}
/// Get the number of full hours in this extended time.
///
/// ```
/// use opening_hours_syntax::ExtendedTime;
///
/// let time = ExtendedTime::new(27, 35).unwrap();
/// assert_eq!(time.hour(), 27);
/// ```
#[inline]
pub fn hour(self) -> u8 {
self.hour
}
/// Get the number of remaining minutes in this extended time.
///
/// ```
/// use opening_hours_syntax::ExtendedTime;
///
/// let time = ExtendedTime::new(27, 35).unwrap();
/// assert_eq!(time.minute(), 35);
/// ```
#[inline]
pub fn minute(self) -> u8 {
self.minute
}
/// Add plain minutes to the extended time and return `None` if this
/// results in it being out of bounds.
///
/// ```
/// use opening_hours_syntax::ExtendedTime;
///
/// let time = ExtendedTime::new(24, 0).unwrap();
/// assert_eq!(time.add_minutes(75), ExtendedTime::new(25, 15));
/// assert!(time.add_minutes(24 * 60 + 1).is_none());
/// assert!(time.add_minutes(-24 * 60 - 1).is_none());
/// ```
#[inline]
pub fn add_minutes(self, minutes: i16) -> Option<Self> {
let as_minutes = (self.mins_from_midnight() as i16).checked_add(minutes)?;
Self::from_mins_from_midnight(as_minutes.try_into().ok()?)
}
/// Add plain hours to the extended time and return `None` if this results
/// in it being out of bounds.
///
/// ```
/// use opening_hours_syntax::ExtendedTime;
///
/// let time = ExtendedTime::new(24, 15).unwrap();
/// assert_eq!(time.add_hours(3), ExtendedTime::new(27, 15));
/// assert!(time.add_hours(25).is_none());
/// assert!(time.add_hours(-25).is_none());
/// ```
#[inline]
pub fn add_hours(self, hours: i8) -> Option<Self> {
Self::new(
(i16::from(self.hour) + i16::from(hours)).try_into().ok()?,
self.minute,
)
}
/// Get the total number of minutes from *00:00*.
///
/// ```
/// use opening_hours_syntax::ExtendedTime;
///
/// let time = ExtendedTime::new(25, 15).unwrap();
/// assert_eq!(time.mins_from_midnight(), 25 * 60 + 15);
/// ```
#[inline]
pub fn mins_from_midnight(self) -> u16 {
u16::from(self.minute) + 60 * u16::from(self.hour)
}
/// Build an extended time from the total number of minutes from midnight
/// and return `None` if the result is out of bounds.
///
/// ```
/// use opening_hours_syntax::ExtendedTime;
///
/// assert_eq!(
/// ExtendedTime::from_mins_from_midnight(26 * 60 + 15),
/// ExtendedTime::new(26, 15),
/// );
///
/// assert!(ExtendedTime::from_mins_from_midnight(65_000).is_none());
/// ```
#[inline]
pub fn from_mins_from_midnight(minute: u16) -> Option<Self> {
let hour = (minute / 60).try_into().ok()?;
let minute = (minute % 60).try_into().ok()?;
Self::new(hour, minute)
}
}
impl Display for ExtendedTime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:02}:{:02}", self.hour, self.minute)
}
}
impl Debug for ExtendedTime {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
write!(f, "{self}")
}
}
impl TryInto<NaiveTime> for ExtendedTime {
type Error = ();
#[inline]
fn try_into(self) -> Result<NaiveTime, Self::Error> {
NaiveTime::from_hms_opt(self.hour.into(), self.minute.into(), 0).ok_or(())
}
}
impl From<NaiveTime> for ExtendedTime {
#[inline]
fn from(time: NaiveTime) -> ExtendedTime {
Self {
hour: time.hour().try_into().expect("invalid NaiveTime"),
minute: time.minute().try_into().expect("invalid NaiveTime"),
}
}
}