-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathdate.ss
More file actions
360 lines (321 loc) · 13.1 KB
/
date.ss
File metadata and controls
360 lines (321 loc) · 13.1 KB
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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
#lang scheme/base
(require "base.ss")
(require scheme/contract
(rename-in srfi/19
[make-date srfi-make-date]
[date->string srfi-date->string]
[string->date srfi-string->date]
[time-utc->date srfi-time-utc->date]
[time-tai->date srfi-time-tai->date])
(rename-in (date-in)
[leap-year? mzlib-leap-year?]
[date+ bzlib-date+])
(date-tz-in)
"debug.ss"
(except-in "time.ss" copy-date))
; Token geographical bias:
(current-tz "GB")
; Constructors/converters ------------------------
; Creates a date representing the specified figures,
; interpreted relative to the immediate time zone offset in the relevant time zone.
;
; Involves some jiggery pokery because DST can cause the time zone offset to
; differ between today and the specified date.
;
; natural natural natural natural natural natural [#:tz string] -> date
(define (make-date nano second minute hour day month year #:tz [tz (current-tz)])
; 1. Work out the current time zone offset for tz.
; 2. Create a temporary date using that offset
; this gets us to within one hour of the intended time.
; 3. Work out the correct offset at the intended time.
; 4. Rewrite the temporary date using the correct offset.
(let* ([offset0 (date-zone-offset (current-date/tz tz))]
[date0 (srfi-make-date nano second minute hour day month year offset0)]
[offset1 (tz-offset date0 tz)])
(srfi-make-date nano second minute hour day month year offset1)))
; Renders date to a string using the immediate time zone offset for the specified time zone.
;
; date string [#:tz string] -> string
(define (date->string date fmt #:tz [tz (current-tz)])
(srfi-date->string (normalize-date date #:tz tz) fmt))
; Renders date to a string using the immediate time zone offset for the specified time zone.
;
; date string [#:tz string] -> string
(define (string->date str fmt #:tz [tz (current-tz)])
; If the string doesn't specify a time zone,
; srfi-string->date creates a date using the current local TZ offset:
(let ([temp (srfi-string->date str fmt)])
; We throw away the time zone offset that we get from SRFI 19 and use our own instead:
(make-date (date-nanosecond temp)
(date-second temp)
(date-minute temp)
(date-hour temp)
; For some reason, SRFI 19's string->date creates a date struct with a default day, month and year of #t.
; To prevent problems with maths, we set any missing components to 0:
(number+any->number (date-day temp) 0)
(number+any->number (date-month temp) 0)
(number+any->number (date-year temp) 0)
#:tz tz)))
; time-utc [#:tz string] -> date
(define (time-utc->date time #:tz [tz (current-tz)])
(normalize-date (srfi-time-utc->date time 0) #:tz tz))
; time-tai [#:tz string] -> date
(define (time-tai->date time #:tz [tz (current-tz)])
(normalize-date (srfi-time-tai->date time 0) #:tz tz))
; date
; [#:nanosecond natural]
; [#:second natural]
; [#:minute natural]
; [#:hour natural]
; [#:day natural]
; [#:month natural]
; [#:year integer]
; [#:tz string]
; ->
; date
(define (copy-date date
#:nanosecond [nanosecond (date-nanosecond date)]
#:second [second (date-second date)]
#:minute [minute (date-minute date)]
#:hour [hour (date-hour date)]
#:day [day (date-day date)]
#:month [month (date-month date)]
#:year [year (date-year date)]
#:tz [tz (current-tz)])
(make-date nanosecond second minute hour day month year #:tz tz))
; Date arithmetic --------------------------------
; date integer [#:tz string] -> date
(define (date+seconds date seconds #:tz [tz (current-tz)])
(time-utc->date
(add-duration (date->time-utc date)
(make-time time-duration 0 seconds))
#:tz tz))
; date integer [#:tz string] -> date
(define (date+minutes date minutes #:tz [tz (current-tz)])
(date+seconds date (* minutes 60) #:tz tz))
; date integer [#:tz string] -> date
(define (date+hours date hours #:tz [tz (current-tz)])
(date+seconds date (* hours 60 60) #:tz tz))
; Adds/subtracts a number of days from the date, preserving the time-of-day in the immediate time zone.
;
; For example, 9am on Jan 1st (GMT) + 151 days = 9am on Jun 1st (BST).
;
; date integer [#:tz string] -> date
(define (date+days date days #:tz [tz (current-tz)])
(date->tz (bzlib-date+ (normalize-date date #:tz tz) days) tz))
; Adds/subtracts a number of weeks from the date, preserving the time-of-day in the immediate time zone.
;
; date integer [#:tz string] -> date
(define (date+weeks date weeks #:tz [tz (current-tz)])
(date+days date (* weeks 7) #:tz tz))
; Adds/subtracts a number of months from the date, preserving the day-of-the-month and
; the time-of-day in the immediate time zone.
;
; For example, 9am on Jan 1st (GMT) + 6 months = 9am on Jun 1st (BST).
;
; date integer [#:tz string] -> date
(define (date+months date months #:tz [tz (current-tz)])
(date+days date (months->days months (date-year date) (date-month date)) #:tz tz))
; Adds/subtracts a number of months from the date, preserving the day-of-the-month and
; the time-of-day in the immediate time zone.
;
; For example, 9am on Jan 1st (GMT) + 6 months = 9am on Jun 1st (BST).
;
; date integer [#:tz string] -> date
(define (date+years date years #:tz [tz (current-tz)])
(make-date (date-nanosecond date)
(date-second date)
(date-minute date)
(date-hour date)
(date-day date)
(date-month date)
(+ (date-year date) years)
#:tz tz))
; date [#:seconds integer] ... [#:tz string] -> date
(define (date+
date
#:seconds [seconds #f]
#:minutes [minutes #f]
#:hours [hours #f]
#:days [days #f]
#:weeks [weeks #f]
#:months [months #f]
#:years [years #f]
#:tz [tz (current-tz)])
(for/fold ([accum date])
([combinator (in-list (list date+seconds date+minutes date+hours date+days date+weeks date+months date+years))]
[amount (in-list (list seconds minutes hours days weeks months years))])
(if amount
(combinator date amount #:tz tz)
date)))
; Rewrites the supplied date using the immediate timezone offset for the specified time zone.
;
; For example, leaves 9am GMT on 1st December as it is, but rewrites 9am GMT on 1st June as 8am BST.
;
; date string -> date
(define (normalize-date date #:tz [tz (current-tz)])
(let* ([offset (tz-offset date tz)])
(if (= offset (date-zone-offset date))
date
(date->date/tz date offset))))
; Time difference --------------------------------
; Counts the number of day starts (midnights) between a and b.
; Returns a positive result if a > b, negative if b < a.
;
; date date -> integer
(define (date-days-difference a b)
(if (date>=? a b)
(+ (for/fold ([accum 0])
([year (in-range (date-year b) (date-year a))])
(+ accum (days-in-year year)))
(for/fold ([accum 0])
([month (in-range 1 (date-month a))])
(+ accum (days-in-month month (date-year a))))
(for/fold ([accum 0])
([month (in-range 1 (date-month b))])
(- accum (days-in-month month (date-year b))))
(- (date-day a)
(date-day b)))
(- (date-days-difference b a))))
; Counts the number of week starts (Sunday midnights) between a and b.
; Returns a positive result if a > b, negative if b < a.
;
; date date -> integer
(define (date-weeks-difference a b)
(if (date>=? a b)
(+ (if (>= (date-week-day a) (date-week-day b)) 0 1)
(quotient (date-days-difference a b) 7))
(- (date-weeks-difference b a))))
; Counts the number of month starts (midnights on the 1st of the month) between a and b.
; Returns a positive result if a > b, negative if b < a.
;
; date date -> integer
(define (date-months-difference a b)
(if (date>=? a b)
(+ (- (date-month a)
(date-month b))
(for/fold ([accum 0])
([year (in-range (date-year b) (date-year a))])
(+ accum 12)))
(- (date-months-difference b a))))
; Counts the number of year starts (midnights on Jan 1st) between a and b.
; Returns a positive result if a > b, negative if b < a.
;
; date date -> integer
(define (date-years-difference a b)
(if (date>=? a b)
(- (date-year a) (date-year b))
(- (date-years-difference b a))))
; Helpers ----------------------------------------
; Returns the number of days counting forward/backward *count* months from the first of *month* in *year*.
;
; integer natural natural [integer] -> integer
(define (months->days count year month [accum 0])
(cond [(zero? count) accum]
[(> count 0) (if (= month 12)
(months->days (sub1 count) (add1 year) 1 (+ accum (days-in-month month year)))
(months->days (sub1 count) year (add1 month) (+ accum (days-in-month month year))))]
[else (if (= month 1)
(months->days (add1 count) (sub1 year) 12 (- accum (days-in-month 12 (sub1 year))))
(months->days (add1 count) year (sub1 month) (- accum (days-in-month (sub1 month) year))))]))
; (U number any) [number]-> number
(define (number+any->number val [default 0])
(if (number? val)
val
default))
; Provides ---------------------------------------
(provide/contract
[make-date (->* (integer? integer? integer? integer? integer? integer? integer?)
(#:tz zone-exists?)
date?)]
[copy-date (->* (date?)
(#:nanosecond natural-number/c
#:second natural-number/c
#:minute natural-number/c
#:hour natural-number/c
#:day natural-number/c
#:month natural-number/c
#:year integer?
#:tz string?)
date?)]
[date->string (->* (date? string?) (#:tz zone-exists?) string?)]
[string->date (->* (string? string?) (#:tz zone-exists?) date?)]
[time-utc->date (->* (time-utc?) (#:tz zone-exists?) date?)]
[time-tai->date (->* (time-tai?) (#:tz zone-exists?) date?)]
[date+seconds (->* (date? integer?) (#:tz zone-exists?) date?)]
[date+minutes (->* (date? integer?) (#:tz zone-exists?) date?)]
[date+hours (->* (date? integer?) (#:tz zone-exists?) date?)]
[date+days (->* (date? integer?) (#:tz zone-exists?) date?)]
[date+weeks (->* (date? integer?) (#:tz zone-exists?) date?)]
[date+months (->* (date? integer?) (#:tz zone-exists?) date?)]
[date+years (->* (date? integer?) (#:tz zone-exists?) date?)]
[date+ (->* (date?) (#:seconds natural-number/c
#:minutes natural-number/c
#:hours natural-number/c
#:days natural-number/c
#:weeks natural-number/c
#:months natural-number/c
#:years natural-number/c
#:tz zone-exists?) date?)]
[normalize-date (->* (date?) (#:tz zone-exists?) date?)]
[date-days-difference (-> date? date? integer?)]
[date-weeks-difference (-> date? date? integer?)]
[date-months-difference (-> date? date? integer?)]
[date-years-difference (-> date? date? integer?)])
(provide
; Constructors:
make-time
date->time-utc
date->time-tai
; Time types:
time-utc
time-tai
time-duration
; Predicates:
date?
date-week-day?
time?
time-utc?
time-tai?
time-duration?
zone-exists?
; Accessors:
current-time
current-date
time-type
time-second
time-nanosecond
date-year
date-month
date-day
date-hour
date-minute
date-second
date-nanosecond
date-zone-offset
date-week-day
date-week-day?
date-day-of-the-week
date-year-day
; Comparisons:
time<?
time>?
time<=?
time>=?
time=?
; Arithmetic:
add-duration
subtract-duration
time-difference
; Conversions:
date->time-utc
date->time-tai
; Utilities:
time->ago-string
current-tz
current-year
current-time-zone-offset
tz-names
leap-year?
days-in-year
days-in-month)