Skip to content

Commit 321736f

Browse files
committed
add milliseconds
1 parent 87b6e75 commit 321736f

File tree

2 files changed

+158
-152
lines changed

2 files changed

+158
-152
lines changed

src/parse_date.rs

Lines changed: 151 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,146 +1,151 @@
1-
/// This module contains date parsing, serialisation and deserialisation helpers
2-
use std::time::SystemTime;
3-
4-
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, ParseError};
5-
use serde::de::Error;
6-
use serde::{Deserialize, Deserializer, Serializer};
7-
8-
const FORMATS_DT: [&str; 13] = [
9-
"%Y-%m-%d_%H-%M-%S",
10-
"%Y-%m-%d %H:%M:%S",
11-
"%Y-%m-%d %H:%M",
12-
"%y-%m-%d %H:%M:%S",
13-
"%y-%m-%d %H:%M",
14-
"%Y.%m.%d %H:%M:%S",
15-
"%Y.%m.%d %H:%M",
16-
"%y.%m.%d %H:%M:%S",
17-
"%y.%m.%d %H:%M",
18-
"%Y%m%d%H%M%S",
19-
"%Y%m%d%H%M",
20-
"%y%m%d%H%M%S",
21-
"%y%m%d%H%M",
22-
];
23-
const FORMATS_D: [&str; 6] = [
24-
"%Y-%m-%d", "%y-%m-%d", "%Y.%m.%d", "%y.%m.%d", "%Y%m%d", "%y%m%d",
25-
];
26-
27-
/// Serialise a Option<NaiveDateTime> (for serde)
28-
pub fn serialize<S>(date: &Option<NaiveDateTime>, serializer: S) -> Result<S::Ok, S::Error>
29-
where
30-
S: Serializer,
31-
{
32-
match date {
33-
None => serializer.serialize_str(""),
34-
Some(date) => serializer.serialize_str(&format!("{}", date.format("%Y-%m-%d %H:%M:%S"))),
35-
}
36-
}
37-
38-
/// Deserialise a Option<NaiveDateTime> (for serde)
39-
pub fn deserialize<'a, D>(deserializer: D) -> Result<Option<NaiveDateTime>, D::Error>
40-
where
41-
D: Deserializer<'a>,
42-
{
43-
let date = &String::deserialize(deserializer)?;
44-
if date.is_empty() {
45-
Ok(None)
46-
} else {
47-
NaiveDateTime::parse_from_str(date, "%Y-%m-%d %H:%M:%S")
48-
.map_err(Error::custom)
49-
.map(Some)
50-
}
51-
}
52-
53-
/// Convert a SystemTime to NaiveDateTime
54-
pub fn system_to_naive(time: SystemTime) -> NaiveDateTime {
55-
DateTime::<Local>::from(time).naive_local()
56-
}
57-
58-
/// Try parsing a string into a NaiveDateTime
59-
pub fn try_parse(input: &str) -> Result<Option<NaiveDateTime>, &'static str> {
60-
if input.is_empty() {
61-
return Ok(None);
62-
}
63-
for f in FORMATS_DT.iter() {
64-
if let Ok(t) = NaiveDateTime::parse_from_str(input, f) {
65-
return Ok(Some(t));
66-
}
67-
}
68-
for f in FORMATS_D.iter() {
69-
if let Ok(t) = NaiveDate::parse_from_str(input, f) {
70-
return Ok(t.and_hms_opt(0, 0, 0));
71-
}
72-
}
73-
Err("Unknown time format, try, e.g., `YYMMDD`")
74-
}
75-
76-
/// Try parsing a backup file name into a NaiveDateTime
77-
pub fn parse_backup_file_name(filename: &str) -> Result<NaiveDateTime, ParseError> {
78-
const PATTERN: &str = "_%Y-%m-%d_%H-%M-%S.tar.zst";
79-
const LENGTH: usize = "_YYYY-mm-dd_HH-MM-SS.tar.zst".len();
80-
NaiveDateTime::parse_from_str(&filename[filename.len().saturating_sub(LENGTH)..], PATTERN)
81-
}
82-
83-
// Encode a NaiveDateTime into a backup file name
84-
pub fn create_backup_file_name(time: NaiveDateTime) -> String {
85-
format!("{}", time.format("backup_%Y-%m-%d_%H-%M-%S.tar.zst"))
86-
}
87-
88-
/// Get the current time as a NaiveDateTime
89-
pub fn naive_now() -> NaiveDateTime {
90-
system_to_naive(SystemTime::now())
91-
}
92-
93-
#[cfg(test)]
94-
mod tests {
95-
use std::time::SystemTime;
96-
97-
use chrono::{Datelike, Timelike};
98-
99-
use crate::parse_date::parse_backup_file_name;
100-
101-
use super::{system_to_naive, try_parse};
102-
103-
#[test]
104-
fn parse() {
105-
let now = SystemTime::now();
106-
let now2 = system_to_naive(now);
107-
let string = format!("{}-{:02}-{:02}", now2.year(), now2.month(), now2.day());
108-
let now3 = try_parse(&string).unwrap().unwrap();
109-
assert_eq!(now2.year(), now3.year());
110-
assert_eq!(now2.month(), now3.month());
111-
assert_eq!(now2.day(), now3.day());
112-
let string = format!("{}{:02}{:02}", now2.year(), now2.month(), now2.day());
113-
let now3 = try_parse(&string).unwrap().unwrap();
114-
assert_eq!(now2.year(), now3.year());
115-
assert_eq!(now2.month(), now3.month());
116-
assert_eq!(now2.day(), now3.day());
117-
assert_eq!(try_parse("").unwrap(), None);
118-
let string = format!(
119-
"{}.{:02}.{:02} {:02}:{:02}:{:02}",
120-
now2.year(),
121-
now2.month(),
122-
now2.day(),
123-
now2.hour(),
124-
now2.minute(),
125-
now2.second()
126-
);
127-
let now3 = try_parse(&string).unwrap().unwrap();
128-
assert_eq!(now2.year(), now3.year());
129-
assert_eq!(now2.month(), now3.month());
130-
assert_eq!(now2.day(), now3.day());
131-
assert_eq!(now2.hour(), now3.hour());
132-
assert_eq!(now2.minute(), now3.minute());
133-
assert_eq!(now2.second(), now3.second());
134-
135-
assert_eq!(
136-
parse_backup_file_name("backup_2020-12-12_20-12-12.tar.zst").unwrap(),
137-
parse_backup_file_name("test_2020-12-12_20-12-12.tar.zst").unwrap()
138-
);
139-
assert_eq!(
140-
parse_backup_file_name("backup_2020-12-12_20-12-12.tar.zst")
141-
.unwrap()
142-
.year(),
143-
2020
144-
);
145-
}
146-
}
1+
/// This module contains date parsing, serialisation and deserialisation helpers
2+
use std::time::SystemTime;
3+
4+
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, ParseError};
5+
use serde::de::Error;
6+
use serde::{Deserialize, Deserializer, Serializer};
7+
8+
const FORMATS_DT: [&str; 13] = [
9+
"%Y-%m-%d_%H-%M-%S",
10+
"%Y-%m-%d %H:%M:%S",
11+
"%Y-%m-%d %H:%M",
12+
"%y-%m-%d %H:%M:%S",
13+
"%y-%m-%d %H:%M",
14+
"%Y.%m.%d %H:%M:%S",
15+
"%Y.%m.%d %H:%M",
16+
"%y.%m.%d %H:%M:%S",
17+
"%y.%m.%d %H:%M",
18+
"%Y%m%d%H%M%S",
19+
"%Y%m%d%H%M",
20+
"%y%m%d%H%M%S",
21+
"%y%m%d%H%M",
22+
];
23+
const FORMATS_D: [&str; 6] = [
24+
"%Y-%m-%d", "%y-%m-%d", "%Y.%m.%d", "%y.%m.%d", "%Y%m%d", "%y%m%d",
25+
];
26+
const FORMAT_SER: [&str; 2] = ["%Y-%m-%d %H:%M:%S.%3f", "%Y-%m-%d %H:%M:%S"];
27+
28+
/// Serialise a Option<NaiveDateTime> (for serde)
29+
pub fn serialize<S>(date: &Option<NaiveDateTime>, serializer: S) -> Result<S::Ok, S::Error>
30+
where
31+
S: Serializer,
32+
{
33+
match date {
34+
None => serializer.serialize_str(""),
35+
Some(date) => serializer.serialize_str(&format!("{}", date.format(FORMAT_SER[0]))),
36+
}
37+
}
38+
39+
/// Deserialise a Option<NaiveDateTime> (for serde)
40+
pub fn deserialize<'a, D>(deserializer: D) -> Result<Option<NaiveDateTime>, D::Error>
41+
where
42+
D: Deserializer<'a>,
43+
{
44+
let date = &String::deserialize(deserializer)?;
45+
if date.is_empty() {
46+
Ok(None)
47+
} else {
48+
for format in FORMAT_SER.iter() {
49+
if let Ok(time) = NaiveDateTime::parse_from_str(date, format) {
50+
return Ok(Some(time));
51+
}
52+
}
53+
NaiveDateTime::parse_from_str(date, FORMAT_SER[0])
54+
.map_err(Error::custom)
55+
.map(Some)
56+
}
57+
}
58+
59+
/// Convert a SystemTime to NaiveDateTime
60+
pub fn system_to_naive(time: SystemTime) -> NaiveDateTime {
61+
DateTime::<Local>::from(time).naive_local()
62+
}
63+
64+
/// Try parsing a string into a NaiveDateTime
65+
pub fn try_parse(input: &str) -> Result<Option<NaiveDateTime>, &'static str> {
66+
if input.is_empty() {
67+
return Ok(None);
68+
}
69+
for f in FORMATS_DT.iter() {
70+
if let Ok(t) = NaiveDateTime::parse_from_str(input, f) {
71+
return Ok(Some(t));
72+
}
73+
}
74+
for f in FORMATS_D.iter() {
75+
if let Ok(t) = NaiveDate::parse_from_str(input, f) {
76+
return Ok(t.and_hms_opt(0, 0, 0));
77+
}
78+
}
79+
Err("Unknown time format, try, e.g., `YYMMDD`")
80+
}
81+
82+
/// Try parsing a backup file name into a NaiveDateTime
83+
pub fn parse_backup_file_name(filename: &str) -> Result<NaiveDateTime, ParseError> {
84+
const PATTERN: &str = "_%Y-%m-%d_%H-%M-%S.tar.zst";
85+
const LENGTH: usize = "_YYYY-mm-dd_HH-MM-SS.tar.zst".len();
86+
NaiveDateTime::parse_from_str(&filename[filename.len().saturating_sub(LENGTH)..], PATTERN)
87+
}
88+
89+
// Encode a NaiveDateTime into a backup file name
90+
pub fn create_backup_file_name(time: NaiveDateTime) -> String {
91+
format!("{}", time.format("backup_%Y-%m-%d_%H-%M-%S.tar.zst"))
92+
}
93+
94+
/// Get the current time as a NaiveDateTime
95+
pub fn naive_now() -> NaiveDateTime {
96+
system_to_naive(SystemTime::now())
97+
}
98+
99+
#[cfg(test)]
100+
mod tests {
101+
use std::time::SystemTime;
102+
103+
use chrono::{Datelike, Timelike};
104+
105+
use super::{system_to_naive, try_parse};
106+
use crate::parse_date::parse_backup_file_name;
107+
108+
#[test]
109+
fn parse() {
110+
let now = SystemTime::now();
111+
let now2 = system_to_naive(now);
112+
let string = format!("{}-{:02}-{:02}", now2.year(), now2.month(), now2.day());
113+
let now3 = try_parse(&string).unwrap().unwrap();
114+
assert_eq!(now2.year(), now3.year());
115+
assert_eq!(now2.month(), now3.month());
116+
assert_eq!(now2.day(), now3.day());
117+
let string = format!("{}{:02}{:02}", now2.year(), now2.month(), now2.day());
118+
let now3 = try_parse(&string).unwrap().unwrap();
119+
assert_eq!(now2.year(), now3.year());
120+
assert_eq!(now2.month(), now3.month());
121+
assert_eq!(now2.day(), now3.day());
122+
assert_eq!(try_parse("").unwrap(), None);
123+
let string = format!(
124+
"{}.{:02}.{:02} {:02}:{:02}:{:02}",
125+
now2.year(),
126+
now2.month(),
127+
now2.day(),
128+
now2.hour(),
129+
now2.minute(),
130+
now2.second()
131+
);
132+
let now3 = try_parse(&string).unwrap().unwrap();
133+
assert_eq!(now2.year(), now3.year());
134+
assert_eq!(now2.month(), now3.month());
135+
assert_eq!(now2.day(), now3.day());
136+
assert_eq!(now2.hour(), now3.hour());
137+
assert_eq!(now2.minute(), now3.minute());
138+
assert_eq!(now2.second(), now3.second());
139+
140+
assert_eq!(
141+
parse_backup_file_name("backup_2020-12-12_20-12-12.tar.zst").unwrap(),
142+
parse_backup_file_name("test_2020-12-12_20-12-12.tar.zst").unwrap()
143+
);
144+
assert_eq!(
145+
parse_backup_file_name("backup_2020-12-12_20-12-12.tar.zst")
146+
.unwrap()
147+
.year(),
148+
2020
149+
);
150+
}
151+
}

