1
+ use crate :: Time ;
2
+ use smallvec:: SmallVec ;
3
+ use std:: str:: FromStr ;
4
+
1
5
#[ derive( thiserror:: Error , Debug , Clone ) ]
2
6
#[ allow( missing_docs) ]
3
7
pub enum Error {
@@ -11,6 +15,61 @@ pub enum Error {
11
15
MissingCurrentTime ,
12
16
}
13
17
18
+ /// A container for just enough bytes to hold the largest-possible [`time`](Time) instance.
19
+ /// It's used in conjunction with
20
+ #[ derive( Default , Clone ) ]
21
+ pub struct TimeBuf {
22
+ buf : SmallVec < [ u8 ; Time :: MAX . size ( ) ] > ,
23
+ }
24
+
25
+ impl TimeBuf {
26
+ /// Represent this instance as standard string, serialized in a format compatible with
27
+ /// signature fields in Git commits, also known as anything parseable as [raw format](function::parse_header()).
28
+ pub fn as_str ( & self ) -> & str {
29
+ // SAFETY: We know that serialized times are pure ASCII, a subset of UTF-8.
30
+ // `buf` and `len` are written only by time-serialization code.
31
+ let time_bytes = self . buf . as_slice ( ) ;
32
+ #[ allow( unsafe_code) ]
33
+ unsafe {
34
+ std:: str:: from_utf8_unchecked ( time_bytes)
35
+ }
36
+ }
37
+
38
+ /// Clear the previous content.
39
+ pub fn clear ( & mut self ) {
40
+ self . buf . clear ( ) ;
41
+ }
42
+ }
43
+
44
+ impl std:: io:: Write for TimeBuf {
45
+ fn write ( & mut self , buf : & [ u8 ] ) -> std:: io:: Result < usize > {
46
+ self . buf . write ( buf)
47
+ }
48
+
49
+ fn flush ( & mut self ) -> std:: io:: Result < ( ) > {
50
+ self . buf . flush ( )
51
+ }
52
+ }
53
+
54
+ impl Time {
55
+ /// Serialize this instance into `buf`, exactly as it would appear in the header of a Git commit,
56
+ /// and return `buf` as `&str` for easy consumption.
57
+ pub fn to_str < ' a > ( & self , buf : & ' a mut TimeBuf ) -> & ' a str {
58
+ buf. clear ( ) ;
59
+ self . write_to ( buf)
60
+ . expect ( "write to memory of just the right size cannot fail" ) ;
61
+ buf. as_str ( )
62
+ }
63
+ }
64
+
65
+ impl FromStr for Time {
66
+ type Err = Error ;
67
+
68
+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
69
+ crate :: parse_header ( s) . ok_or_else ( || Error :: InvalidDateString { input : s. into ( ) } )
70
+ }
71
+ }
72
+
14
73
pub ( crate ) mod function {
15
74
use std:: { str:: FromStr , time:: SystemTime } ;
16
75
@@ -25,7 +84,67 @@ pub(crate) mod function {
25
84
SecondsSinceUnixEpoch , Time ,
26
85
} ;
27
86
28
- #[ allow( missing_docs) ]
87
+ /// Parse `input` as any time that Git can parse when inputting a date.
88
+ ///
89
+ /// ## Examples
90
+ ///
91
+ /// ### 1. SHORT Format
92
+ ///
93
+ /// * `2018-12-24`
94
+ /// * `1970-01-01`
95
+ /// * `1950-12-31`
96
+ /// * `2024-12-31`
97
+ ///
98
+ /// ### 2. RFC2822 Format
99
+ ///
100
+ /// * `Thu, 18 Aug 2022 12:45:06 +0800`
101
+ /// * `Mon Oct 27 10:30:00 2023 -0800`
102
+ ///
103
+ /// ### 3. GIT_RFC2822 Format
104
+ ///
105
+ /// * `Thu, 8 Aug 2022 12:45:06 +0800`
106
+ /// * `Mon Oct 27 10:30:00 2023 -0800` (Note the single-digit day)
107
+ ///
108
+ /// ### 4. ISO8601 Format
109
+ ///
110
+ /// * `2022-08-17 22:04:58 +0200`
111
+ /// * `1970-01-01 00:00:00 -0500`
112
+ ///
113
+ /// ### 5. ISO8601_STRICT Format
114
+ ///
115
+ /// * `2022-08-17T21:43:13+08:00`
116
+ ///
117
+ /// ### 6. UNIX Timestamp (Seconds Since Epoch)
118
+ ///
119
+ /// * `123456789`
120
+ /// * `0` (January 1, 1970 UTC)
121
+ /// * `-1000`
122
+ /// * `1700000000`
123
+ ///
124
+ /// ### 7. Commit Header Format
125
+ ///
126
+ /// * `1745582210 +0200`
127
+ /// * `1660874655 +0800`
128
+ /// * `-1660874655 +0800`
129
+ ///
130
+ /// See also the [`parse_header()`].
131
+ ///
132
+ /// ### 8. GITOXIDE Format
133
+ ///
134
+ /// * `Thu Sep 04 2022 10:45:06 -0400`
135
+ /// * `Mon Oct 27 2023 10:30:00 +0000`
136
+ ///
137
+ /// ### 9. DEFAULT Format
138
+ ///
139
+ /// * `Thu Sep 4 10:45:06 2022 -0400`
140
+ /// * `Mon Oct 27 10:30:00 2023 +0000`
141
+ ///
142
+ /// ### 10. Relative Dates (e.g., "2 minutes ago", "1 hour from now")
143
+ ///
144
+ /// These dates are parsed *relative to a `now` timestamp*. The examples depend entirely on the value of `now`.
145
+ /// If `now` is October 27, 2023 at 10:00:00 UTC:
146
+ /// * `2 minutes ago` (October 27, 2023 at 09:58:00 UTC)
147
+ /// * `3 hours ago` (October 27, 2023 at 07:00:00 UTC)
29
148
pub fn parse ( input : & str , now : Option < SystemTime > ) -> Result < Time , Error > {
30
149
// TODO: actual implementation, this is just to not constantly fail
31
150
if input == "1979-02-26 18:30:00" {
@@ -50,7 +169,7 @@ pub(crate) mod function {
50
169
} else if let Ok ( val) = SecondsSinceUnixEpoch :: from_str ( input) {
51
170
// Format::Unix
52
171
Time :: new ( val, 0 )
53
- } else if let Some ( val) = parse_raw ( input) {
172
+ } else if let Some ( val) = parse_header ( input) {
54
173
// Format::Raw
55
174
val
56
175
} else if let Some ( val) = relative:: parse ( input, now) . transpose ( ) ? {
@@ -60,8 +179,9 @@ pub(crate) mod function {
60
179
} )
61
180
}
62
181
63
- #[ allow( missing_docs) ]
64
- pub fn parse_raw ( input : & str ) -> Option < Time > {
182
+ /// Unlike [`parse()`] which handles all kinds of input, this function only parses the commit-header format
183
+ /// like `1745582210 +0200`.
184
+ pub fn parse_header ( input : & str ) -> Option < Time > {
65
185
let mut split = input. split_whitespace ( ) ;
66
186
let seconds: SecondsSinceUnixEpoch = split. next ( ) ?. parse ( ) . ok ( ) ?;
67
187
let offset = split. next ( ) ?;
0 commit comments