Skip to main content

steam_user/utils/
time.rs

1//! Time-related utility functions.
2
3use chrono::{DateTime, Datelike, Duration, TimeZone, Utc};
4
5/// Get the timestamp of the last XP reset (Wednesday 1 AM UTC).
6///
7/// CS2 weekly XP bonuses reset every Wednesday at 1 AM UTC.
8/// This function calculates the timestamp (in milliseconds) of the most recent
9/// reset.
10pub fn get_last_xp_reset_timestamp_ms() -> i64 {
11    let now = chrono::Utc::now();
12    // Monday = 0, Tuesday = 1, Wednesday = 2, ...
13    let weekday = now.weekday().num_days_from_monday();
14
15    // Find this week's Wednesday 1 AM UTC
16    let days_since_monday = weekday as i64;
17    let days_to_wednesday = 2 - days_since_monday;
18
19    let mut reset_day = now
20        .date_naive()
21        .and_hms_opt(1, 0, 0)
22        .unwrap_or_default() // Should be safe for constant 1,0,0
23        .and_utc()
24        + chrono::Duration::days(days_to_wednesday);
25
26    // If now is before this week's Wednesday 1 AM, the last reset was last week
27    if now < reset_day {
28        reset_day -= chrono::Duration::weeks(1);
29    }
30    reset_day.timestamp_millis()
31}
32
33/// Calculate the next Steam market weekly reset time.
34///
35/// The market resets every Wednesday at 01:00 UTC. Given any `now`, this
36/// function returns the `DateTime<Utc>` of the *next* Wednesday 01:00 UTC
37/// strictly after `now`.
38///
39/// Algorithm:
40/// - `days_back_to_wednesday = (weekday_from_monday + 5) % 7`
41///   * Wednesday → 0, Thursday → 1, Friday → 2, Saturday → 3,
42///     Sunday → 4, Monday → 5, Tuesday → 6
43/// - `last_reset = today_at_01:00_UTC - days_back_to_wednesday`
44/// - `next_reset = last_reset + 7 days`
45/// - If `next_reset <= now` (exactly on the boundary), add another week.
46pub fn next_market_reset_time(now: DateTime<Utc>) -> DateTime<Utc> {
47    // num_days_from_monday(): Mon=0, Tue=1, Wed=2, Thu=3, Fri=4, Sat=5, Sun=6
48    let weekday_from_monday = now.weekday().num_days_from_monday() as i64;
49
50    // Distance back (in days) to the most recent Wednesday
51    // Wed→0, Thu→1, Fri→2, Sat→3, Sun→4, Mon→5, Tue→6
52    let days_back = (weekday_from_monday + 5) % 7;
53
54    let reset_today_naive = now
55        .date_naive()
56        .and_hms_opt(1, 0, 0)
57        .expect("01:00:00 is always valid");
58    let reset_today = Utc.from_utc_datetime(&reset_today_naive);
59
60    let last_reset = reset_today - Duration::days(days_back);
61
62    // When today is a Wednesday before 01:00, `last_reset` is in the future
63    // (later today) and IS the next reset.
64    if now < last_reset {
65        return last_reset;
66    }
67
68    let mut next_reset = last_reset + Duration::weeks(1);
69    if next_reset <= now {
70        next_reset += Duration::weeks(1);
71    }
72    next_reset
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use chrono::TimeZone;
79
80    fn utc(year: i32, month: u32, day: u32, hour: u32, min: u32) -> DateTime<Utc> {
81        Utc.with_ymd_and_hms(year, month, day, hour, min, 0)
82            .single()
83            .expect("valid datetime")
84    }
85
86    /// Expected next Wednesday 01:00 UTC given the inputs below.
87    fn next_wed_01(year: i32, month: u32, day: u32) -> DateTime<Utc> {
88        utc(year, month, day, 1, 0)
89    }
90
91    #[test]
92    fn tuesday_before_reset_next_wed_is_tomorrow() {
93        // Tuesday 2024-06-04 23:00 UTC → next reset is Wed 2024-06-05 01:00 UTC
94        let now = utc(2024, 6, 4, 23, 0);
95        assert_eq!(next_market_reset_time(now), next_wed_01(2024, 6, 5, ));
96    }
97
98    #[test]
99    fn wednesday_before_reset_same_day() {
100        // Wednesday 2024-06-05 00:30 UTC (before 01:00) → reset is today at 01:00
101        let now = utc(2024, 6, 5, 0, 30);
102        assert_eq!(next_market_reset_time(now), next_wed_01(2024, 6, 5));
103    }
104
105    #[test]
106    fn wednesday_after_reset_next_week() {
107        // Wednesday 2024-06-05 01:30 UTC (after 01:00) → next reset is 2024-06-12 01:00
108        let now = utc(2024, 6, 5, 1, 30);
109        assert_eq!(next_market_reset_time(now), next_wed_01(2024, 6, 12));
110    }
111
112    #[test]
113    fn thursday_after_reset_next_week() {
114        // Thursday 2024-06-06 02:00 UTC → next reset is 2024-06-12 01:00
115        let now = utc(2024, 6, 6, 2, 0);
116        assert_eq!(next_market_reset_time(now), next_wed_01(2024, 6, 12));
117    }
118
119    #[test]
120    fn sunday_midday_next_wed() {
121        // Sunday 2024-06-09 12:00 UTC → next reset is 2024-06-12 01:00
122        let now = utc(2024, 6, 9, 12, 0);
123        assert_eq!(next_market_reset_time(now), next_wed_01(2024, 6, 12));
124    }
125}