Anki/rslib/src/timestamp.rs
Damien Elmes adf6220bb9 Fix for crash with invalid dates on Windows (#1837)
https://forums.ankiweb.net/t/bug-report-crashing-when-opening-deck-browser/19768

Caused by a note mtime that was 1000x larger than it should have been.
Check DB will now fix this case (but there are others it still does not
cover, such as invalid card/note IDs).

https://docs.rs/chrono/0.4.19/x86_64-pc-windows-msvc/src/chrono/sys/windows.rs.html#128
2022-05-07 10:30:23 +10:00

103 lines
2.7 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::time;
use chrono::prelude::*;
use crate::{define_newtype, prelude::*};
define_newtype!(TimestampSecs, i64);
define_newtype!(TimestampMillis, i64);
impl TimestampSecs {
pub fn now() -> Self {
Self(elapsed().as_secs() as i64)
}
pub fn zero() -> Self {
Self(0)
}
pub fn elapsed_secs_since(self, other: TimestampSecs) -> i64 {
self.0 - other.0
}
pub fn elapsed_secs(self) -> u64 {
(Self::now().0 - self.0).max(0) as u64
}
pub fn as_millis(self) -> TimestampMillis {
TimestampMillis(self.0 * 1000)
}
#[cfg(windows)]
pub(crate) fn local_datetime(self) -> Result<DateTime<Local>> {
std::panic::catch_unwind(|| Local.timestamp(self.0, 0))
.map_err(|_err| AnkiError::invalid_input("invalid date"))
}
#[cfg(not(windows))]
pub(crate) fn local_datetime(self) -> Result<DateTime<Local>> {
Ok(Local.timestamp(self.0, 0))
}
/// YYYY-mm-dd
pub(crate) fn date_string(self) -> String {
self.local_datetime()
.map(|dt| dt.format("%Y-%m-%d").to_string())
.unwrap_or_else(|_err| "invalid date".to_string())
}
/// HH-MM
pub(crate) fn time_string(self) -> String {
self.local_datetime()
.map(|dt| dt.format("%H:%M").to_string())
.unwrap_or_else(|_err| "invalid date".to_string())
}
pub fn local_utc_offset(self) -> Result<FixedOffset> {
Ok(*self.local_datetime()?.offset())
}
pub fn datetime(self, utc_offset: FixedOffset) -> DateTime<FixedOffset> {
utc_offset.timestamp(self.0, 0)
}
pub fn adding_secs(self, secs: i64) -> Self {
TimestampSecs(self.0 + secs)
}
}
impl TimestampMillis {
pub fn now() -> Self {
Self(elapsed().as_millis() as i64)
}
pub fn zero() -> Self {
Self(0)
}
pub fn as_secs(self) -> TimestampSecs {
TimestampSecs(self.0 / 1000)
}
}
fn elapsed() -> time::Duration {
if *crate::PYTHON_UNIT_TESTS {
// shift clock around rollover time to accomodate Python tests that make bad assumptions.
// we should update the tests in the future and remove this hack.
let mut elap = time::SystemTime::now()
.duration_since(time::SystemTime::UNIX_EPOCH)
.unwrap();
let now = Utc::now();
if now.hour() >= 2 && now.hour() < 4 {
elap -= time::Duration::from_secs(60 * 60 * 2);
}
elap
} else {
time::SystemTime::now()
.duration_since(time::SystemTime::UNIX_EPOCH)
.unwrap()
}
}