summaryrefslogtreecommitdiff
path: root/tests/test-calendar-sync--convert-tz-to-local.el
blob: cf45aa6104c01fcb59d003da12a861f98e70f611 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
;;; test-calendar-sync--convert-tz-to-local.el --- Tests for timezone conversion  -*- lexical-binding: t; -*-

;;; Commentary:
;; Unit tests for calendar-sync--convert-tz-to-local function.
;; Tests conversion from named timezones to local time.
;; Uses `date` command as reference implementation for verification.
;; Covers Normal, Boundary, and Error cases.

;;; Code:

(require 'ert)
(require 'calendar-sync)
(require 'testutil-calendar-sync)

;;; Normal Cases

(ert-deftest test-calendar-sync--convert-tz-to-local-normal-lisbon-to-local ()
  "Test converting Europe/Lisbon time to local.
Europe/Lisbon is UTC+0 in winter, UTC+1 in summer.
Uses date command as reference for expected result."
  (let* ((source-tz "Europe/Lisbon")
         (year 2026) (month 2) (day 2) (hour 19) (minute 0)
         ;; Get expected result from date command
         (expected (test-calendar-sync-convert-tz-via-date
                    year month day hour minute source-tz))
         (result (calendar-sync--convert-tz-to-local
                  year month day hour minute source-tz)))
    (should expected)  ; Sanity check that date command worked
    (should result)
    (should (equal expected result))))

(ert-deftest test-calendar-sync--convert-tz-to-local-normal-yerevan-to-local ()
  "Test converting Asia/Yerevan time to local.
Asia/Yerevan is UTC+4 year-round."
  (let* ((source-tz "Asia/Yerevan")
         (year 2026) (month 2) (day 2) (hour 20) (minute 0)
         (expected (test-calendar-sync-convert-tz-via-date
                    year month day hour minute source-tz))
         (result (calendar-sync--convert-tz-to-local
                  year month day hour minute source-tz)))
    (should expected)
    (should result)
    (should (equal expected result))))

(ert-deftest test-calendar-sync--convert-tz-to-local-normal-utc-to-local ()
  "Test converting UTC time to local."
  (let* ((source-tz "UTC")
         (year 2026) (month 2) (day 2) (hour 19) (minute 0)
         (expected (test-calendar-sync-convert-tz-via-date
                    year month day hour minute source-tz))
         (result (calendar-sync--convert-tz-to-local
                  year month day hour minute source-tz)))
    (should expected)
    (should result)
    (should (equal expected result))))

(ert-deftest test-calendar-sync--convert-tz-to-local-normal-new-york-to-local ()
  "Test converting America/New_York time to local."
  (let* ((source-tz "America/New_York")
         (year 2026) (month 2) (day 2) (hour 14) (minute 30)
         (expected (test-calendar-sync-convert-tz-via-date
                    year month day hour minute source-tz))
         (result (calendar-sync--convert-tz-to-local
                  year month day hour minute source-tz)))
    (should expected)
    (should result)
    (should (equal expected result))))

(ert-deftest test-calendar-sync--convert-tz-to-local-normal-tokyo-to-local ()
  "Test converting Asia/Tokyo time to local.
Asia/Tokyo is UTC+9 year-round (no DST)."
  (let* ((source-tz "Asia/Tokyo")
         (year 2026) (month 2) (day 2) (hour 10) (minute 0)
         (expected (test-calendar-sync-convert-tz-via-date
                    year month day hour minute source-tz))
         (result (calendar-sync--convert-tz-to-local
                  year month day hour minute source-tz)))
    (should expected)
    (should result)
    (should (equal expected result))))

;;; Boundary Cases

(ert-deftest test-calendar-sync--convert-tz-to-local-boundary-crosses-date-forward ()
  "Test conversion that crosses to next day.
Late evening in Europe becomes next day morning in Americas."
  (let* ((source-tz "Europe/London")
         (year 2026) (month 2) (day 2) (hour 23) (minute 30)
         (expected (test-calendar-sync-convert-tz-via-date
                    year month day hour minute source-tz))
         (result (calendar-sync--convert-tz-to-local
                  year month day hour minute source-tz)))
    (should expected)
    (should result)
    (should (equal expected result))))

(ert-deftest test-calendar-sync--convert-tz-to-local-boundary-crosses-date-backward ()
  "Test conversion that crosses to previous day.
Early morning in Asia becomes previous day evening in Americas."
  (let* ((source-tz "Asia/Tokyo")
         (year 2026) (month 2) (day 3) (hour 2) (minute 0)
         (expected (test-calendar-sync-convert-tz-via-date
                    year month day hour minute source-tz))
         (result (calendar-sync--convert-tz-to-local
                  year month day hour minute source-tz)))
    (should expected)
    (should result)
    (should (equal expected result))))

(ert-deftest test-calendar-sync--convert-tz-to-local-boundary-midnight ()
  "Test conversion of midnight (00:00) in source timezone."
  (let* ((source-tz "Europe/Paris")
         (year 2026) (month 2) (day 2) (hour 0) (minute 0)
         (expected (test-calendar-sync-convert-tz-via-date
                    year month day hour minute source-tz))
         (result (calendar-sync--convert-tz-to-local
                  year month day hour minute source-tz)))
    (should expected)
    (should result)
    (should (equal expected result))))

(ert-deftest test-calendar-sync--convert-tz-to-local-boundary-2359 ()
  "Test conversion of 23:59 in source timezone."
  (let* ((source-tz "Europe/Berlin")
         (year 2026) (month 2) (day 2) (hour 23) (minute 59)
         (expected (test-calendar-sync-convert-tz-via-date
                    year month day hour minute source-tz))
         (result (calendar-sync--convert-tz-to-local
                  year month day hour minute source-tz)))
    (should expected)
    (should result)
    (should (equal expected result))))

(ert-deftest test-calendar-sync--convert-tz-to-local-boundary-dst-spring-forward ()
  "Test conversion during US DST spring-forward transition.
March 8, 2026 at 2:30 AM doesn't exist in America/Chicago (skipped)."
  ;; Use a time AFTER the transition to avoid the gap
  (let* ((source-tz "America/New_York")
         (year 2026) (month 3) (day 8) (hour 15) (minute 0)
         (expected (test-calendar-sync-convert-tz-via-date
                    year month day hour minute source-tz))
         (result (calendar-sync--convert-tz-to-local
                  year month day hour minute source-tz)))
    (should expected)
    (should result)
    (should (equal expected result))))

(ert-deftest test-calendar-sync--convert-tz-to-local-boundary-dst-fall-back ()
  "Test conversion during fall-back DST transition.
November 1, 2026 at 1:30 AM exists twice in America/Chicago."
  (let* ((source-tz "America/New_York")
         (year 2026) (month 11) (day 1) (hour 14) (minute 0)
         (expected (test-calendar-sync-convert-tz-via-date
                    year month day hour minute source-tz))
         (result (calendar-sync--convert-tz-to-local
                  year month day hour minute source-tz)))
    (should expected)
    (should result)
    (should (equal expected result))))

(ert-deftest test-calendar-sync--convert-tz-to-local-boundary-etc-gmt-plus ()
  "Test conversion from Etc/GMT+5 (note: Etc/GMT+N is UTC-N)."
  (let* ((source-tz "Etc/GMT+5")
         (year 2026) (month 2) (day 2) (hour 12) (minute 0)
         (expected (test-calendar-sync-convert-tz-via-date
                    year month day hour minute source-tz))
         (result (calendar-sync--convert-tz-to-local
                  year month day hour minute source-tz)))
    (should expected)
    (should result)
    (should (equal expected result))))

(ert-deftest test-calendar-sync--convert-tz-to-local-boundary-month-boundary ()
  "Test conversion that crosses month boundary."
  (let* ((source-tz "Pacific/Auckland")  ; UTC+12/+13
         (year 2026) (month 2) (day 1) (hour 5) (minute 0)
         (expected (test-calendar-sync-convert-tz-via-date
                    year month day hour minute source-tz))
         (result (calendar-sync--convert-tz-to-local
                  year month day hour minute source-tz)))
    (should expected)
    (should result)
    (should (equal expected result))))

(ert-deftest test-calendar-sync--convert-tz-to-local-boundary-year-boundary ()
  "Test conversion that crosses year boundary."
  (let* ((source-tz "Pacific/Auckland")
         (year 2026) (month 1) (day 1) (hour 5) (minute 0)
         (expected (test-calendar-sync-convert-tz-via-date
                    year month day hour minute source-tz))
         (result (calendar-sync--convert-tz-to-local
                  year month day hour minute source-tz)))
    (should expected)
    (should result)
    (should (equal expected result))))

;;; Error Cases

(ert-deftest test-calendar-sync--convert-tz-to-local-boundary-invalid-timezone-falls-back ()
  "Test that invalid timezone falls back to treating time as local.
The `date` command doesn't error on unrecognized timezones - it ignores
the TZ specification and treats the input as local time. This is acceptable
because calendar providers (Google, Proton) always use valid IANA timezones.
This test documents the fallback behavior rather than testing for nil."
  (let ((result (calendar-sync--convert-tz-to-local
                 2026 2 2 19 0 "Invalid/Timezone")))
    ;; Should return something (falls back to local interpretation)
    (should result)
    ;; The time values should be present (year month day hour minute)
    (should (= 5 (length result)))))

(ert-deftest test-calendar-sync--convert-tz-to-local-error-nil-timezone ()
  "Test that nil timezone returns nil."
  (let ((result (calendar-sync--convert-tz-to-local
                 2026 2 2 19 0 nil)))
    (should (null result))))

(ert-deftest test-calendar-sync--convert-tz-to-local-error-empty-timezone ()
  "Test that empty timezone string returns nil."
  (let ((result (calendar-sync--convert-tz-to-local
                 2026 2 2 19 0 "")))
    (should (null result))))

(provide 'test-calendar-sync--convert-tz-to-local)
;;; test-calendar-sync--convert-tz-to-local.el ends here