]> gitweb.factorcode.org Git - factor.git/blob - basis/calendar/calendar.factor
calendar: Add some more words that should exist.
[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?
44     dup 100 divisor? 400 4 ? divisor? ;
45
46 M: timestamp leap-year?
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
125     dup easter-month-day <date> ;
126
127 M: timestamp easter
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
171     [ + ] curry change-year adjust-leap-year ;
172
173 M: real +year
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
180     [ over month>> + months/years [ >>month ] dip +year ] unless-zero ;
181
182 M: real +month
183     [ float>whole-part swapd average-month * +day swap +month ] unless-zero ;
184
185 M: integer +day
186     [
187         over >date< julian-day-number + julian-day-number>date
188         [ >>year ] [ >>month ] [ >>day ] tri*
189     ] unless-zero ;
190
191 M: real +day
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
198     [ over hour>> + hours/days [ >>hour ] dip +day ] unless-zero ;
199
200 M: real +hour
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
207     [ over minute>> + minutes/hours [ >>minute ] dip +hour ] unless-zero ;
208
209 M: real +minute
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
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 <=> [ >gmt tuple-slots ] compare ;
295
296 : same-day? ( ts1 ts2 -- ? )
297     [ >gmt >date< <date> ] same? ;
298
299 : (time-) ( timestamp timestamp -- n )
300     [ >gmt ] bi@
301     [ [ >date< julian-day-number ] bi@ - 86400 * ] 2keep
302     [ >time< [ [ 3600 * ] [ 60 * ] bi* ] dip + + ] bi@ - + ;
303
304 M: timestamp time-
305     ! Exact calendar-time difference
306     (time-) seconds ;
307
308 : time* ( obj1 obj2 -- obj3 )
309     dup real? [ swap ] when
310     dup real? [ * ] [
311         {
312             [   year>> * ]
313             [  month>> * ]
314             [    day>> * ]
315             [   hour>> * ]
316             [ minute>> * ]
317             [ second>> * ]
318         } 2cleave <duration>
319     ] if ;
320
321 : before ( duration -- -duration )
322     -1 time* ;
323
324 <PRIVATE
325
326 : -slots ( obj1 obj2 quot -- n obj1 obj2 )
327     [ bi@ - ] curry 2keep ; inline
328
329 PRIVATE>
330
331 M: duration time-
332     over timestamp? [
333         before time+
334     ] [
335         [ year>> ] -slots
336         [ month>> ] -slots
337         [ day>> ] -slots
338         [ hour>> ] -slots
339         [ minute>> ] -slots
340         [ second>> ] -slots
341         2drop <duration>
342     ] if ;
343
344 : unix-1970 ( -- timestamp )
345     1970 <year-gmt> ; inline
346
347 : millis>timestamp ( x -- timestamp )
348     [ unix-1970 ] dip 1000 / +second ;
349
350 : timestamp>millis ( timestamp -- n )
351     unix-1970 (time-) 1000 * >integer ;
352
353 : micros>timestamp ( x -- timestamp )
354     [ unix-1970 ] dip 1000000 / +second ;
355
356 : timestamp>micros ( timestamp -- n )
357     unix-1970 (time-) 1000000 * >integer ;
358
359 : now ( -- timestamp )
360     gmt gmt-offset-duration (time+) >>gmt-offset ;
361
362 : hence ( duration -- timestamp ) now swap time+ ;
363
364 : ago ( duration -- timestamp ) now swap time- ;
365
366 : zeller-congruence ( year month day -- n )
367     ! Zeller Congruence
368     ! http://web.textfiles.com/computers/formulas.txt
369     ! good for any date since October 15, 1582
370     [
371         dup 2 <= [ [ 1 - ] [ 12 + ] bi* ] when
372         [ dup [ 4 /i + ] [ 100 /i - ] [ 400 /i + ] tri ] dip
373         [ 1 + 3 * 5 /i + ] keep 2 * +
374     ] dip 1 + + 7 mod ;
375
376 GENERIC: days-in-year ( obj -- n )
377
378 M: integer days-in-year leap-year? 366 365 ? ;
379
380 M: timestamp days-in-year 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     0 >>hour 0 >>minute 0 >>second ; inline
401
402 : midnight ( timestamp -- new-timestamp )
403     clone midnight! ; inline
404
405 : noon ( timestamp -- new-timestamp )
406     midnight 12 >>hour ; inline
407
408 : today ( -- timestamp )
409     now midnight! ; inline
410
411 : tomorrow ( -- timestamp )
412     1 days hence midnight! ; inline
413
414 : yesterday ( -- timestamp )
415     1 days ago midnight! ; inline
416
417 GENERIC: beginning-of-day ( object -- new-timestamp )
418 M: timestamp beginning-of-day midnight ;
419
420 : end-of-day! ( timestamp -- timestamp )
421     23 >>hour 59 >>minute 59+999/1000 >>second ;
422
423 GENERIC: end-of-day ( object -- new-timestamp )
424 M: timestamp end-of-day clone end-of-day! ;
425
426 : beginning-of-month ( timestamp -- new-timestamp )
427     midnight 1 >>day ; inline
428
429 : end-of-month ( timestamp -- new-timestamp )
430     [ end-of-day ] [ days-in-month ] bi >>day ;
431
432 GENERIC: beginning-of-year ( object -- new-timestamp )
433 M: timestamp beginning-of-year beginning-of-month 1 >>month ;
434 M: integer beginning-of-year <year> ;
435
436 GENERIC: end-of-year ( object -- new-timestamp )
437 M: timestamp end-of-year end-of-day 12 >>month 31 >>day ;
438 M: integer end-of-year 12 31 <date> end-of-day! ;
439
440 <PRIVATE
441
442 : day-offset ( timestamp m -- new-timestamp n )
443     over day-of-week - ; inline
444
445 : day-this-week ( timestamp n -- new-timestamp )
446     day-offset days time+ ;
447
448 :: nth-day-this-month ( timestamp n day -- new-timestamp )
449     timestamp beginning-of-month day day-this-week
450     dup timestamp [ month>> ] same? [ 1 weeks time+ ] unless
451     n 1 - [ weeks time+ ] unless-zero ;
452
453 : last-day-this-month ( timestamp day -- new-timestamp )
454     [ 1 months time+ 1 ] dip nth-day-this-month 1 weeks time- ;
455
456 PRIVATE>
457
458 GENERIC: january ( obj -- timestamp )
459 GENERIC: february ( obj -- timestamp )
460 GENERIC: march ( obj -- timestamp )
461 GENERIC: april ( obj -- timestamp )
462 GENERIC: may ( obj -- timestamp )
463 GENERIC: june ( obj -- timestamp )
464 GENERIC: july ( obj -- timestamp )
465 GENERIC: august ( obj -- timestamp )
466 GENERIC: september ( obj -- timestamp )
467 GENERIC: october ( obj -- timestamp )
468 GENERIC: november ( obj -- timestamp )
469 GENERIC: december ( obj -- timestamp )
470
471 M: integer january 1 1 <date> ;
472 M: integer february 2 1 <date> ;
473 M: integer march 3 1 <date> ;
474 M: integer april 4 1 <date> ;
475 M: integer may 5 1 <date> ;
476 M: integer june 6 1 <date> ;
477 M: integer july 7 1 <date> ;
478 M: integer august 8 1 <date> ;
479 M: integer september 9 1 <date> ;
480 M: integer october 10 1 <date> ;
481 M: integer november 11 1 <date> ;
482 M: integer december 12 1 <date> ;
483
484 M: timestamp january clone 1 >>month ;
485 M: timestamp february clone 2 >>month ;
486 M: timestamp march clone 3 >>month ;
487 M: timestamp april clone 4 >>month ;
488 M: timestamp may clone 5 >>month ;
489 M: timestamp june clone 6 >>month ;
490 M: timestamp july clone 7 >>month ;
491 M: timestamp august clone 8 >>month ;
492 M: timestamp september clone 9 >>month ;
493 M: timestamp october clone 10 >>month ;
494 M: timestamp november clone 11 >>month ;
495 M: timestamp december clone 12 >>month ;
496
497 : <january> ( year day -- timestamp ) 1 swap <date> ; inline
498 : <february> ( year day -- timestamp ) 2 swap <date> ; inline
499 : <march> ( year day -- timestamp ) 3 swap <date> ; inline
500 : <april> ( year day -- timestamp ) 4 swap <date> ; inline
501 : <may> ( year day -- timestamp ) 5 swap <date> ; inline
502 : <june> ( year day -- timestamp ) 6 swap <date> ; inline
503 : <july> ( year day -- timestamp ) 7 swap <date> ; inline
504 : <august> ( year day -- timestamp ) 8 swap <date> ; inline
505 : <september> ( year day -- timestamp ) 9 swap <date> ; inline
506 : <october> ( year day -- timestamp ) 10 swap <date> ; inline
507 : <november> ( year day -- timestamp ) 11 swap <date> ; inline
508 : <december> ( year day -- timestamp ) 12 swap <date> ; inline
509
510 : sunday ( timestamp -- new-timestamp ) 0 day-this-week ;
511 : monday ( timestamp -- new-timestamp ) 1 day-this-week ;
512 : tuesday ( timestamp -- new-timestamp ) 2 day-this-week ;
513 : wednesday ( timestamp -- new-timestamp ) 3 day-this-week ;
514 : thursday ( timestamp -- new-timestamp ) 4 day-this-week ;
515 : friday ( timestamp -- new-timestamp ) 5 day-this-week ;
516 : saturday ( timestamp -- new-timestamp ) 6 day-this-week ;
517
518 : sunday? ( timestamp -- ? ) day-of-week 0 = ;
519 : monday? ( timestamp -- ? ) day-of-week 1 = ;
520 : tuesday? ( timestamp -- ? ) day-of-week 2 = ;
521 : wednesday? ( timestamp -- ? ) day-of-week 3 = ;
522 : thursday? ( timestamp -- ? ) day-of-week 4 = ;
523 : friday? ( timestamp -- ? ) day-of-week 5 = ;
524 : saturday? ( timestamp -- ? ) day-of-week 6 = ;
525
526 : sunday-of-month ( timestamp n -- new-timestamp ) 0 nth-day-this-month ;
527 : monday-of-month ( timestamp n -- new-timestamp ) 1 nth-day-this-month ;
528 : tuesday-of-month ( timestamp n -- new-timestamp ) 2 nth-day-this-month ;
529 : wednesday-of-month ( timestamp n -- new-timestamp ) 3 nth-day-this-month ;
530 : thursday-of-month ( timestamp n -- new-timestamp ) 4 nth-day-this-month ;
531 : friday-of-month ( timestamp n -- new-timestamp ) 5 nth-day-this-month ;
532 : saturday-of-month ( timestamp n -- new-timestamp ) 6 nth-day-this-month ;
533
534 : last-sunday-of-month ( timestamp -- new-timestamp ) 0 last-day-this-month ;
535 : last-monday-of-month ( timestamp -- new-timestamp ) 1 last-day-this-month ;
536 : last-tuesday-of-month ( timestamp -- new-timestamp ) 2 last-day-this-month ;
537 : last-wednesday-of-month ( timestamp -- new-timestamp ) 3 last-day-this-month ;
538 : last-thursday-of-month ( timestamp -- new-timestamp ) 4 last-day-this-month ;
539 : last-friday-of-month ( timestamp -- new-timestamp ) 5 last-day-this-month ;
540 : last-saturday-of-month ( timestamp -- new-timestamp ) 6 last-day-this-month ;
541
542 : beginning-of-week ( timestamp -- new-timestamp )
543     midnight sunday ;
544
545 : o'clock ( timestamp n -- new-timestamp )
546     [ midnight ] dip >>hour ;
547
548 : am ( timestamp n -- new-timestamp )
549     0 12 [a,b] check-interval o'clock ;
550
551 : pm ( timestamp n -- new-timestamp )
552     0 12 [a,b] check-interval 12 + o'clock ;
553
554 : time-since-midnight ( timestamp -- duration )
555     dup midnight time- ; inline
556
557 : since-1970 ( duration -- timestamp )
558     unix-1970 time+ ; inline
559
560 : timestamp>unix-time ( timestamp -- seconds )
561     unix-1970 (time-) ; inline
562
563 : unix-time>timestamp ( seconds -- timestamp )
564     [ unix-1970 ] dip +second ; inline
565
566 : (week-number) ( timestamp -- [0,53] )
567     [ day-of-year ] [ day-of-week [ 7 ] when-zero ] bi - 10 + 7 /i ;
568
569 : week-number ( timestamp -- [1,53] )
570     dup (week-number) {
571         {  0 [ year>> 1 - end-of-year (week-number) ] }
572         { 53 [ year>> 1 + <year> (week-number) 1 = 1 53 ? ] }
573         [ nip ]
574     } case ;
575
576 : quarter ( timestamp -- [1,4] )
577     month>> 3 /mod [ drop 1 + ] unless-zero ; inline
578
579 GENERIC: weeks-in-week-year ( obj -- n )
580 M: integer weeks-in-week-year
581     { [ 1 1 <date> thursday? ] [ 12 31 <date> thursday? ] } 1|| 53 52 ? ;
582
583 M: timestamp weeks-in-week-year
584     { [ january 1 >>day thursday? ] [ december 31 >>day thursday? ] } 1|| 53 52 ? ;
585
586 {
587     { [ os unix? ] [ "calendar.unix" ] }
588     { [ os windows? ] [ "calendar.windows" ] }
589 } cond require
590
591 { "threads" "calendar" } "calendar.threads" require-when