1#![no_std]
2#![deny(missing_docs)]
3
4#[derive(Debug, Copy, Clone)]
53pub struct Clock<Time>
54where
55 Time: TimeLike,
56{
57 time: Time,
58 rounding: Rounding,
59 meridiem: Option<Meridiem>,
60}
61
62impl<T> core::fmt::Display for Clock<T>
63where
64 T: TimeLike,
65{
66 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
67 let (hour, half_hour) = self.rounding.round(&self.time);
68 let clock = clock_emoji(hour, half_hour);
69
70 if let Some(Meridiem { am, pm }) = self.meridiem {
71 #[allow(clippy::zero_prefixed_literal)]
72 let meridiem = match hour {
73 00..=11 => am,
74 12..=23 => pm,
75 _ => return Err(core::fmt::Error),
76 };
77 write!(f, "{clock}{meridiem}")
78 } else {
79 write!(f, "{clock}")
80 }
81 }
82}
83
84impl<Time> Clock<Time>
85where
86 Time: TimeLike,
87{
88 #[must_use]
90 pub const fn new(time: Time) -> Self {
91 Self {
92 time,
93 rounding: Rounding::Round,
94 meridiem: None,
95 }
96 }
97 #[must_use]
99 pub fn with_rounding(self, rounding: Rounding) -> Self {
100 Self {
101 time: self.time,
102 rounding,
103 meridiem: self.meridiem,
104 }
105 }
106 #[must_use]
108 pub fn with_meridiem(self, meridiem: Meridiem) -> Self {
109 Self {
110 time: self.time,
111 rounding: self.rounding,
112 meridiem: Some(meridiem),
113 }
114 }
115}
116
117pub trait TimeLike {
119 fn hour(&self) -> u8;
121 fn minute(&self) -> u8;
123}
124
125#[derive(Debug, Copy, Clone)]
127pub struct Meridiem {
128 pub am: char,
130 pub pm: char,
132}
133
134impl Default for Meridiem {
135 fn default() -> Self {
137 Self {
138 am: '🌞', pm: '🌝'
139 }
140 }
141}
142
143#[derive(Debug, Copy, Clone)]
145#[non_exhaustive]
146pub enum Rounding {
147 Round,
151 Floor,
155 Ceil,
159}
160
161#[must_use]
167pub fn clock_emoji(hour: u8, half_hour: bool) -> char {
168 char::from_u32(128336 + (u32::from(hour) + 11) % 12 + if half_hour { 12 } else { 0 }).unwrap()
169}
170
171impl Rounding {
172 fn round<Time>(&self, time: &Time) -> (u8, bool)
174 where
175 Time: TimeLike,
176 {
177 let (hour, minute) = (time.hour(), time.minute());
178 assert!(hour <= 23, "Hour greater than 23");
179 #[allow(clippy::zero_prefixed_literal)]
180 match *self {
181 Self::Floor => match minute {
182 00..=29 => (hour, false),
183 30..=59 => (hour, true),
184 _ => panic!("Minute greater than 59"),
185 },
186 Self::Ceil => match minute {
187 0 => (hour, false),
188 01..=30 => (hour, true),
189 31..=59 => ((hour + 1) % 24, false),
190 _ => panic!("Minute greater than 59"),
191 },
192 Self::Round => match minute {
193 00..=14 => (hour, false),
194 15..=44 => (hour, true),
195 45..=59 => ((hour + 1) % 24, false),
196 _ => panic!("Minute greater than 59"),
197 },
198 }
199 }
200}
201
202#[cfg(feature = "chrono")]
203mod chrono;
204#[cfg(feature = "time")]
205mod time;
206
207#[cfg(test)]
208mod tests;
209
210impl TimeLike for (u8, u8) {
211 fn hour(&self) -> u8 {
212 self.0
213 }
214 fn minute(&self) -> u8 {
215 self.1
216 }
217}
218
219impl core::convert::From<(char, char)> for Meridiem {
220 fn from((am, pm): (char, char)) -> Self {
221 Self { am, pm }
222 }
223}
224
225impl<Time> core::ops::Deref for Clock<Time>
226where
227 Time: TimeLike,
228{
229 type Target = Time;
230 fn deref(&self) -> &Self::Target {
231 &self.time
232 }
233}
234
235impl<Time> core::ops::DerefMut for Clock<Time>
236where
237 Time: TimeLike,
238{
239 fn deref_mut(&mut self) -> &mut Self::Target {
240 &mut self.time
241 }
242}