tests/integration_test.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,18 +137,19 @@ fn absolute_test() -> std::result::Result<(), Box<dyn std::error::Error>> {
137137
let mut bw1 = BackupWriter::new(config).0;
138138
bw1.write(|_, _| Ok(()), || ())?;
139139

140-
std::thread::sleep(std::time::Duration::from_millis(10));
141140
let f5 = dir.path().join("e.txt");
142141
let f6 = dir.path().join("f.txt");
143142
File::create(&f5)?;
144143
File::create(&f6)?;
144+
std::thread::sleep(std::time::Duration::from_millis(20));
145145

146146
let mut bw2 = BackupWriter::new(bw1.config).0;
147147
bw2.path = dir.path().join("b2.tar.zst");
148148
bw2.write(|_, _| Ok(()), || ())?;
149149

150150
remove_file(&f2)?;
151151
remove_file(&f5)?;
152+
std::thread::sleep(std::time::Duration::from_millis(20));
152153
assert!(!f2.exists());
153154
assert!(!f5.exists());
154155

@@ -341,7 +342,7 @@ fn time_test() -> Result<(), Box<dyn std::error::Error>> {
341342
let f4 = dir.path().join("d.txt");
342343
File::create(&f1)?;
343344
File::create(&f2)?;
344-
std::thread::sleep(std::time::Duration::from_millis(100));
345+
std::thread::sleep(std::time::Duration::from_millis(20));
345346

346347
let config = Config {
347348
include: vec![dir.path().to_string_lossy().to_string()],
@@ -356,7 +357,7 @@ fn time_test() -> Result<(), Box<dyn std::error::Error>> {
356357
origin: PathBuf::new(),
357358
};
358359

359-
std::thread::sleep(std::time::Duration::from_millis(100));
360+
std::thread::sleep(std::time::Duration::from_millis(20));
360361
File::create(&f3)?;
361362
File::create(&f4)?;
362363

@@ -476,15 +477,15 @@ fn merge_test() -> Result<(), Box<dyn std::error::Error>> {
476477
config.time = Some(naive_now());
477478

478479
remove_file(&f1)?;
479-
std::thread::sleep(std::time::Duration::from_millis(100));
480+
std::thread::sleep(std::time::Duration::from_millis(20));
480481
File::create(&f2)?;
481482

482483
backup(config.clone(), false, false, false, true);
483484
assert!(b2.exists());
484485
config.output = b3.clone();
485486
config.time = Some(naive_now());
486487

487-
std::thread::sleep(std::time::Duration::from_millis(100));
488+
std::thread::sleep(std::time::Duration::from_millis(20));
488489
File::create(&f3)?;
489490

490491
backup(config, false, false, false, true);
@@ -526,7 +527,7 @@ fn merge_test() -> Result<(), Box<dyn std::error::Error>> {
526527
true,
527528
);
528529

529-
std::thread::sleep(std::time::Duration::from_millis(100));
530+
std::thread::sleep(std::time::Duration::from_millis(20));
530531
assert!(!b1.exists());
531532
assert!(!b2.exists());
532533
assert!(!b3.exists());

0 commit comments

Comments
 (0)