steam-user 0.1.0

Steam User web client for Rust - HTTP-based Steam Community interactions
Documentation
//! Time-related utility functions.

use chrono::{DateTime, Datelike, Duration, TimeZone, Utc};

/// Get the timestamp of the last XP reset (Wednesday 1 AM UTC).
///
/// CS2 weekly XP bonuses reset every Wednesday at 1 AM UTC.
/// This function calculates the timestamp (in milliseconds) of the most recent
/// reset.
pub fn get_last_xp_reset_timestamp_ms() -> i64 {
    let now = chrono::Utc::now();
    // Monday = 0, Tuesday = 1, Wednesday = 2, ...
    let weekday = now.weekday().num_days_from_monday();

    // Find this week's Wednesday 1 AM UTC
    let days_since_monday = weekday as i64;
    let days_to_wednesday = 2 - days_since_monday;

    let mut reset_day = now
        .date_naive()
        .and_hms_opt(1, 0, 0)
        .unwrap_or_default() // Should be safe for constant 1,0,0
        .and_utc()
        + chrono::Duration::days(days_to_wednesday);

    // If now is before this week's Wednesday 1 AM, the last reset was last week
    if now < reset_day {
        reset_day -= chrono::Duration::weeks(1);
    }
    reset_day.timestamp_millis()
}

/// Calculate the next Steam market weekly reset time.
///
/// The market resets every Wednesday at 01:00 UTC. Given any `now`, this
/// function returns the `DateTime<Utc>` of the *next* Wednesday 01:00 UTC
/// strictly after `now`.
///
/// Algorithm:
/// - `days_back_to_wednesday = (weekday_from_monday + 5) % 7`
///   * Wednesday → 0, Thursday → 1, Friday → 2, Saturday → 3,
///     Sunday → 4, Monday → 5, Tuesday → 6
/// - `last_reset = today_at_01:00_UTC - days_back_to_wednesday`
/// - `next_reset = last_reset + 7 days`
/// - If `next_reset <= now` (exactly on the boundary), add another week.
pub fn next_market_reset_time(now: DateTime<Utc>) -> DateTime<Utc> {
    // num_days_from_monday(): Mon=0, Tue=1, Wed=2, Thu=3, Fri=4, Sat=5, Sun=6
    let weekday_from_monday = now.weekday().num_days_from_monday() as i64;

    // Distance back (in days) to the most recent Wednesday
    // Wed→0, Thu→1, Fri→2, Sat→3, Sun→4, Mon→5, Tue→6
    let days_back = (weekday_from_monday + 5) % 7;

    let reset_today_naive = now
        .date_naive()
        .and_hms_opt(1, 0, 0)
        .expect("01:00:00 is always valid");
    let reset_today = Utc.from_utc_datetime(&reset_today_naive);

    let last_reset = reset_today - Duration::days(days_back);

    // When today is a Wednesday before 01:00, `last_reset` is in the future
    // (later today) and IS the next reset.
    if now < last_reset {
        return last_reset;
    }

    let mut next_reset = last_reset + Duration::weeks(1);
    if next_reset <= now {
        next_reset += Duration::weeks(1);
    }
    next_reset
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::TimeZone;

    fn utc(year: i32, month: u32, day: u32, hour: u32, min: u32) -> DateTime<Utc> {
        Utc.with_ymd_and_hms(year, month, day, hour, min, 0)
            .single()
            .expect("valid datetime")
    }

    /// Expected next Wednesday 01:00 UTC given the inputs below.
    fn next_wed_01(year: i32, month: u32, day: u32) -> DateTime<Utc> {
        utc(year, month, day, 1, 0)
    }

    #[test]
    fn tuesday_before_reset_next_wed_is_tomorrow() {
        // Tuesday 2024-06-04 23:00 UTC → next reset is Wed 2024-06-05 01:00 UTC
        let now = utc(2024, 6, 4, 23, 0);
        assert_eq!(next_market_reset_time(now), next_wed_01(2024, 6, 5, ));
    }

    #[test]
    fn wednesday_before_reset_same_day() {
        // Wednesday 2024-06-05 00:30 UTC (before 01:00) → reset is today at 01:00
        let now = utc(2024, 6, 5, 0, 30);
        assert_eq!(next_market_reset_time(now), next_wed_01(2024, 6, 5));
    }

    #[test]
    fn wednesday_after_reset_next_week() {
        // Wednesday 2024-06-05 01:30 UTC (after 01:00) → next reset is 2024-06-12 01:00
        let now = utc(2024, 6, 5, 1, 30);
        assert_eq!(next_market_reset_time(now), next_wed_01(2024, 6, 12));
    }

    #[test]
    fn thursday_after_reset_next_week() {
        // Thursday 2024-06-06 02:00 UTC → next reset is 2024-06-12 01:00
        let now = utc(2024, 6, 6, 2, 0);
        assert_eq!(next_market_reset_time(now), next_wed_01(2024, 6, 12));
    }

    #[test]
    fn sunday_midday_next_wed() {
        // Sunday 2024-06-09 12:00 UTC → next reset is 2024-06-12 01:00
        let now = utc(2024, 6, 9, 12, 0);
        assert_eq!(next_market_reset_time(now), next_wed_01(2024, 6, 12));
    }
}