\ calendar 05.2.16 NAB
\ ANSI Forth Gregorian & ISO
\ date routines.  Algorithms are
\ from 'Calendrical Calculations',
\ N. Dershowitz & E.M. Reingold, 1997,
\ ISBN 0-521-56474-3.
\ Note: dates before year 1 are not
\ currently supported by this
\ implementation.

needs dblmath

decimal

: date>day-of-week  7 dmod ;

: kday ( d1 k o -- d2)
\ Compute kday on-or-before d1+o:
  swap  >r ( R: k)  m+  ( d1+o)
  2dup   r> ( k) s>d  d-  ( d1-k)
  date>day-of-week  s>d  d-  ( d2) ;

: before ( d1 k -- d2)  -1 kday ;
: on-or-before ( d1 k -- d2)  0 kday ;
: nearest ( d1 k -- d2)  3 kday ;
: on-or-after ( d1 k -- d2)  6 kday ;
: after ( d1 k -- d2)  7 kday ;

: nth ( d1 k n -- d2)
\ Find nth kday relative to date d1:
  7 * dup >r ( R: nweeks)
  0 > if  before  else  on-or-after  then
  r> ( nweeks) m+ ;

: last ( d1 k -- d2)  -1 nth ;
: first ( d1 k -- d2)  1 nth ;
: second ( d1 k -- d2)  2 nth ;
: third ( d1 k -- d2)  3 nth ;
: fourth ( d1 k -- d2)  4 nth ;
: fifth ( d1 k -- d2)  5 nth ;

: leapyear? ( year -- bool)
  dup  4 mod  0=
  over 100 mod  0= invert  and
  swap  400 mod  0=  or ;

: dmy>date ( day month year -- d)
  dup   leapyear? >r ( R: leapyear?)
\ Compute days to start of year:
  1-  s>d  ( year-1.)
  2dup  365 dm*  \ ordinary days
\ ...plus leap days:
  2over  4 dm/  d+
  2over  100 dm/  d-
  2swap  400 dm/  d+
\ Compute days to start of month
\ (assumes Feb. has 30 days):
  rot dup >r ( R: month) s>d  367 dm*
  -362 m+ 12 dm/  d+  \ rough count
\ ...adjust for February:
  r>  February >  -2  \ 28 days
\ 29 in leap years:
  r> ( leapyear?) 1 and +  and  m+
\ Add days from start of month:
  rot m+ ;

: reduce ( ud1 ud2 -- udquot udrem)
  ud/mod  2swap ;

: date>year ( d -- year)
\ Multiple-radix conversion.
  1. d-
\ 146097=365.2425*400:
  146097. reduce ( n400.)
  36524. reduce ( n100.)
    2over  4. d=  >r ( R: n100=4?)
  1461. reduce ( n4.) \ 1461=365.25*4
  365. reduce ( n1.)   2drop
\ Correct for ends-of-cycles;
\ n1=n1+1 unless n1=4 or n100=4:
  2dup  4. d= ( n1=4?)  r> ( n100=4?)
  or  -1 and  1+  m+
\ Combine results:
  2swap  4 dm* d+  \ year=n1+4*n4
  2swap  100 dm* d+  \  +100*n100
  2swap  400 dm* d+  \  +400*n400.
  d>s ( year) ;

: date>dmy ( d -- day month year)
\ Determine year:
  2dup  date>year >r ( R: year)
\ Compute prior days in year:
  2dup  1 January r@ dmy>date  d-
\ Adjust for leap years:
  2over  1 March r@ dmy>date
  d< invert  2  r@ leapyear? 1 and  -
  and  m+
\ Compute month:
  12 dm*  373 m+  367 dm/  d>s
\ Compute prior days in month:
  1 over r@ ( year) dmy>date
  rot >r ( R: -- year month)
  d-  d>s  1+ ( day)
  r> ( month)  r> ( year) ;

: iso>date ( day week year -- d)
\ Find nth Sunday in ISO year:
  28 December rot 1-  dmy>date
  rot  Sunday  swap  nth
\ Add day:
  rot m+ ;

: date>iso ( d -- day week year)
  2dup  2dup  2dup
\ Approx. ISO year:
  3. d- date>year >r ( R: year)
\ Add 1 if date is in next ISO year:
  1 1 r@ ( year) 1+  iso>date
  d< invert  1 and  r> + >r
\ Compute week:
  1 1 r@ ( year) iso>date  d-
  7 dm/  1 m+  d>s >r ( R: year week)
  7 admod ( day)
  r> ( week)  r> ( year) ;
