1use chrono::{DateTime, NaiveDate, Utc};
4use std::path::PathBuf;
5
6#[must_use]
8pub fn get_default_database_path() -> PathBuf {
9 let home = std::env::var("HOME").unwrap_or_else(|_| "~".to_string());
10 PathBuf::from(format!(
11 "{home}/Library/Group Containers/JLMPQHK86H.com.culturedcode.ThingsMac/ThingsData-0Z0Z2/Things Database.thingsdatabase/main.sqlite"
12 ))
13}
14
15#[must_use]
17pub fn format_date(date: &NaiveDate) -> String {
18 date.format("%Y-%m-%d").to_string()
19}
20
21#[must_use]
23pub fn format_datetime(dt: &DateTime<Utc>) -> String {
24 dt.format("%Y-%m-%d %H:%M:%S UTC").to_string()
25}
26
27pub fn parse_date(date_str: &str) -> Result<NaiveDate, chrono::ParseError> {
32 NaiveDate::parse_from_str(date_str, "%Y-%m-%d")
33}
34
35#[must_use]
37pub fn is_valid_uuid(uuid_str: &str) -> bool {
38 uuid::Uuid::parse_str(uuid_str).is_ok()
39}
40
41#[must_use]
43pub fn truncate_string(s: &str, max_len: usize) -> String {
44 if s.len() <= max_len {
45 s.to_string()
46 } else {
47 format!("{}...", &s[..max_len.saturating_sub(3)])
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54 use chrono::{Datelike, NaiveDate};
55
56 #[test]
57 fn test_get_default_database_path() {
58 let path = get_default_database_path();
59
60 assert!(path.to_string_lossy().contains("Library"));
62 assert!(path.to_string_lossy().contains("Group Containers"));
63 assert!(path
64 .to_string_lossy()
65 .contains("JLMPQHK86H.com.culturedcode.ThingsMac"));
66 assert!(path.to_string_lossy().contains("ThingsData-0Z0Z2"));
67 assert!(path
68 .to_string_lossy()
69 .contains("Things Database.thingsdatabase"));
70 assert!(path.to_string_lossy().contains("main.sqlite"));
71
72 let home = std::env::var("HOME").unwrap_or_else(|_| "~".to_string());
74 assert!(path.to_string_lossy().starts_with(&home));
75 }
76
77 #[test]
78 fn test_format_date() {
79 let date = NaiveDate::from_ymd_opt(2023, 12, 25).unwrap();
80 let formatted = format_date(&date);
81 assert_eq!(formatted, "2023-12-25");
82 }
83
84 #[test]
85 fn test_format_date_edge_cases() {
86 let date = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
88 let formatted = format_date(&date);
89 assert_eq!(formatted, "2024-01-01");
90
91 let date = NaiveDate::from_ymd_opt(2023, 12, 31).unwrap();
93 let formatted = format_date(&date);
94 assert_eq!(formatted, "2023-12-31");
95
96 let date = NaiveDate::from_ymd_opt(2024, 2, 29).unwrap();
98 let formatted = format_date(&date);
99 assert_eq!(formatted, "2024-02-29");
100 }
101
102 #[test]
103 fn test_format_datetime() {
104 let dt = Utc::now();
105 let formatted = format_datetime(&dt);
106
107 assert!(formatted.contains("UTC"));
109 assert!(formatted.contains("-"));
110 assert!(formatted.contains(" "));
111 assert!(formatted.contains(":"));
112
113 assert!(formatted.len() >= 20); }
116
117 #[test]
118 fn test_format_datetime_specific() {
119 let dt = DateTime::parse_from_rfc3339("2023-12-25T15:30:45Z")
121 .unwrap()
122 .with_timezone(&Utc);
123 let formatted = format_datetime(&dt);
124 assert_eq!(formatted, "2023-12-25 15:30:45 UTC");
125 }
126
127 #[test]
128 fn test_parse_date_valid() {
129 let result = parse_date("2023-12-25");
130 assert!(result.is_ok());
131 let date = result.unwrap();
132 assert_eq!(date.year(), 2023);
133 assert_eq!(date.month(), 12);
134 assert_eq!(date.day(), 25);
135 }
136
137 #[test]
138 fn test_parse_date_edge_cases() {
139 let result = parse_date("2024-01-01");
141 assert!(result.is_ok());
142 let date = result.unwrap();
143 assert_eq!(date.year(), 2024);
144 assert_eq!(date.month(), 1);
145 assert_eq!(date.day(), 1);
146
147 let result = parse_date("2023-12-31");
149 assert!(result.is_ok());
150 let date = result.unwrap();
151 assert_eq!(date.year(), 2023);
152 assert_eq!(date.month(), 12);
153 assert_eq!(date.day(), 31);
154
155 let result = parse_date("2024-02-29");
157 assert!(result.is_ok());
158 let date = result.unwrap();
159 assert_eq!(date.year(), 2024);
160 assert_eq!(date.month(), 2);
161 assert_eq!(date.day(), 29);
162 }
163
164 #[test]
165 fn test_parse_date_invalid() {
166 let result = parse_date("2023/12/25");
168 assert!(result.is_err());
169
170 let result = parse_date("2023-13-01");
172 assert!(result.is_err());
173
174 let result = parse_date("2023-02-30");
176 assert!(result.is_err());
177
178 let result = parse_date("");
180 assert!(result.is_err());
181
182 let result = parse_date("not-a-date");
184 assert!(result.is_err());
185 }
186
187 #[test]
188 fn test_is_valid_uuid_valid() {
189 assert!(is_valid_uuid("550e8400-e29b-41d4-a716-446655440000"));
191 assert!(is_valid_uuid("6ba7b810-9dad-11d1-80b4-00c04fd430c8"));
192 assert!(is_valid_uuid("6ba7b811-9dad-11d1-80b4-00c04fd430c8"));
193 assert!(is_valid_uuid("00000000-0000-0000-0000-000000000000"));
194 assert!(is_valid_uuid("ffffffff-ffff-ffff-ffff-ffffffffffff"));
195 }
196
197 #[test]
198 fn test_is_valid_uuid_invalid() {
199 assert!(!is_valid_uuid(""));
201 assert!(!is_valid_uuid("not-a-uuid"));
202 assert!(!is_valid_uuid("550e8400-e29b-41d4-a716"));
203 assert!(!is_valid_uuid("550e8400-e29b-41d4-a716-44665544000"));
204 assert!(!is_valid_uuid("550e8400-e29b-41d4-a716-4466554400000"));
205 assert!(!is_valid_uuid("550e8400-e29b-41d4-a716-44665544000g"));
206 assert!(!is_valid_uuid("550e8400-e29b-41d4-a716-44665544000-"));
207 assert!(!is_valid_uuid("550e8400-e29b-41d4-a716-44665544000 "));
208 }
209
210 #[test]
211 fn test_truncate_string_short() {
212 let result = truncate_string("hello", 10);
214 assert_eq!(result, "hello");
215
216 let result = truncate_string("hello", 5);
218 assert_eq!(result, "hello");
219 }
220
221 #[test]
222 fn test_truncate_string_long() {
223 let result = truncate_string("hello world", 8);
225 assert_eq!(result, "hello...");
226
227 let result = truncate_string("this is a very long string", 10);
229 assert_eq!(result, "this is...");
230 }
231
232 #[test]
233 fn test_truncate_string_edge_cases() {
234 let result = truncate_string("hello", 0);
236 assert_eq!(result, "...");
237
238 let result = truncate_string("hello", 1);
240 assert_eq!(result, "...");
241
242 let result = truncate_string("hello", 2);
244 assert_eq!(result, "...");
245
246 let result = truncate_string("hello", 3);
248 assert_eq!(result, "...");
249
250 let result = truncate_string("hello", 4);
252 assert_eq!(result, "h...");
253
254 let result = truncate_string("hello", 5);
256 assert_eq!(result, "hello");
257 }
258
259 #[test]
260 fn test_truncate_string_empty() {
261 let result = truncate_string("", 10);
263 assert_eq!(result, "");
264
265 let result = truncate_string("", 0);
267 assert_eq!(result, "");
268 }
269
270 #[test]
271 fn test_truncate_string_unicode() {
272 let result = truncate_string("hello δΈη", 8);
274 assert_eq!(result, "hello...");
275
276 let result = truncate_string("hello π", 8);
278 assert_eq!(result, "hello...");
279 }
280
281 #[test]
282 fn test_truncate_string_very_long() {
283 let long_string = "a".repeat(1000);
285 let result = truncate_string(&long_string, 10);
286 assert_eq!(result, "aaaaaaa...");
287 assert_eq!(result.len(), 10);
288 }
289
290 #[test]
291 fn test_utils_integration() {
292 let date_str = "2023-12-25";
294 let parsed_date = parse_date(date_str).unwrap();
295 let formatted_date = format_date(&parsed_date);
296 assert_eq!(formatted_date, date_str);
297
298 let uuid = "550e8400-e29b-41d4-a716-446655440000";
300 assert!(is_valid_uuid(uuid));
301 let truncated = truncate_string(uuid, 20);
302 assert_eq!(truncated, "550e8400-e29b-41d...");
303 }
304
305 #[test]
306 fn test_get_default_database_path_consistency() {
307 let path1 = get_default_database_path();
309 let path2 = get_default_database_path();
310 assert_eq!(path1, path2);
311 }
312
313 #[test]
314 fn test_format_date_consistency() {
315 let date = NaiveDate::from_ymd_opt(2023, 12, 25).unwrap();
317 let formatted = format_date(&date);
318 let parsed = parse_date(&formatted).unwrap();
319 assert_eq!(date, parsed);
320 }
321}