Tests/OrgCoreTests/ReadDateTests.swift
79 lines · 4049 bytes
1import Foundation
2import Testing
3@testable import OrgCore
4
5/// Emacs Lisp that runs `body` with the clock stopped at `seconds` (UTC in the oracle).
6func frozenClock(_ seconds: Int, _ body: String) -> String {
7 """
8 (cl-letf* ((frozen (seconds-to-time \(seconds)))
9 (odt (symbol-function 'decode-time))
10 (oft (symbol-function 'format-time-string))
11 (otl (symbol-function 'time-less-p))
12 (ots (symbol-function 'time-subtract))
13 (ota (symbol-function 'time-add))
14 (oflt (symbol-function 'float-time))
15 ((symbol-function 'current-time) (lambda () frozen))
16 ((symbol-function 'org-today) (lambda () (time-to-days frozen)))
17 ((symbol-function 'time-less-p) (lambda (a b) (funcall otl (or a frozen) (or b frozen))))
18 ((symbol-function 'time-subtract) (lambda (a b) (funcall ots (or a frozen) (or b frozen))))
19 ((symbol-function 'time-add) (lambda (a b) (funcall ota (or a frozen) (or b frozen))))
20 ((symbol-function 'float-time) (lambda (&optional time) (funcall oflt (or time frozen))))
21 ((symbol-function 'decode-time) (lambda (&optional time zone form) (funcall odt (or time frozen) zone form)))
22 ((symbol-function 'format-time-string) (lambda (f &optional time zone) (funcall oft f (or time frozen) zone))))
23 \(body))
24 """
25}
26
27struct ReadDateTests {
28 /// Thu 2026-09-10 00:26 UTC.
29 static let now = Date(timeIntervalSince1970: 1_789_000_000)
30 static var utc: Calendar {
31 var calendar = Calendar(identifier: .gregorian)
32 calendar.timeZone = TimeZone(identifier: "UTC")!
33 return calendar
34 }
35
36 static func format(_ answer: ReadDate.Answer) -> String {
37 let c = answer.components(utc)
38 let date = String(format: "%04d-%02d-%02d", c.year!, c.month!, c.day!)
39 return (answer.timeGiven ? date + String(format: " %02d:%02d", c.hour!, c.minute!) : date) + "|" + (answer.endTime ?? "nil")
40 }
41
42 static let inputs = [
43 "", ".", "+0", "+1", "+2d", "-3d", "+1w", "+2m", "-1y", "++3", "fri", "+fri", "-fri", "+2fri", "mon",
44 "2026-10-07", "2026-10-7", "10-7", "7.10.", "7.10.2027", "10/7", "10/7/27", "12/31",
45 "3", "15", "sep 15", "15 sep", "jan", "dec 25", "2027",
46 "10:00", "9:30", "10am", "3pm", "12am", "12:30pm", "10h", "10h30", "h45",
47 "10:00-11:30", "10:00+1:30", "fri 10:00", "2026-10-07 14:15", "+2d 9:00", "+3h",
48 "w42", "2027-w01", "w3-5", "tomorrow", "nonsense",
49 ]
50
51 @Test func examples() {
52 let answer = ReadDate.analyze("fri 10:00", default: Self.now, now: Self.now, calendar: Self.utc)
53 #expect(Self.format(answer) == "2026-09-11 10:00|nil")
54 #expect(Self.format(ReadDate.analyze("10:00-11:30", default: Self.now, now: Self.now, calendar: Self.utc)) == "2026-09-10 10:00|11:30")
55 }
56
57 @Test(.enabled(if: ProcessInfo.processInfo.environment["ORGSTAR_SKIP_ORACLE"] == nil))
58 func readDateMatchesEmacs() throws {
59 try #require(EmacsOracle.isAvailable)
60 let cases = Self.inputs.map { input in
61 EmacsOracle.Case(text: "", point: 1, form: frozenClock(1_789_000_000, """
62 (eval '(let ((org-time-was-given nil) (org-end-time-was-given nil))
63 (let ((d (org-read-date nil nil "\(input)")))
64 (insert d "|" (or org-end-time-was-given "nil"))))
65 nil)
66 """))
67 }
68 var mismatches = 0
69 for (input, emacs) in zip(Self.inputs, try EmacsOracle.run(cases)) {
70 let ours = Self.format(ReadDate.analyze(input, default: Self.now, now: Self.now, calendar: Self.utc))
71 let theirs = emacs.error.isEmpty ? emacs.text : "error: \(emacs.error)"
72 if ours != theirs {
73 mismatches += 1
74 Issue.record("\(input.debugDescription): ours \(ours), emacs \(theirs)")
75 }
76 }
77 #expect(mismatches == 0)
78 }
79}