/ src / aoc / day.rs
day.rs
  1  use chrono::{DateTime, Datelike, TimeZone, Utc};
  2  
  3  use crate::utils::datetime::{EST, now_est};
  4  
  5  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
  6  pub struct AocDay {
  7      pub year: i32,
  8      pub day: u32,
  9  }
 10  
 11  impl AocDay {
 12      pub fn unlock_datetime(self) -> DateTime<Utc> {
 13          EST.with_ymd_and_hms(self.year, 12, self.day, 0, 0, 0)
 14              .unwrap()
 15              .to_utc()
 16      }
 17  
 18      pub fn url(self) -> String {
 19          format!("https://adventofcode.com/{}/day/{}", self.year, self.day)
 20      }
 21  
 22      pub fn current() -> Option<Self> {
 23          let now = now_est();
 24          (now.month() == 12 && now.day() <= number_of_days(now.year())).then_some(Self {
 25              year: now.year(),
 26              day: now.day(),
 27          })
 28      }
 29  
 30      pub fn next() -> Self {
 31          let now = now_est();
 32          let (year, day) = if now.month() != 12 {
 33              // december this year
 34              (now.year(), 1)
 35          } else if now.day() < number_of_days(now.year()) {
 36              // tomorrow
 37              (now.year(), now.day() + 1)
 38          } else {
 39              // next year
 40              (now.year() + 1, 1)
 41          };
 42          Self { year, day }
 43      }
 44  
 45      pub fn most_recent() -> Self {
 46          let now = now_est();
 47          let (year, day) = if now.month() != 12 {
 48              // december last year
 49              (now.year() - 1, number_of_days(now.year() - 1))
 50          } else {
 51              // this year, today or last aoc day
 52              (now.year(), now.day().min(number_of_days(now.year())))
 53          };
 54          Self { year, day }
 55      }
 56  }
 57  
 58  pub fn number_of_days(year: i32) -> u32 {
 59      if year >= 2025 { 12 } else { 25 }
 60  }
 61  
 62  #[cfg(test)]
 63  mod tests {
 64      use super::*;
 65      use crate::utils::datetime::set_now;
 66  
 67      #[test]
 68      fn unlock_datetime() {
 69          for (year, day, expected) in [
 70              (2024, 1, "2024-12-01T06:00:00+01:00"),
 71              (2024, 2, "2024-12-02T06:00:00+01:00"),
 72              (2024, 16, "2024-12-16T06:00:00+01:00"),
 73              (2024, 25, "2024-12-25T06:00:00+01:00"),
 74              (2025, 1, "2025-12-01T06:00:00+01:00"),
 75          ] {
 76              let day = AocDay { year, day };
 77              let expected = expected.parse::<DateTime<Utc>>().unwrap();
 78              assert_eq!(day.unlock_datetime(), expected);
 79          }
 80      }
 81  
 82      #[test]
 83      fn url() {
 84          let day = |year, day| AocDay { year, day };
 85          assert_eq!(day(2023, 1).url(), "https://adventofcode.com/2023/day/1");
 86          assert_eq!(day(2018, 17).url(), "https://adventofcode.com/2018/day/17");
 87      }
 88  
 89      #[test]
 90      fn next() {
 91          for (now, year, day) in [
 92              ("2024-07-17T13:37:42+01:00", 2024, 1),
 93              ("2024-11-17T13:37:42+01:00", 2024, 1),
 94              ("2024-12-01T05:59:07+01:00", 2024, 1),
 95              ("2024-12-01T06:00:13+01:00", 2024, 2),
 96              ("2024-12-15T14:17:00+01:00", 2024, 16),
 97              ("2024-12-25T05:59:07+01:00", 2024, 25),
 98              ("2024-12-25T06:00:07+01:00", 2025, 1),
 99              ("2024-12-31T13:37:42+01:00", 2025, 1),
100              ("2025-01-07T13:37:42+01:00", 2025, 1),
101              ("2025-12-11T14:17:00+01:00", 2025, 12),
102              ("2025-12-12T14:17:00+01:00", 2026, 1),
103          ] {
104              let _guard = set_now(now.parse().unwrap());
105              let expected = AocDay { year, day };
106              assert_eq!(AocDay::next(), expected);
107          }
108      }
109  
110      #[test]
111      fn current() {
112          for (now, expected) in [
113              ("2024-07-17T13:37:42+01:00", None),
114              ("2024-11-17T13:37:42+01:00", None),
115              ("2024-12-01T05:59:07+01:00", None),
116              ("2024-12-01T06:00:13+01:00", Some((2024, 1))),
117              ("2024-12-15T14:17:00+01:00", Some((2024, 15))),
118              ("2024-12-25T05:59:07+01:00", Some((2024, 24))),
119              ("2024-12-25T06:00:07+01:00", Some((2024, 25))),
120              ("2024-12-31T13:37:42+01:00", None),
121              ("2025-01-07T13:37:42+01:00", None),
122              ("2025-12-12T14:17:00+01:00", Some((2025, 12))),
123              ("2025-12-13T14:17:00+01:00", None),
124          ] {
125              let _guard = set_now(now.parse().unwrap());
126              let expected = expected.map(|(year, day)| AocDay { year, day });
127              assert_eq!(AocDay::current(), expected);
128          }
129      }
130  
131      #[test]
132      fn most_recent() {
133          for (now, year, day) in [
134              ("2024-07-17T13:37:42+01:00", 2023, 25),
135              ("2024-11-17T13:37:42+01:00", 2023, 25),
136              ("2024-12-01T05:59:07+01:00", 2023, 25),
137              ("2024-12-01T06:00:13+01:00", 2024, 1),
138              ("2024-12-15T14:17:00+01:00", 2024, 15),
139              ("2024-12-25T05:59:07+01:00", 2024, 24),
140              ("2024-12-25T06:00:07+01:00", 2024, 25),
141              ("2024-12-31T13:37:42+01:00", 2024, 25),
142              ("2025-01-07T13:37:42+01:00", 2024, 25),
143              ("2025-12-11T14:17:00+01:00", 2025, 11),
144              ("2025-12-15T14:17:00+01:00", 2025, 12),
145              ("2026-01-07T13:37:42+01:00", 2025, 12),
146          ] {
147              let _guard = set_now(now.parse().unwrap());
148              let expected = AocDay { year, day };
149              assert_eq!(AocDay::most_recent(), expected);
150          }
151      }
152  }