]> gitweb.factorcode.org Git - factor.git/blob - basis/calendar/calendar.factor
factor: Rename GENERIC# to GENERIC#:.
[factor.git] / basis / calendar / calendar.factor
1 ! Copyright (C) 2007 Doug Coleman.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors arrays classes.tuple combinators
4 combinators.short-circuit kernel locals math math.functions
5 math.intervals math.order sequences summary system vocabs vocabs.loader
6 assocs ;
7 IN: calendar
8
9 ERROR: not-in-interval value interval ;
10
11 : check-interval ( value interval -- value )
12     2dup interval-contains? [ drop ] [ not-in-interval ] if ;
13
14 HOOK: gmt-offset os ( -- hours minutes seconds )
15
16 HOOK: gmt os ( -- timestamp )
17
18 TUPLE: duration
19     { year real }
20     { month real }
21     { day real }
22     { hour real }
23     { minute real }
24     { second real } ;
25
26 C: <duration> duration
27
28 : instant ( -- duration ) 0 0 0 0 0 0 <duration> ;
29
30 TUPLE: timestamp
31     { year integer }
32     { month integer }
33     { day integer }
34     { hour integer }
35     { minute integer }
36     { second real }
37     { gmt-offset duration } ;
38
39 CONSTANT: day-counts { 0 31 28 31 30 31 30 31 31 30 31 30 31 }
40
41 GENERIC: leap-year? ( obj -- ? )
42
43 M: integer leap-year? ( year -- ? )
44     dup 100 divisor? 400 4 ? divisor? ;
45
46 M: timestamp leap-year? ( timestamp -- ? )
47     year>> leap-year? ;
48
49 : (days-in-month) ( year month -- n )
50     dup 2 = [ drop leap-year? 29 28 ? ] [ nip day-counts nth ] if ;
51
52 :: <timestamp> ( year month day hour minute second gmt-offset -- timestamp )
53     year
54     month 1 12 [a,b] check-interval
55     day 1 year month (days-in-month) [a,b] check-interval
56     hour 0 23 [a,b] check-interval
57     minute 0 59 [a,b] check-interval
58     second 0 60 [a,b) check-interval
59     gmt-offset timestamp boa ;
60
61 M: timestamp clone (clone) [ clone ] change-gmt-offset ;
62
63 : gmt-offset-duration ( -- duration )
64     0 0 0 gmt-offset <duration> ; inline
65
66 : <date> ( year month day -- timestamp )
67     0 0 0 gmt-offset-duration <timestamp> ; inline
68
69 : <date-gmt> ( year month day -- timestamp )
70     0 0 0 instant <timestamp> ; inline
71
72 : <year> ( year -- timestamp )
73     1 1 <date> ; inline
74
75 : <year-gmt> ( year -- timestamp )
76     1 1 <date-gmt> ; inline
77
78 CONSTANT: average-month 30+5/12
79 CONSTANT: months-per-year 12
80 CONSTANT: days-per-year 3652425/10000
81 CONSTANT: hours-per-year 876582/100
82 CONSTANT: minutes-per-year 5259492/10
83 CONSTANT: seconds-per-year 31556952
84
85 :: julian-day-number ( year month day -- n )
86     ! Returns a composite date number
87     ! Not valid before year -4800
88     14 month - 12 /i :> a
89     year 4800 + a - :> y
90     month 12 a * + 3 - :> m
91
92     day 153 m * 2 + 5 /i + 365 y * +
93     y 4 /i + y 100 /i - y 400 /i + 32045 - ;
94
95 :: julian-day-number>date ( n -- year month day )
96     ! Inverse of julian-day-number
97     n 32044 + :> a
98     4 a * 3 + 146097 /i :> b
99     a 146097 b * 4 /i - :> c
100     4 c * 3 + 1461 /i :> d
101     c 1461 d * 4 /i - :> e
102     5 e * 2 + 153 /i :> m
103
104     100 b * d + 4800 -
105     m 10 /i + m 3 +
106     12 m 10 /i * -
107     e 153 m * 2 + 5 /i - 1 + ;
108
109 GENERIC: easter ( obj -- obj' )
110
111 :: easter-month-day ( year -- month day )
112     year 19 mod :> a
113     year 100 /mod :> ( b c )
114     b 4 /mod :> ( d e )
115     b 8 + 25 /i :> f
116     b f - 1 + 3 /i :> g
117     19 a * b + d - g - 15 + 30 mod :> h
118     c 4 /mod :> ( i k )
119     32 2 e * + 2 i * + h - k - 7 mod :> l
120     a 11 h * + 22 l * + 451 /i :> m
121
122     h l + 7 m * - 114 + 31 /mod 1 + ;
123
124 M: integer easter ( year -- timestamp )
125     dup easter-month-day <date> ;
126
127 M: timestamp easter ( timestamp -- timestamp )
128     clone
129     dup year>> easter-month-day
130     swapd >>day swap >>month ;
131
132 : >date< ( timestamp -- year month day )
133     [ year>> ] [ month>> ] [ day>> ] tri ;
134
135 : >time< ( timestamp -- hour minute second )
136     [ hour>> ] [ minute>> ] [ second>> ] tri ;
137
138 : years ( x -- duration ) instant swap >>year ;
139 : months ( x -- duration ) instant swap >>month ;
140 : days ( x -- duration ) instant swap >>day ;
141 : weeks ( x -- duration ) 7 * days ;
142 : hours ( x -- duration ) instant swap >>hour ;
143 : minutes ( x -- duration ) instant swap >>minute ;
144 : seconds ( x -- duration ) instant swap >>second ;
145 : milliseconds ( x -- duration ) 1000 / seconds ;
146 : microseconds ( x -- duration ) 1000000 / seconds ;
147 : nanoseconds ( x -- duration ) 1000000000 / seconds ;
148
149 <PRIVATE
150
151 GENERIC: +year ( timestamp x -- timestamp )
152 GENERIC: +month ( timestamp x -- timestamp )
153 GENERIC: +day ( timestamp x -- timestamp )
154 GENERIC: +hour ( timestamp x -- timestamp )
155 GENERIC: +minute ( timestamp x -- timestamp )
156 GENERIC: +second ( timestamp x -- timestamp )
157
158 : /rem ( f n -- q r )
159     ! q is positive or negative, r is positive from 0 <= r < n
160     [ / floor >integer ] 2keep rem ;
161
162 : float>whole-part ( float -- int float )
163     [ floor >integer ] keep over - ;
164
165 : adjust-leap-year ( timestamp -- timestamp )
166     dup
167     { [ day>> 29 = ] [ month>> 2 = ] [ leap-year? not ] } 1&&
168     [ 3 >>month 1 >>day ] when ;
169
170 M: integer +year ( timestamp n -- timestamp )
171     [ + ] curry change-year adjust-leap-year ;
172
173 M: real +year ( timestamp n -- timestamp )
174     [ float>whole-part swapd days-per-year * +day swap +year ] unless-zero ;
175
176 : months/years ( n -- months years )
177     12 /rem [ 1 - 12 ] when-zero swap ; inline
178
179 M: integer +month ( timestamp n -- timestamp )
180     [ over month>> + months/years [ >>month ] dip +year ] unless-zero ;
181
182 M: real +month ( timestamp n -- timestamp )
183     [ float>whole-part swapd average-month * +day swap +month ] unless-zero ;
184
185 M: integer +day ( timestamp n -- timestamp )
186     [
187         over >date< julian-day-number + julian-day-number>date
188         [ >>year ] [ >>month ] [ >>day ] tri*
189     ] unless-zero ;
190
191 M: real +day ( timestamp n -- timestamp )
192     [ float>whole-part swapd 24 * +hour swap +day ] unless-zero ;
193
194 : hours/days ( n -- hours days )
195     24 /rem swap ;
196
197 M: integer +hour ( timestamp n -- timestamp )
198     [ over hour>> + hours/days [ >>hour ] dip +day ] unless-zero ;
199
200 M: real +hour ( timestamp n -- timestamp )
201     float>whole-part swapd 60 * +minute swap +hour ;
202
203 : minutes/hours ( n -- minutes hours )
204     60 /rem swap ;
205
206 M: integer +minute ( timestamp n -- timestamp )
207     [ over minute>> + minutes/hours [ >>minute ] dip +hour ] unless-zero ;
208
209 M: real +minute ( timestamp n -- timestamp )
210     [ float>whole-part swapd 60 * +second swap +minute ] unless-zero ;
211
212 : seconds/minutes ( n -- seconds minutes )
213     60 /rem swap >integer ;
214
215 M: number +second ( timestamp n -- timestamp )
216     [ over second>> + seconds/minutes [ >>second ] dip +minute ] unless-zero ;
217
218 : (time+) ( timestamp duration -- timestamp' duration )
219     [ second>> +second ] keep
220     [ minute>> +minute ] keep
221     [ hour>>   +hour   ] keep
222     [ day>>    +day    ] keep
223     [ month>>  +month  ] keep
224     [ year>>   +year   ] keep ; inline
225
226 : +slots ( obj1 obj2 quot -- n obj1 obj2 )
227     [ bi@ + ] curry 2keep ; inline
228
229 PRIVATE>
230
231 GENERIC#: time+ 1 ( time1 time2 -- time3 )
232
233 M: timestamp time+
234     [ clone ] dip (time+) drop ;
235
236 M: duration time+
237     dup timestamp? [
238         swap time+
239     ] [
240         [ year>> ] +slots
241         [ month>> ] +slots
242         [ day>> ] +slots
243         [ hour>> ] +slots
244         [ minute>> ] +slots
245         [ second>> ] +slots
246         2drop <duration>
247     ] if ;
248
249 : duration>years ( duration -- x )
250     ! Uses average month/year length since duration loses calendar
251     ! data
252     0 swap
253     {
254         [ year>> + ]
255         [ month>> months-per-year / + ]
256         [ day>> days-per-year / + ]
257         [ hour>> hours-per-year / + ]
258         [ minute>> minutes-per-year / + ]
259         [ second>> seconds-per-year / + ]
260     } cleave ;
261
262 M: duration <=> [ duration>years ] compare ;
263
264 : duration>months ( duration -- x ) duration>years months-per-year * ;
265 : duration>days ( duration -- x ) duration>years days-per-year * ;
266 : duration>hours ( duration -- x ) duration>years hours-per-year * ;
267 : duration>minutes ( duration -- x ) duration>years minutes-per-year * ;
268 : duration>seconds ( duration -- x ) duration>years seconds-per-year * ;
269 : duration>milliseconds ( duration -- x ) duration>seconds 1000 * ;
270 : duration>microseconds ( duration -- x ) duration>seconds 1000000 * ;
271 : duration>nanoseconds ( duration -- x ) duration>seconds 1000000000 * ;
272
273 GENERIC: time- ( time1 time2 -- time3 )
274
275 : convert-timezone ( timestamp duration -- timestamp' )
276     over gmt-offset>> over = [ drop ] [
277         [ over gmt-offset>> time- time+ ] keep >>gmt-offset
278     ] if ;
279
280 : >local-time ( timestamp -- timestamp' )
281     clone gmt-offset-duration convert-timezone ;
282
283 : >gmt ( timestamp -- timestamp' )
284     clone dup gmt-offset>> dup instant =
285     [ drop ] [
286         [ neg +second 0 ] change-second
287         [ neg +minute 0 ] change-minute
288         [ neg +hour   0 ] change-hour
289         [ neg +day    0 ] change-day
290         [ neg +month  0 ] change-month
291         [ neg +year   0 ] change-year drop
292     ] if ;
293
294 M: timestamp <=> ( ts1 ts2 -- n )
295     [ >gmt tuple-slots ] compare ;
296
297 : same-day? ( ts1 ts2 -- ? )
298     [ >gmt >date< <date> ] same? ;
299
300 : (time-) ( timestamp timestamp -- n )
301     [ >gmt ] bi@
302     [ [ >date< julian-day-number ] bi@ - 86400 * ] 2keep
303     [ >time< [ [ 3600 * ] [ 60 * ] bi* ] dip + + ] bi@ - + ;
304
305 M: timestamp time-
306     ! Exact calendar-time difference
307     (time-) seconds ;
308
309 : time* ( obj1 obj2 -- obj3 )
310     dup real? [ swap ] when
311     dup real? [ * ] [
312         {
313             [   year>> * ]
314             [  month>> * ]
315             [    day>> * ]
316             [   hour>> * ]
317             [ minute>> * ]
318             [ second>> * ]
319         } 2cleave <duration>
320     ] if ;
321
322 : before ( duration -- -duration )
323     -1 time* ;
324
325 <PRIVATE
326
327 : -slots ( obj1 obj2 quot -- n obj1 obj2 )
328     [ bi@ - ] curry 2keep ; inline
329
330 PRIVATE>
331
332 M: duration time-
333     over timestamp? [
334         before time+
335     ] [
336         [ year>> ] -slots
337         [ month>> ] -slots
338         [ day>> ] -slots
339         [ hour>> ] -slots
340         [ minute>> ] -slots
341         [ second>> ] -slots
342         2drop <duration>
343     ] if ;
344
345 : unix-1970 ( -- timestamp )
346     1970 <year-gmt> ; inline
347
348 : millis>timestamp ( x -- timestamp )
349     [ unix-1970 ] dip 1000 / +second ;
350
351 : timestamp>millis ( timestamp -- n )
352     unix-1970 (time-) 1000 * >integer ;
353
354 : micros>timestamp ( x -- timestamp )
355     [ unix-1970 ] dip 1000000 / +second ;
356
357 : timestamp>micros ( timestamp -- n )
358     unix-1970 (time-) 1000000 * >integer ;
359
360 : now ( -- timestamp )
361     gmt gmt-offset-duration (time+) >>gmt-offset ;
362
363 : hence ( duration -- timestamp ) now swap time+ ;
364
365 : ago ( duration -- timestamp ) now swap time- ;
366
367 : zeller-congruence ( year month day -- n )
368     ! Zeller Congruence
369     ! http://web.textfiles.com/computers/formulas.txt
370     ! good for any date since October 15, 1582
371     [
372         dup 2 <= [ [ 1 - ] [ 12 + ] bi* ] when
373         [ dup [ 4 /i + ] [ 100 /i - ] [ 400 /i + ] tri ] dip
374         [ 1 + 3 * 5 /i + ] keep 2 * +
375     ] dip 1 + + 7 mod ;
376
377 GENERIC: days-in-year ( obj -- n )
378
379 M: integer days-in-year ( year -- n ) leap-year? 366 365 ? ;
380 M: timestamp days-in-year ( timestamp -- n ) year>> days-in-year ;
381
382 : days-in-month ( timestamp -- n )
383     >date< drop (days-in-month) ;
384
385 : day-of-week ( timestamp -- n )
386     >date< zeller-congruence ;
387
388 :: (day-of-year) ( year month day -- n )
389     day-counts month head-slice sum day +
390     year leap-year? [
391         year month day <date>
392         year 3 1 <date>
393         after=? [ 1 + ] when
394     ] when ;
395
396 : day-of-year ( timestamp -- n )
397     >date< (day-of-year) ;
398
399 : midnight ( timestamp -- new-timestamp )
400     clone 0 >>hour 0 >>minute 0 >>second ; inline
401
402 : noon ( timestamp -- new-timestamp )
403     midnight 12 >>hour ; inline
404
405 : today ( -- timestamp )
406     now midnight ; inline
407
408 : tomorrow ( -- timestamp )
409     1 days hence midnight ; inline
410
411 : yesterday ( -- timestamp )
412     1 days ago midnight ; inline
413
414 : beginning-of-month ( timestamp -- new-timestamp )
415     midnight 1 >>day ; inline
416
417 : end-of-month ( timestamp -- new-timestamp )
418     [ midnight ] [ days-in-month ] bi >>day ;
419
420 <PRIVATE
421
422 : day-offset ( timestamp m -- new-timestamp n )
423     over day-of-week - ; inline
424
425 : day-this-week ( timestamp n -- new-timestamp )
426     day-offset days time+ ;
427
428 :: nth-day-this-month ( timestamp n day -- new-timestamp )
429     timestamp beginning-of-month day day-this-week
430     dup timestamp [ month>> ] same? [ 1 weeks time+ ] unless
431     n 1 - [ weeks time+ ] unless-zero ;
432
433 : last-day-this-month ( timestamp day -- new-timestamp )
434     [ 1 months time+ 1 ] dip nth-day-this-month 1 weeks time- ;
435
436 PRIVATE>
437
438 GENERIC: january ( obj -- timestamp )
439 GENERIC: february ( obj -- timestamp )
440 GENERIC: march ( obj -- timestamp )
441 GENERIC: april ( obj -- timestamp )
442 GENERIC: may ( obj -- timestamp )
443 GENERIC: june ( obj -- timestamp )
444 GENERIC: july ( obj -- timestamp )
445 GENERIC: august ( obj -- timestamp )
446 GENERIC: september ( obj -- timestamp )
447 GENERIC: october ( obj -- timestamp )
448 GENERIC: november ( obj -- timestamp )
449 GENERIC: december ( obj -- timestamp )
450
451 M: integer january 1 1 <date> ;
452 M: integer february 2 1 <date> ;
453 M: integer march 3 1 <date> ;
454 M: integer april 4 1 <date> ;
455 M: integer may 5 1 <date> ;
456 M: integer june 6 1 <date> ;
457 M: integer july 7 1 <date> ;
458 M: integer august 8 1 <date> ;
459 M: integer september 9 1 <date> ;
460 M: integer october 10 1 <date> ;
461 M: integer november 11 1 <date> ;
462 M: integer december 12 1 <date> ;
463
464 M: timestamp january clone 1 >>month ;
465 M: timestamp february clone 2 >>month ;
466 M: timestamp march clone 3 >>month ;
467 M: timestamp april clone 4 >>month ;
468 M: timestamp may clone 5 >>month ;
469 M: timestamp june clone 6 >>month ;
470 M: timestamp july clone 7 >>month ;
471 M: timestamp august clone 8 >>month ;
472 M: timestamp september clone 9 >>month ;
473 M: timestamp october clone 10 >>month ;
474 M: timestamp november clone 11 >>month ;
475 M: timestamp december clone 12 >>month ;
476
477 : sunday ( timestamp -- new-timestamp ) 0 day-this-week ;
478 : monday ( timestamp -- new-timestamp ) 1 day-this-week ;
479 : tuesday ( timestamp -- new-timestamp ) 2 day-this-week ;
480 : wednesday ( timestamp -- new-timestamp ) 3 day-this-week ;
481 : thursday ( timestamp -- new-timestamp ) 4 day-this-week ;
482 : friday ( timestamp -- new-timestamp ) 5 day-this-week ;
483 : saturday ( timestamp -- new-timestamp ) 6 day-this-week ;
484
485 : sunday? ( timestamp -- ? ) day-of-week 0 = ;
486 : monday? ( timestamp -- ? ) day-of-week 1 = ;
487 : tuesday? ( timestamp -- ? ) day-of-week 2 = ;
488 : wednesday? ( timestamp -- ? ) day-of-week 3 = ;
489 : thursday? ( timestamp -- ? ) day-of-week 4 = ;
490 : friday? ( timestamp -- ? ) day-of-week 5 = ;
491 : saturday? ( timestamp -- ? ) day-of-week 6 = ;
492
493 : sunday-of-month ( timestamp n -- new-timestamp ) 0 nth-day-this-month ;
494 : monday-of-month ( timestamp n -- new-timestamp ) 1 nth-day-this-month ;
495 : tuesday-of-month ( timestamp n -- new-timestamp ) 2 nth-day-this-month ;
496 : wednesday-of-month ( timestamp n -- new-timestamp ) 3 nth-day-this-month ;
497 : thursday-of-month ( timestamp n -- new-timestamp ) 4 nth-day-this-month ;
498 : friday-of-month ( timestamp n -- new-timestamp ) 5 nth-day-this-month ;
499 : saturday-of-month ( timestamp n -- new-timestamp ) 6 nth-day-this-month ;
500
501 : last-sunday-of-month ( timestamp -- new-timestamp ) 0 last-day-this-month ;
502 : last-monday-of-month ( timestamp -- new-timestamp ) 1 last-day-this-month ;
503 : last-tuesday-of-month ( timestamp -- new-timestamp ) 2 last-day-this-month ;
504 : last-wednesday-of-month ( timestamp -- new-timestamp ) 3 last-day-this-month ;
505 : last-thursday-of-month ( timestamp -- new-timestamp ) 4 last-day-this-month ;
506 : last-friday-of-month ( timestamp -- new-timestamp ) 5 last-day-this-month ;
507 : last-saturday-of-month ( timestamp -- new-timestamp ) 6 last-day-this-month ;
508
509 : beginning-of-week ( timestamp -- new-timestamp )
510     midnight sunday ;
511
512 : o'clock ( timestamp n -- new-timestamp )
513     [ midnight ] dip >>hour ;
514
515 : am ( timestamp n -- new-timestamp )
516     0 12 [a,b] check-interval o'clock ;
517
518 : pm ( timestamp n -- new-timestamp )
519     0 12 [a,b] check-interval 12 + o'clock ;
520
521 GENERIC: beginning-of-year ( object -- new-timestamp )
522 M: timestamp beginning-of-year beginning-of-month 1 >>month ;
523 M: integer beginning-of-year <year> ;
524
525 GENERIC: end-of-year ( object -- new-timestamp )
526 M: timestamp end-of-year 12 >>month 31 >>day ;
527 M: integer end-of-year 12 31 <date> ;
528
529 : time-since-midnight ( timestamp -- duration )
530     dup midnight time- ; inline
531
532 : since-1970 ( duration -- timestamp )
533     unix-1970 time+ ; inline
534
535 : timestamp>unix-time ( timestamp -- seconds )
536     unix-1970 (time-) ; inline
537
538 : unix-time>timestamp ( seconds -- timestamp )
539     [ unix-1970 ] dip +second ; inline
540
541 {
542     { [ os unix? ] [ "calendar.unix" ] }
543     { [ os windows? ] [ "calendar.windows" ] }
544 } cond require
545
546 { "threads" "calendar" } "calendar.threads" require-when