1#![allow(clippy::redundant_pub_crate, reason = "re-exported as pub lol")]
4
5use chrono::{Datelike, Local, NaiveDate, NaiveDateTime, NaiveTime, DateTime, Utc, TimeDelta, Timelike};
6use derive_more::{Display, FromStr};
7use serde::de::Error;
8use serde::{Deserialize, Deserializer};
9use std::fmt::{Debug, Display, Formatter};
10use std::num::{ParseFloatError, ParseIntError};
11use std::ops::{Add, RangeInclusive};
12use std::str::FromStr;
13use std::ops::Not;
14use thiserror::Error;
15use crate::season::SeasonId;
16
17#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
19#[serde(from = "__CopyrightStruct")]
20pub enum Copyright {
21 Typical {
23 year: u32,
25 },
26 UnknownSpec(Box<str>),
28}
29
30#[derive(Deserialize)]
31#[doc(hidden)]
32struct __CopyrightStruct(String);
33
34impl From<__CopyrightStruct> for Copyright {
35 fn from(value: __CopyrightStruct) -> Self {
36 let __CopyrightStruct(value) = value;
37 if let Some(value) = value.strip_prefix("Copyright ") && let Some(value) = value.strip_suffix(" MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt") && let Ok(year) = value.parse::<u32>() {
38 Self::Typical { year }
39 } else {
40 Self::UnknownSpec(value.into_boxed_str())
41 }
42 }
43}
44
45impl Display for Copyright {
46 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
47 match self {
48 Self::Typical { year } => write!(f, "Copyright {year} MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt"),
49 Self::UnknownSpec(copyright) => write!(f, "{copyright}"),
50 }
51 }
52}
53
54impl Default for Copyright {
55 #[allow(clippy::cast_sign_loss, reason = "jesus is not alive")]
56 fn default() -> Self {
57 Self::Typical { year: Local::now().year() as _ }
58 }
59}
60
61pub fn try_from_str<'de, D: Deserializer<'de>, T: FromStr>(deserializer: D) -> Result<Option<T>, D::Error> {
65 Ok(String::deserialize(deserializer)?.parse::<T>().ok())
66}
67
68pub fn from_str<'de, D: Deserializer<'de>, T: FromStr>(deserializer: D) -> Result<T, D::Error>
74where
75 <T as FromStr>::Err: Debug,
76{
77 String::deserialize(deserializer)?.parse::<T>().map_err(|e| Error::custom(format!("{e:?}")))
78}
79
80pub fn from_yes_no<'de, D: Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> {
85 #[derive(Deserialize)]
86 #[repr(u8)]
87 enum Boolean {
88 #[serde(rename = "Y")]
89 Yes = 1,
90 #[serde(rename = "N")]
91 No = 0,
92 }
93
94 Ok(match Boolean::deserialize(deserializer)? {
95 Boolean::Yes => true,
96 Boolean::No => false,
97 })
98}
99
100#[derive(Debug, PartialEq, Eq, Copy, Clone)]
104pub enum HeightMeasurement {
105 FeetAndInches { feet: u8, inches: u8 },
107 Centimeters { cm: u16 },
109}
110
111impl FromStr for HeightMeasurement {
112 type Err = HeightMeasurementParseError;
113
114 fn from_str(s: &str) -> Result<Self, Self::Err> {
118 if let Some((feet, Some((inches, "")))) = s.split_once("' ").map(|(feet, rest)| (feet, rest.split_once('"'))) {
119 let feet = feet.parse::<u8>()?;
120 let inches = inches.parse::<u8>()?;
121 Ok(Self::FeetAndInches { feet, inches })
122 } else if let Some((cm, "")) = s.split_once("cm") {
123 let cm = cm.parse::<u16>()?;
124 Ok(Self::Centimeters { cm })
125 } else {
126 Err(HeightMeasurementParseError::UnknownSpec(s.to_owned()))
127 }
128 }
129}
130
131impl<'de> Deserialize<'de> for HeightMeasurement {
132 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
133 where
134 D: Deserializer<'de>
135 {
136 String::deserialize(deserializer)?.parse().map_err(D::Error::custom)
137 }
138}
139
140#[derive(Debug, Error)]
142pub enum HeightMeasurementParseError {
143 #[error(transparent)]
145 ParseIntError(#[from] ParseIntError),
146 #[error("Unknown height '{0}'")]
148 UnknownSpec(String),
149}
150
151#[derive(Debug, Display, PartialEq, Eq, Copy, Clone, Default)]
153pub enum PlayerPool {
154 #[default]
156 #[display("ALL")]
157 All,
158 #[display("QUALIFIED")]
160 Qualified,
161 #[display("ROOKIES")]
163 Rookies,
164 #[display("QUALIFIED_ROOKIES")]
166 QualifiedAndRookies,
167 #[display("ORGANIZATION")]
169 Organization,
170 #[display("ORGANIZATION_NO_MLB")]
172 OrganizationNotMlb,
173 #[display("CURRENT")]
175 Current,
176 #[display("ALL_CURRENT")]
178 AllCurrent,
179 #[display("QUALIFIED_CURRENT")]
181 QualifiedAndCurrent,
182}
183
184#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone, Default)]
188pub enum Gender {
189 #[serde(rename = "M")]
190 Male,
191 #[serde(rename = "F")]
192 Female,
193 #[default]
194 #[serde(other)]
195 Other,
196}
197
198#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone, Display)]
202#[serde(try_from = "__HandednessStruct")]
203#[display("{}", self.into_char())]
204pub enum Handedness {
205 Left,
206 Right,
207 Switch,
208}
209
210impl Handedness {
211 #[must_use]
212 pub const fn into_char(self) -> char {
213 match self {
214 Self::Left => 'L',
215 Self::Right => 'R',
216 Self::Switch => 'S',
217 }
218 }
219}
220
221#[derive(Deserialize)]
222#[doc(hidden)]
223struct __HandednessStruct {
224 code: String,
225}
226
227#[derive(Debug, Error)]
229pub enum HandednessParseError {
230 #[error("Invalid handedness '{0}'")]
232 InvalidHandedness(String),
233}
234
235impl TryFrom<__HandednessStruct> for Handedness {
236 type Error = HandednessParseError;
237
238 fn try_from(value: __HandednessStruct) -> Result<Self, Self::Error> {
239 Ok(match &*value.code {
240 "L" => Self::Left,
241 "R" => Self::Right,
242 "S" => Self::Switch,
243 _ => return Err(HandednessParseError::InvalidHandedness(value.code)),
244 })
245 }
246}
247
248pub type NaiveDateRange = RangeInclusive<NaiveDate>;
255
256pub(crate) const MLB_API_DATE_FORMAT: &str = "%m/%d/%Y";
257
258pub(crate) fn deserialize_datetime<'de, D: Deserializer<'de>>(deserializer: D) -> Result<DateTime<Utc>, D::Error> {
262 let string = String::deserialize(deserializer)?;
263 if let Ok(parsed) = NaiveDateTime::from_str(&string) {
264 return Ok(parsed.and_utc())
265 }
266 let fmt = match (string.ends_with('Z'), string.contains('.')) {
267 (false, false) => "%FT%TZ%#z",
268 (false, true) => "%FT%TZ%.3f%#z",
269 (true, false) => "%FT%TZ",
270 (true, true) => "%FT%T%.3fZ",
271 };
272 NaiveDateTime::parse_from_str(&string, fmt).map(|x| x.and_utc()).map_err(D::Error::custom)
273}
274
275#[allow(clippy::unnecessary_wraps, reason = "serde return type")]
278pub(crate) fn try_deserialize_datetime<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error> {
279 Ok(deserialize_datetime(deserializer).ok())
280}
281
282pub(crate) fn deserialize_comma_separated_vec<'de, D: Deserializer<'de>, T: FromStr>(deserializer: D) -> Result<Vec<T>, D::Error>
286where
287 <T as FromStr>::Err: Debug,
288{
289 String::deserialize(deserializer)?
290 .split(", ")
291 .map(|entry| T::from_str(entry))
292 .collect::<Result<Vec<T>, <T as FromStr>::Err>>()
293 .map_err(|e| Error::custom(format!("{e:?}")))
294}
295
296#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)]
297pub enum TeamSide {
298 #[default]
299 Home,
300 Away,
301}
302
303impl Not for TeamSide {
304 type Output = Self;
305
306 fn not(self) -> Self::Output {
307 match self {
308 Self::Home => Self::Away,
309 Self::Away => Self::Home,
310 }
311 }
312}
313
314impl TeamSide {
315 #[must_use]
316 pub const fn is_home(self) -> bool {
317 matches!(self, Self::Home)
318 }
319
320 #[must_use]
321 pub const fn is_away(self) -> bool {
322 matches!(self, Self::Away)
323 }
324}
325
326pub fn deserialize_team_side_from_is_home<'de, D: Deserializer<'de>>(deserializer: D) -> Result<TeamSide, D::Error> {
327 Ok(if bool::deserialize(deserializer)? { TeamSide::Home } else { TeamSide::Away })
328}
329
330#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone, Default)]
340pub struct HomeAway<T> {
341 pub home: T,
342 pub away: T,
343}
344
345impl<T> HomeAway<T> {
346 #[must_use]
348 pub const fn new(home: T, away: T) -> Self {
349 Self { home, away }
350 }
351
352 #[must_use]
354 pub fn choose(self, side: TeamSide) -> T {
355 match side {
356 TeamSide::Home => self.home,
357 TeamSide::Away => self.away,
358 }
359 }
360
361 #[must_use]
362 pub const fn as_ref(&self) -> HomeAway<&T> {
363 HomeAway {
364 home: &self.home,
365 away: &self.away,
366 }
367 }
368
369 #[must_use]
370 pub const fn as_mut(&mut self) -> HomeAway<&mut T> {
371 HomeAway {
372 home: &mut self.home,
373 away: &mut self.away,
374 }
375 }
376
377 #[must_use]
378 pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> HomeAway<U> {
379 HomeAway {
380 home: f(self.home),
381 away: f(self.away),
382 }
383 }
384
385 #[must_use]
387 pub fn swap(self) -> Self {
388 Self {
389 home: self.away,
390 away: self.home,
391 }
392 }
393
394 #[must_use]
395 pub fn zip<U>(self, other: HomeAway<U>) -> HomeAway<(T, U)> {
396 HomeAway {
397 home: (self.home, other.home),
398 away: (self.away, other.away),
399 }
400 }
401
402 #[must_use]
403 pub fn zip_with<U, V, F: FnMut(T, U) -> V>(self, other: HomeAway<U>, mut f: F) -> HomeAway<V> {
404 HomeAway {
405 home: f(self.home, other.home),
406 away: f(self.away, other.away),
407 }
408 }
409
410 #[must_use]
411 pub fn combine<U, F: FnOnce(T, T) -> U>(self, f: F) -> U {
412 f(self.home, self.away)
413 }
414
415 #[must_use]
417 pub fn added(self) -> <T as Add>::Output where T: Add {
418 self.home + self.away
419 }
420
421 #[must_use]
422 pub fn both(self, mut f: impl FnMut(T) -> bool) -> bool {
423 f(self.home) && f(self.away)
424 }
425
426 #[must_use]
427 pub fn either(self, mut f: impl FnMut(T) -> bool) -> bool {
428 f(self.home) || f(self.away)
429 }
430}
431
432impl<T> HomeAway<Option<T>> {
433 #[must_use]
434 pub fn flatten(self) -> Option<HomeAway<T>> {
435 Some(HomeAway {
436 home: self.home?,
437 away: self.away?,
438 })
439 }
440}
441
442impl<T> From<(T, T)> for HomeAway<T> {
443 fn from((home, away): (T, T)) -> Self {
444 Self {
445 home,
446 away
447 }
448 }
449}
450
451#[derive(Debug, Deserialize, PartialEq, Clone, Default)]
455#[serde(rename_all = "camelCase")]
456pub struct Location {
457 pub address_line_1: Option<String>,
458 pub address_line_2: Option<String>,
459 pub address_line_3: Option<String>,
460 pub address_line_4: Option<String>,
461 pub attention: Option<String>,
462 #[serde(alias = "phone")]
463 pub phone_number: Option<String>,
464 pub city: Option<String>,
465 #[serde(alias = "province")]
466 pub state: Option<String>,
467 pub country: Option<String>,
468 #[serde(rename = "stateAbbrev")] pub state_abbreviation: Option<String>,
469 pub postal_code: Option<String>,
470 pub latitude: Option<f64>,
471 pub longitude: Option<f64>,
472 pub azimuth_angle: Option<f64>,
473 pub elevation: Option<u32>,
474}
475
476#[derive(Debug, Deserialize, PartialEq, Clone)]
478#[serde(rename_all = "camelCase")]
479pub struct FieldInfo {
480 pub capacity: u32,
481 pub turf_type: TurfType,
482 pub roof_type: RoofType,
483 pub left_line: Option<u32>,
484 pub left: Option<u32>,
485 pub left_center: Option<u32>,
486 pub center: Option<u32>,
487 pub right_center: Option<u32>,
488 pub right: Option<u32>,
489 pub right_line: Option<u32>,
490}
491
492#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Display)]
494pub enum TurfType {
495 #[serde(rename = "Artificial Turf")]
496 #[display("Artificial Turf")]
497 ArtificialTurf,
498
499 #[serde(rename = "Grass")]
500 #[display("Grass")]
501 Grass,
502}
503
504#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Display)]
506pub enum RoofType {
507 #[serde(rename = "Retractable")]
508 #[display("Retractable")]
509 Retractable,
510
511 #[serde(rename = "Open")]
512 #[display("Open")]
513 Open,
514
515 #[serde(rename = "Dome")]
516 #[display("Dome")]
517 Dome,
518}
519
520#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
522#[serde(rename_all = "camelCase")]
523pub struct TimeZoneData {
524 #[serde(rename = "id")]
525 pub timezone: chrono_tz::Tz,
526 pub offset: i32,
527 pub offset_at_game_time: i32,
528}
529
530#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
532pub struct ExternalReference {
533 #[serde(rename = "xrefId")]
534 pub id: String,
535 #[serde(rename = "xrefType")]
536 pub xref_type: String,
537 pub season: Option<SeasonId>,
538}
539
540#[derive(Debug, Deserialize, PartialEq, Clone)]
542#[serde(rename_all = "camelCase")]
543pub struct TrackingSystem {
544 pub id: TrackingSystemVendorId,
545 pub description: String,
546 pub pitch_vendor: Option<TrackingSystemVendor>,
547 pub hit_vendor: Option<TrackingSystemVendor>,
548 pub player_vendor: Option<TrackingSystemVendor>,
549 pub skeletal_vendor: Option<TrackingSystemVendor>,
550 pub bat_vendor: Option<TrackingSystemVendor>,
551 pub biomechanics_vendor: Option<TrackingSystemVendor>,
552}
553
554id!(TrackingSystemVendorId { id: u32 });
555
556#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
558pub struct TrackingSystemVendor {
559 pub id: TrackingSystemVendorId,
560 pub description: String,
561 #[serde(rename = "version")]
562 pub details: String,
563}
564
565#[derive(Debug, Copy, Clone)]
569pub enum IntegerOrFloatStat {
570 Integer(i64),
572 Float(f64),
574}
575
576impl PartialEq for IntegerOrFloatStat {
577 fn eq(&self, other: &Self) -> bool {
578 match (*self, *other) {
579 (Self::Integer(lhs), Self::Integer(rhs)) => lhs == rhs,
580 (Self::Float(lhs), Self::Float(rhs)) => lhs == rhs,
581
582 #[allow(clippy::cast_precision_loss, reason = "we checked if it's perfectly representable")]
583 #[allow(clippy::cast_possible_truncation, reason = "we checked if it's perfectly representable")]
584 (Self::Integer(int), Self::Float(float)) | (Self::Float(float), Self::Integer(int)) => {
585 if float.is_normal() && float.floor() == float && (i64::MIN as f64..-(i64::MIN as f64)).contains(&float) {
588 float as i64 == int
589 } else {
590 false
591 }
592 },
593 }
594 }
595}
596
597impl<'de> Deserialize<'de> for IntegerOrFloatStat {
598 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
599 where
600 D: Deserializer<'de>
601 {
602 struct Visitor;
603
604 impl serde::de::Visitor<'_> for Visitor {
605 type Value = IntegerOrFloatStat;
606
607 fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
608 formatter.write_str("integer, float, or string that can be parsed to either")
609 }
610
611 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
612 where
613 E: Error,
614 {
615 Ok(IntegerOrFloatStat::Integer(v))
616 }
617
618 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
619 where
620 E: Error,
621 {
622 Ok(IntegerOrFloatStat::Float(v))
623 }
624
625 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
626 where
627 E: Error,
628 {
629 if v == "-.--" || v == ".---" {
630 Ok(IntegerOrFloatStat::Float(0.0))
631 } else if let Ok(i) = v.parse::<i64>() {
632 Ok(IntegerOrFloatStat::Integer(i))
633 } else if let Ok(f) = v.parse::<f64>() {
634 Ok(IntegerOrFloatStat::Float(f))
635 } else {
636 Err(E::invalid_value(serde::de::Unexpected::Str(v), &self))
637 }
638 }
639 }
640
641 deserializer.deserialize_any(Visitor)
642 }
643}
644
645#[derive(Debug, Deserialize, Display)]
649#[display("An error occurred parsing the statsapi http request: {message}")]
650pub struct MLBError {
651 pub(crate) message: String,
652}
653
654impl MLBError {
655 pub fn new(message: impl Into<String>) -> Self {
656 Self { message: message.into() }
657 }
658}
659
660impl std::error::Error for MLBError {}
661
662#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone, Default)]
664#[serde(try_from = "&str")]
665pub struct RGBAColor {
666 pub red: u8,
667 pub green: u8,
668 pub blue: u8,
669 pub alpha: u8,
670}
671
672impl Display for RGBAColor {
673 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
674 write!(f, "0x{:02x}{:02x}{:02x}{:02x}", self.alpha, self.red, self.green, self.blue)
675 }
676}
677
678#[derive(Debug, Error)]
680pub enum RGBAColorFromStrError {
681 #[error("Invalid spec")]
682 InvalidFormat,
683 #[error(transparent)]
684 InvalidInt(#[from] ParseIntError),
685 #[error(transparent)]
686 InvalidFloat(#[from] ParseFloatError),
687}
688
689impl<'a> TryFrom<&'a str> for RGBAColor {
690 type Error = <Self as FromStr>::Err;
691
692 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
693 <Self as FromStr>::from_str(value)
694 }
695}
696
697impl FromStr for RGBAColor {
698 type Err = RGBAColorFromStrError;
699
700 #[allow(clippy::single_char_pattern, reason = "other patterns are strings, the choice to make that one a char does not denote any special case")]
702 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, reason = "intended behaviour with alpha channel")]
703 fn from_str(mut s: &str) -> Result<Self, Self::Err> {
704 s = s.strip_suffix("rgba(").ok_or(Self::Err::InvalidFormat)?;
705 let (red, s) = s.split_once(", ").ok_or(Self::Err::InvalidFormat)?;
706 let red = red.parse::<u8>()?;
707 let (green, s) = s.split_once(", ").ok_or(Self::Err::InvalidFormat)?;
708 let green = green.parse::<u8>()?;
709 let (blue, s) = s.split_once(", ").ok_or(Self::Err::InvalidFormat)?;
710 let blue = blue.parse::<u8>()?;
711 let (alpha, s) = s.split_once(")").ok_or(Self::Err::InvalidFormat)?;
712 let alpha = (alpha.parse::<f32>()? * 255.0).round() as u8;
713 if !s.is_empty() { return Err(Self::Err::InvalidFormat); }
714 Ok(Self {
715 red,
716 green,
717 blue,
718 alpha
719 })
720 }
721}
722
723#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone, Display, FromStr)]
725#[serde(try_from = "&str")]
726pub enum HeatmapTemperature {
727 Hot,
728 Warm,
729 Lukewarm,
730 Cool,
731 Cold,
732}
733
734impl<'a> TryFrom<&'a str> for HeatmapTemperature {
735 type Error = <Self as FromStr>::Err;
736
737 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
738 <Self as FromStr>::from_str(value)
739 }
740}
741
742pub(crate) fn write_nth(n: usize, f: &mut Formatter<'_>) -> std::fmt::Result {
743 write!(f, "{n}")?;
744 let (tens, ones) = (n / 10, n % 10);
745 let is_teen = (tens % 10) == 1;
746 if is_teen {
747 write!(f, "th")?;
748 } else {
749 write!(f, "{}", match ones {
750 1 => "st",
751 2 => "nd",
752 3 => "rd",
753 _ => "th",
754 })?;
755 }
756 Ok(())
757}
758
759pub fn deserialize_time_delta_from_hms<'de, D: Deserializer<'de>>(deserializer: D) -> Result<TimeDelta, D::Error> {
762 let string = String::deserialize(deserializer)?;
763 let (hour, rest) = string.split_once(':').ok_or_else(|| D::Error::custom("Unable to find `:`"))?;
764 let (minute, second) = rest.split_once(':').ok_or_else(|| D::Error::custom("Unable to find `:`"))?;
765 let hour = hour.parse::<u32>().map_err(D::Error::custom)?;
766 let minute = minute.parse::<u32>().map_err(D::Error::custom)?;
767 let second = second.parse::<u32>().map_err(D::Error::custom)?;
768
769 TimeDelta::new(((hour * 24 + minute) * 60 + second) as _, 0).ok_or_else(|| D::Error::custom("Invalid time quantity, overflow."))
770}
771
772#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone, Display, FromStr)]
774#[serde(try_from = "&str")]
775pub enum DayHalf {
776 AM,
777 PM,
778}
779
780impl DayHalf {
781 #[must_use]
783 pub fn into_24_hour_time(self, mut time: NaiveTime) -> NaiveTime {
784 if (self == Self::PM) ^ (time.hour() == 12) {
785 time += TimeDelta::hours(12);
786 }
787
788 time
789 }
790}
791
792impl<'a> TryFrom<&'a str> for DayHalf {
793 type Error = <Self as FromStr>::Err;
794
795 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
796 <Self as FromStr>::from_str(value)
797 }
798}
799
800#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
801#[serde(rename_all = "camelCase")]
802pub struct ResourceUsage {
803 pub used: u32,
804 pub remaining: u32,
805}
806
807#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Default)]
808#[serde(rename_all = "camelCase")]
809pub struct ResultHoldingResourceUsage {
810 #[serde(rename = "usedSuccessful")]
811 pub used_successfully: u32,
812 pub used_failed: u32,
813 pub remaining: u32,
814}
815
816#[cfg(test)]
817mod tests {
818 use super::*;
819
820 #[test]
821 fn test_ampm() {
822 assert_eq!(NaiveTime::from_hms_opt(0, 0, 0).unwrap(), DayHalf::AM.into_24_hour_time(NaiveTime::from_hms_opt(12, 0, 0).unwrap()));
823 assert_eq!(NaiveTime::from_hms_opt(12, 0, 0).unwrap(), DayHalf::PM.into_24_hour_time(NaiveTime::from_hms_opt(12, 0, 0).unwrap()));
824 assert_eq!(NaiveTime::from_hms_opt(0, 1, 0).unwrap(), DayHalf::AM.into_24_hour_time(NaiveTime::from_hms_opt(12, 1, 0).unwrap()));
825 assert_eq!(NaiveTime::from_hms_opt(12, 1, 0).unwrap(), DayHalf::PM.into_24_hour_time(NaiveTime::from_hms_opt(12, 1, 0).unwrap()));
826 assert_eq!(NaiveTime::from_hms_opt(0, 1, 0).unwrap(), DayHalf::AM.into_24_hour_time(NaiveTime::from_hms_opt(12, 1, 0).unwrap()));
827 assert_eq!(NaiveTime::from_hms_opt(23, 59, 0).unwrap(), DayHalf::PM.into_24_hour_time(NaiveTime::from_hms_opt(11, 59, 0).unwrap()));
828 assert_eq!(NaiveTime::from_hms_opt(1, 1, 0).unwrap(), DayHalf::AM.into_24_hour_time(NaiveTime::from_hms_opt(1, 1, 0).unwrap()));
829 }
830}