+31
atproto/syntax/datetime.go
+31
atproto/syntax/datetime.go
···
4
4
"fmt"
5
5
"regexp"
6
6
"strings"
7
+
"time"
8
+
)
9
+
10
+
const (
11
+
// Prefered atproto Datetime string syntax, for use with [time.Format].
12
+
//
13
+
// Note that *parsing* syntax is more flexible.
14
+
AtprotoDatetimeLayout = "2006-01-02T15:04:05.999Z"
7
15
)
8
16
9
17
// Represents the a Datetime in string format, as would pass Lexicon syntax validation: the intersection of RFC-3339 and ISO-8601 syntax.
···
23
31
return "", fmt.Errorf("Datetime can't use '-00:00' for UTC timezone, must use '+00:00', per ISO-8601")
24
32
}
25
33
return Datetime(raw), nil
34
+
}
35
+
36
+
// Parses a string to a golang time.Time in a single step.
37
+
func ParseDatetimeTime(raw string) (time.Time, error) {
38
+
var zero time.Time
39
+
d, err := ParseDatetime(raw)
40
+
if err != nil {
41
+
return zero, err
42
+
}
43
+
return d.Time()
44
+
}
45
+
46
+
// Parses the Datetime string in to a golang time.Time.
47
+
//
48
+
// There are a small number of strings which will pass initial syntax validation but fail when actually parsing, so this function can return an error. Use [ParseDatetimeTime] to fully parse in a single function call.
49
+
func (d Datetime) Time() (time.Time, error) {
50
+
return time.Parse(time.RFC3339Nano, d.String())
51
+
}
52
+
53
+
// Creates a new valid Datetime string matching the current time, in prefered syntax.
54
+
func DatetimeNow() Datetime {
55
+
t := time.Now().UTC()
56
+
return Datetime(t.Format(AtprotoDatetimeLayout))
26
57
}
27
58
28
59
func (d Datetime) String() string {
+31
-3
atproto/syntax/datetime_test.go
+31
-3
atproto/syntax/datetime_test.go
···
9
9
"github.com/stretchr/testify/assert"
10
10
)
11
11
12
-
func TestInteropDatetimesValid(t *testing.T) {
12
+
func TestInteropDatetimeValid(t *testing.T) {
13
13
assert := assert.New(t)
14
14
file, err := os.Open("testdata/datetime_syntax_valid.txt")
15
15
assert.NoError(err)
···
20
20
if len(line) == 0 || line[0] == '#' {
21
21
continue
22
22
}
23
-
_, err := ParseDatetime(line)
23
+
_, err := ParseDatetimeTime(line)
24
24
if err != nil {
25
25
fmt.Println("GOOD: " + line)
26
26
}
···
29
29
assert.NoError(scanner.Err())
30
30
}
31
31
32
-
func TestInteropDatetimesInvalid(t *testing.T) {
32
+
func TestInteropDatetimeInvalid(t *testing.T) {
33
33
assert := assert.New(t)
34
34
file, err := os.Open("testdata/datetime_syntax_invalid.txt")
35
35
assert.NoError(err)
···
48
48
}
49
49
assert.NoError(scanner.Err())
50
50
}
51
+
52
+
func TestInteropDatetimeTimeInvalid(t *testing.T) {
53
+
assert := assert.New(t)
54
+
file, err := os.Open("testdata/datetime_parse_invalid.txt")
55
+
assert.NoError(err)
56
+
defer file.Close()
57
+
scanner := bufio.NewScanner(file)
58
+
for scanner.Scan() {
59
+
line := scanner.Text()
60
+
if len(line) == 0 || line[0] == '#' {
61
+
continue
62
+
}
63
+
_, err := ParseDatetimeTime(line)
64
+
if err == nil {
65
+
fmt.Println("BAD: " + line)
66
+
}
67
+
assert.Error(err)
68
+
}
69
+
assert.NoError(scanner.Err())
70
+
}
71
+
72
+
func TestInteropDatetimeNow(t *testing.T) {
73
+
assert := assert.New(t)
74
+
75
+
dt := DatetimeNow()
76
+
_, err := ParseDatetimeTime(dt.String())
77
+
assert.NoError(err)
78
+
}
+7
atproto/syntax/testdata/datetime_parse_invalid.txt
+7
atproto/syntax/testdata/datetime_parse_invalid.txt
-8
atproto/syntax/testdata/datetime_syntax_invalid.txt
-8
atproto/syntax/testdata/datetime_syntax_invalid.txt
···
16
16
1985-04-12T23:20:50.123Z
17
17
1985-04-12T 23:20:50.123Z
18
18
19
-
# TODO: full parse to validate?
20
-
#1985-00-12T23:20:50.123Z
21
-
#1985-04-00T23:20:50.123Z
22
-
#1985-13-12T23:20:50.123Z
23
-
#1985-04-12T25:20:50.123Z
24
-
#1985-04-12T23:61:50.123Z
25
-
#1985-04-12T23:20:61.123Z
26
-
27
19
# not enough zero padding
28
20
1985-4-12T23:20:50.123Z
29
21
1985-04-2T23:20:50.123Z
+14
atproto/syntax/testdata/datetime_syntax_valid.txt
+14
atproto/syntax/testdata/datetime_syntax_valid.txt
···
18
18
0985-04-12T23:20:50.123-07:00
19
19
1985-04-12T23:20:50.123-07:00
20
20
0123-01-01T00:00:00.000Z
21
+
22
+
# various precisions, up through at least 12 digits
23
+
1985-04-12T23:20:50.1Z
24
+
1985-04-12T23:20:50.12Z
25
+
1985-04-12T23:20:50.123Z
26
+
1985-04-12T23:20:50.1234Z
27
+
1985-04-12T23:20:50.12345Z
28
+
1985-04-12T23:20:50.123456Z
29
+
1985-04-12T23:20:50.1234567Z
30
+
1985-04-12T23:20:50.12345678Z
31
+
1985-04-12T23:20:50.123456789Z
32
+
1985-04-12T23:20:50.1234567890Z
33
+
1985-04-12T23:20:50.12345678901Z
34
+
1985-04-12T23:20:50.123456789012Z