Skip to main content

set_time/
lib.rs

1//! set-time
2//!
3//! A simple cross-platform utility library to set the system time for a system
4//!
5//! # Example
6//! ```ignore
7//! use set_time::{set_time, Utc, DateTime};
8//!
9//! fn main() {
10//!   // Set the system time to January 1, 2020
11//!   let new_time_str = "2020-01-01 00:00:00";
12//!   let new_time = DateTime::parse_from_str(new_time_str, "%Y-%m-%d %H:%M:%S")
13//!       .expect("Failed to parse time");
14//!   set_time(new_time).expect("Failed to set system time");
15//! }
16//! ```
17#![allow(dead_code)]
18
19pub use chrono::{DateTime, Datelike, Timelike, Utc, offset::TimeZone};
20
21#[derive(Debug)]
22pub enum SetTimeError {
23    PermissionDenied,
24    InvalidTime,
25}
26
27impl std::fmt::Display for SetTimeError {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        match self {
30            SetTimeError::PermissionDenied => write!(f, "Permission denied, are you running as administrator/root?"),
31            SetTimeError::InvalidTime => write!(f, "Invalid time format"),
32        }
33    }
34}
35impl std::error::Error for SetTimeError {}
36
37#[cfg(target_os = "windows")]
38fn set_time_windows<D: Datelike + Timelike>(time: D) -> Result<(), SetTimeError> {
39    use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime};
40    let time: SYSTEMTIME = SYSTEMTIME {
41        wYear: time.year() as u16,
42        wMonth: time.month() as u16,
43        wDayOfWeek: time.weekday().num_days_from_sunday() as u16,
44        wDay: time.day() as u16,
45        wHour: time.hour() as u16,
46        wMinute: time.minute() as u16,
47        wSecond: time.second() as u16,
48        wMilliseconds: (time.nanosecond() / 1_000_000) as u16,
49    };
50    unsafe {
51        let result = SetSystemTime(&time);
52        if result == 0 {
53            return Err(SetTimeError::PermissionDenied);
54        }
55    }
56    Ok(())
57}
58
59#[cfg(unix)]
60fn set_time_unix<D: Datelike + Timelike>(time: D) -> Result<(), SetTimeError> {
61    let naive_date = chrono::NaiveDate::from_ymd_opt(time.year(), time.month(), time.day())
62        .ok_or(SetTimeError::InvalidTime)?;
63    let naive_time = chrono::NaiveTime::from_hms_nano_opt(time.hour(), time.minute(), time.second(), time.nanosecond())
64        .ok_or(SetTimeError::InvalidTime)?;
65    let naive_dt = chrono::NaiveDateTime::new(naive_date, naive_time);
66    
67    // Convert to Unix timestamp
68    let timestamp = naive_dt.and_utc().timestamp();
69    
70    let tv = libc::timeval {
71        tv_sec: timestamp as libc::time_t,
72        tv_usec: (time.nanosecond() / 1000) as libc::suseconds_t,
73    };
74    
75    unsafe {
76        let result = libc::settimeofday(&tv, std::ptr::null());
77        if result != 0 {
78            return Err(SetTimeError::PermissionDenied);
79        }
80    }
81    Ok(())
82}
83
84#[cfg(target_os = "macos")]
85fn set_time_macos<D: Datelike + Timelike>(time: D) -> Result<(), SetTimeError> {
86    set_time_unix(time)
87}
88
89#[cfg(target_os = "linux")]
90fn set_time_linux<D: Datelike + Timelike>(time: D) -> Result<(), SetTimeError> {
91    set_time_unix(time)
92}
93
94/// Sets the system time to the specified time.
95/// 
96/// # Errors
97/// 
98/// - `SetTimeError::PermissionDenied` if the operation fails due to insufficient permissions.
99/// - `SetTimeError::InvalidTime` if the provided time is invalid.
100pub fn set_time<D: Datelike + Timelike>(time: D) -> Result<(), SetTimeError> {
101    #[cfg(target_os = "windows")]
102    return set_time_windows(time);
103    
104    #[cfg(target_os = "linux")]
105    return set_time_linux(time);
106    
107    #[cfg(target_os = "macos")]
108    return set_time_macos(time);
109    
110    #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
111    compile_error!("Unsupported platform");
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use chrono::NaiveDateTime;
118
119    #[test]
120    fn test_set_time() {
121        let original_time = chrono::Utc::now();
122        let new_time = NaiveDateTime::parse_from_str("2020-01-01 00:00:00", "%Y-%m-%d %H:%M:%S")
123            .unwrap()
124            .and_utc();
125        let result = set_time(new_time);
126        assert!(result.is_ok());
127        let now_after = chrono::Utc::now();
128        assert_eq!(now_after.year(), 2020);
129        assert_eq!(now_after.month(), 1);
130        assert_eq!(now_after.day(), 1);
131        set_time(original_time).expect("Failed to restore original time");
132    }
133}