;;; date-time.mhtml: -*- Meta-HTML -*- 
;;;
;;;  Copyright (c) 1996 Brian J. Fox
;;;  Author: Brian J. Fox (bfox@ua.com) Tue Nov  7 08:49:14 1995.

;;; Relative difference between GMT and your timezone, in hours.
;;; For example, PST is 8 hours behind GMT, so -8 is the right value
;;; for California sites.
<defvar *date*::tz-offset -8>

;;; Don't forget about Daylight savings time.  Set this to non-zero if
;;; you have daylight savings time where you are.
<defvar *date*::daylight-savings-time? true>

<set-var *date*::mm-dd-yy-regexp =
  "([0-9][0-9])([/-])([0-9][0-9])([/-])(([0-9][0-9])|([12][90][0-9][0-9]))">

<defun date::parse-date string &optional pack>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Populates the package <var pack> with the result of parsing
  ;;; <var string>.
  ;;;
  ;;; If <var string> is passed, that string is used instead of <tag date>,
  ;;; and should be the date in human readable format, just as
  ;;; <funref language-operators date> returns.
  ;;;
  ;;; If no <var pack> is specified, returns an alist of the values instead.
  <defvar string <date>>
  <if <match <get-var-once string> <get-var-once *date*::mm-dd-yy-regexp>>
      <return <date::parse-date <date::mm-dd-yy-to-date-string
				 <get-var-once string>> <get-var-once pack>>>>
  <in-package local>
    <set-var
      Jan=01 Feb=02 Mar=03 Apr=04 May=05 Jun=06
      Jul=07 Aug=08 Sep=09 Oct=10 Nov=11 Dec=12>

    <set-var temp=<get-var ^::string>>

    ;;; If the string is of the form "Feb 27, 1975" modify it to have a weekday
    ;;; and a time.
    <set-var temp[]=<subst-in-string <get-var-once temp> "[\t\n, ]+" "\n">>
    <set-var mindex=<array-member <substring <get-var-once temp[0]> 0 3>
				  *date*::short-month-names>>
    <when <get-var-once mindex>>
      <set-var temp[0]=<get-var-once *date*::short-month-names[mindex]>>
      <array-shift 1 temp>
      <set-var temp[0]=Fri>
      <array-shift 1 temp start=3>
      <set-var temp[3]="00:00:01">
    </when>

    <set-var
      components[]=<get-var-once temp[]>
      weekday=<get-var components[0]>
      month=<get-var components[1]>
      mindex=<array-member <substring <get-var-once month> 0 3>
			   *date*::short-month-names caseless=true>
      day=<get-var components[2]>
      cday=<pad <get-var-once day> 2 align=right pad-char="0">
      year=<get-var components[4]>
      canon1 = <concat <get-var <get-var month>>/
		       <get-var cday>/
		       <get-var year>>
      canon = <concat <get-var <get-var month>>/
		      <get-var cday>/
		      <match <get-var year> (^19|^20) action=delete>>>
    <unset-var temp>
    <unset-var Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec>
    <set-var ^::result = <package-to-alist>>
  </in-package>
  <if <get-var-once pack>
      <alist-to-package <get-var-once result> <get-var-once pack>>
    <get-var-once result>>
</defun>

;;; We are deprecating <parse-date-string> in favor of <date::parse-date>.
<defun parse-date-string string>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Deprecated.  Please use <funref DATES-AND-TIMES date::parse-date> instead.
  ;;; <example code> <parse-date-string> </example> is equivalent to
  ;;; <example code> <date::parse-date <date> date> </example>
  <date::parse-date <get-var-once string> date>
</defun>

;;; Months, arrayified by index.
<set-var *date*::month-info[]=
  <prog
    <make-alist days=31 short-name=Jan long-name=January>
    <make-alist days=28 short-name=Feb long-name=February leap=1>
    <make-alist days=31 short-name=Mar long-name=March>
    <make-alist days=30 short-name=Apr long-name=April>
    <make-alist days=31 short-name=May long-name=May>
    <make-alist days=30 short-name=Jun long-name=June>
    <make-alist days=31 short-name=Jul long-name=July>
    <make-alist days=31 short-name=Aug long-name=August>
    <make-alist days=30 short-name=Sep long-name=September>
    <make-alist days=31 short-name=Oct long-name=October>
    <make-alist days=30 short-name=Nov long-name=November>
    <make-alist days=31 short-name=Dec long-name=December>>>

<set-var i=0 *date*::seconds-per-year = 0>
<set-var *date*::seconds-per-day = <mul 60 <mul 60 24>>>
<foreach entry *date*::month-info>
  <package-delete temp>
  <alist-to-package <get-var entry> temp>
  <set-var temp::seconds=<mul *date*::seconds-per-day temp::days>>
  <set-var entry = <package-to-alist temp strip=true>>
  <set-var *date*::month-info[i] = <get-var entry>>
  <set-var *date*::<get-var temp::short-name> = <get-var entry>>
  <set-var *date*::<get-var temp::long-name> = <get-var entry>>
  <array-append <get-var temp::long-name> *date*::long-month-names>
  <array-append <get-var temp::short-name> *date*::short-month-names>
  <increment *date*::seconds-per-year by=<get-var temp::seconds>>
  <increment i>
</foreach>

<unset-var *date*::long-day-names *date*::short-day-names>
<array-append Sunday *date*::long-day-names>
<array-append Monday *date*::long-day-names>
<array-append Tuesday *date*::long-day-names>
<array-append Wednesday *date*::long-day-names>
<array-append Thursday *date*::long-day-names>
<array-append Friday *date*::long-day-names>
<array-append Saturday *date*::long-day-names>

<foreach day *date*::long-day-names>
  <array-append <substring <get-var day> 0 3> *date*::short-day-names>
</foreach>

<defun date::is-leap-year? year>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Returns "true" if <var year> is a leap year.
  <eq year <mul <div year 4> 4>>
</defun>

<defun date::mm-dd-yy-to-time mm-dd-yy>
  <set-var parts[] = <subst-in-string <get-var-once mm-dd-yy>
				      <get-var-once *date*::mm-dd-yy-regexp>
				      "\\1\n\\3\n\\5">>
  <set-var
    day   = <get-var-once parts[1]>
    month = <get-var-once *date*::short-month-names[<sub parts[0] 1>]>
    year  = <get-var-once parts[2]>>

  <if <lt year 38>
      <set-var year = <add year 2000>>
    <if <lt year 100>
	<set-var year = <add year 1900>>>>
  <date::seconds-since-epoch
   <concat "Mon <get-var month> <get-var day> " "00:00:00 <get-var year>">>
</defun>

<defun date::mm-dd-yy-to-date-string mm-dd-yy>
  <date <date::mm-dd-yy-to-time <get-var-once mm-dd-yy>>>
</defun>

<defun date::seconds-since-epoch date-string>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Return the number of seconds that have transpired between
  ;;; Jan 1, 1970 and <var date-string>.
  <defvar date-string <date>>
  <date::parse-date <get-var date-string> date>
  <set-var years-since-epoch = <sub date::year 1970>>

  ;;;
  ;;; Get the number of seconds up to the first second of this year.
  ;;;
  <set-var seconds-since-epoch =
    <mul *date*::seconds-per-year years-since-epoch>>

  ;;; Add in leap years, skipping the current year.
  <if <gt years-since-epoch 2>
      <increment seconds-since-epoch
		 by=<mul <add 1 <div <sub years-since-epoch 2> 4>>
			 *date*::seconds-per-day>>>

  ;;; Is the current year a leap year?
  <set-var doleap = <eq <mul 4 <div <sub years-since-epoch 2> 4>>
			years-since-epoch>>

  ;;; Add in the seconds used up till this month.
  <set-var month-index=0>
  <foreach entry *date*::month-info>
    <unset-var leap>
    <alist-to-package <get-var entry>>
    <if <string-eq <get-var short-name> <get-var date::month> caseless=true>
	<break>>
    <if <and <get-var doleap> <get-var leap>>
	<increment seconds by=<get-var *date*::seconds-per-day>>>
    <increment seconds-since-epoch by=<get-var seconds>>
    <increment month-index>
  </foreach>

  ;;; Add in the time zone offset.
  <set-var tz-diff = <mul -1 <mul *date*::tz-offset <mul 60 60>>>>
  <increment seconds-since-epoch by=<get-var tz-diff>>

  ;;; Correct for daylight savings time, if necessary.
  <set-var dst-start=3 dst-end=8>
  <if <and <get-var *date*::daylight-savings-time?>
	   <gt dst-end month-index dst-start>>
      <increment seconds-since-epoch by=<mul -60 60>>>

  ;;; Add in the seconds used up this month, and return that.
  <set-var hms[]=<subst-in-string <get-var date::components[3]> ":" "\n">>
  <add seconds-since-epoch <mul hms[0] 60 60> <mul hms[1] 60>
       hms[2] <mul *date*::seconds-per-day <sub date::day 1>>>
</defun>

<defun date::seconds-in-month month>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Return the number of seconds in <var month>.
  ;;; <var month> is the name of a month, such as "Apr" or "april".
  <alist-get-var <get-var *date*::<get-var month>> seconds>
</defun>

<defun date::days-in-month month>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Return the number of days in <var month>.
  ;;; <var month> is the name of a month, such as "Apr" or "april".
  <alist-to-package <get-var *date*::<get-var month>>>
  <get-var days>
</defun>

<defun date::short-month-name month>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Return the canonical short name of <var month>.
  ;;; <var month> is the name of a month, such as "Apr" or "april".
  <alist-to-package <get-var *date*::<get-var month>>>
  <get-var short-name>
</defun>

<defun date::long-month-name month>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Return the canonical long name of <var month>.
  ;;; <var month> is the name of a month, such as "Apr" or "april".
  <alist-to-package <get-var *date*::<get-var month>>>
  <get-var long-name>
</defun>

<defun date::format-time format-string &optional time>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Return a string representation of the date from the time in <var time>.
  ;;; If <var time> is not supplied, it defaults to the current time.
  ;;; The format string consists of any characters you would like, with the
  ;;; following special sets being replaced by their corresponding elements
  ;;; of the date.  Please note that case is significant:
  ;;;
  ;;; <ul>
  ;;; <li> <b>YY</b>:   Last two digits of the year.
  ;;; <li> <b>YYYY</b>: Four digits of the year.
  ;;; <li> <b>MM</b>:   Two digits of month, with January as <code>01</code>.
  ;;; <li> <b>MON</b>:  3 characters of month, as in <code>Jan</code>.
  ;;; <li> <b>MONTH</b>: The long name of the month, as in <code>March</code>
  ;;; <li> <b>DD</b>:   Two digits of day, as in <code>23</code>.
  ;;; <li> <b>DDD</b>:  3 character day of week, as in <code>Thu</code>.
  ;;; <li> <b>DAY</b>:  Full name of the weekday, as in <code>Tuesday</code>.
  ;;; <li> <b>hh</b>:   Hours, in 24 hour format, as in <code>17</code>.
  ;;; <li> <b>mm</b>:   Minutes, as in <code>43</code>.
  ;;; <li> <b>ss</b>:   Seconds, as in <code>04</code>.
  ;;; </ul>
  ;;;
  ;;; Example:
  ;;; <complete-example>
  ;;; <date::format-time "DAY, MONTH DD, YYYY (DD-MM-YY) at hh:mm:ss">
  ;;; </complete-example>
  <defvar time <time>>
  <date::parse-date <date <get-var time>> date>
  <subst-in-var
   format-string
    "YYYY" <get-var date::year>
      "YY" <substring <get-var date::year> 2 4>
   "MONTH" <date::long-month-name <get-var date::month>>
     "MON" <get-var date::month>
      "MM" <substring <get-var date::canon> 0 2>
     "DDD" <get-var date::weekday>
     "DAY" <get-var *date*::long-day-names[<array-member
					    <get-var date::weekday>
					    *date*::short-day-names>]>
      "DD" <substring <get-var date::canon> 3 5>
      "hh" <substring <get-var date::components[3]> 0 2>
      "mm" <substring <get-var date::components[3]> 3 5>
      "ss" <substring <get-var date::components[3]> 6 8>>
  <get-var-once format-string>
</defun>

<defun date::canonicalize date-string>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Return the canonical version of DATE-STRING, in the format
  ;;; "MM/DD/YY HH:MM:SS".
  ;;;
  ;;; Example:
  ;;; <complete-example>
  ;;; <date::canonicalize <date>>
  ;;; </complete-example>
  <set-var time = <date::seconds-since-epoch <get-var-once date-string>>>
  <date::format-time "MM/DD/YY hh:mm:ss" <get-var-once time>>
</defun>

<defun date::month-start-string time>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Return a date string represent the first second of the month specified
  ;;; by <var time>.
  ;;; <complete-example>
  ;;; <set-var now  = <time>>
  ;;; <set-var today= <date <get-var now>>>
  ;;; <set-var mstart = <date::month-start-string <get-var now>>>
  ;;; <pre>
  ;;;  Now: <get-var today>
  ;;; Then: <get-var mstart>
  ;;; </pre>
  ;;; </complete-example>
  <date::parse-date <date <get-var time>> date>
  ;;; Manufacture a date which is the first second of this month.
  <set-var start-date = <date <sub time <mul 60 60 24 <sub date::day 1>>>>>
  <subst-in-string <get-var start-date>
		   "[0-9][0-9]:[0-9][0-9]:[0-9][0-9]" "00:00:01">
</defun>

<defun date::month-table-standard-formatter day month year &key highlight link>
  <if <get-var-once link>
      <a href="<get-var-once link>?<cgi-encode day month year>">>
  <if <get-var-once highlight> <b>>
  <get-var-once day>
  <if <get-var-once highlight> </b>>
  <if <get-var-once link> </a>>
</defun>

<defun date::month-table &optional time &key highlight-days[] link-days[] href
  show-year formatter>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Create a table representing the month found in <var time>.
  ;;;
  ;;; Keyword argument <var highlight-days> is an array of day numbers
  ;;; that you would like to have highlighted in the month display.
  ;;;
  ;;; Keyword argument <var link-days> is an array of day numbers
  ;;; that should be linked to <var href> in the month display.
  ;;; 
  ;;; The arrays may have overlapping members.
  ;;;
  ;;; The keyword argument <var href> is the document to go to when a linked
  ;;; day is clicked on -- it is passed the day, month, and year, as two
  ;;; digit fields in the POSTED package under the variable names
  ;;; <var day>, <var month>, and <var year>.
  ;;;
  ;;; If keyword argument <var show-year> is non-empty, the year is
  ;;; displayed next to the month name.
  ;;;
  ;;; Finally, you can control the formatting of each day by supplying a
  ;;; function which takes day, month, year, and keywords of highlight,
  ;;; and link.  You pass the name of this function as the value of
  ;;; the keyword argument <var formatter>, and it is called for each day
  ;;; in the month to display.
  ;;;
  ;;; <html-complete-example>
  ;;; <date::month-table
  ;;;    highlight-days="3\n8" link-days="18\n8" href=<thisdoc>>
  ;;; </html-complete-example>
  ;;;
  ;;; <html-complete-example>
  ;;; <defun myformat day month year &key highlight link>
  ;;;   <if <get-var-once link>
  ;;;       <concat <a href="<get-var-once link><cgi-encode day month year>">
  ;;;               <get-var-once day>
  ;;;               </a>>
  ;;;     <if <get-var-once highlight>
  ;;;         <prog <b><get-var-once day></b>>
  ;;;       <get-var-once day>>>
  ;;; </defun>
  ;;;
  ;;; <date::month-table
  ;;;    highlight-days="3" link-days="12" href=<thisdoc> formatter=myformat>
  ;;; </html-complete-example>
  <defvar time <time>>
  <defvar formatter date::month-table-standard-formatter>
  <set-var start-date = <date::month-start-string <get-var time>>>
  <date::parse-date <get-var start-date> date>
  <set-var
    malist = <get-var-once *date*::<get-var date::month>>
    first-day = <array-member <get-var date::weekday> *date*::short-day-names>
    day=01
    month= <pad <add <array-member
		      <get-var date::month> *date*::short-month-names>
		     1>
		2 pad-char=0>
    year=<substring <get-var date::year> 2 4>
    i=0 limit=<alist-get-var malist days>>
  <while <lt i first-day>>
    <array-append <concat <td>&nbsp;</td>> days>
    <increment i>
  </while>
  <set-var i = 0>
  <while <lt i limit>>
    <increment i>
    <set-var opener=<concat "<" "td align=right>"> closer=<concat "<" "/td>">>
    <array-append
     <concat
      <get-var-once opener>
      <apply
       <get-var-once formatter>
       <pad <get-var-once i> 2 align=right pad-char="0">
       <get-var-once month>
       <get-var-once year>
       <if <array-member <get-var-once i> highlight-days> "highlight=true">
       <if <array-member <get-var-once i> link-days>
	   "link=<get-var-once href>">>
      <get-var-once closer>>
     days>
  </while>
  <increment i by=<get-var first-day>>
  <while <lt i 42>>
    <array-append <concat <td>&nbsp;</td>> days>
    <increment i>
  </while>

  ;;; Now place all of the days.
  <table border=1>
    <tr>
      <td bgcolor=black colspan=7 align=center>
	<font color=White><b>
	<alist-get-var malist long-name>
	<if <get-var show-year> <concat " <get-var year>">>
	</b></font>
      </td>
    </tr>
    <tr valign=top>
      <td align=right> <b>Su</b> </td>
      <td align=right> <b>Mo</b> </td>
      <td align=right> <b>Tu</b> </td>
      <td align=right> <b>We</b> </td>
      <td align=right> <b>Th</b> </td>
      <td align=right> <b>Fr</b> </td>
      <td align=right> <b>Sa</b> </td>
    </tr>
    <foreach td days iter=i>
      <if <eq 0 <mod i 7>>
	  <concat
	   <if <get-var-once in-row> <concat "<" "/tr>">>
	   <set-var in-row=true>
	   <concat "<" "tr valign=top>">>>
      <get-var-once td>
    </foreach>
    </tr>
  </table>
</defun>

<defun date::skip-months-forward count &optional time>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Return the seconds since the epoch representing the date which is
  ;;; exactly the first of the month which is <var count> months following
  ;;; the month represented by <var time>.
  ;;;
  ;;; The <i>epoch</i> is defined as Jan 01, 1970, 00:00:00.
  ;;;
  ;;; <var time> defaults to the current time.
  <defvar time <time>>
  <defvar count 1>
  <set-var
    month-date = <date::month-start-string <get-var time>>
    this-month = <get-var date::month>
    this-time = <date::seconds-since-epoch <get-var month-date>>>
  <while <gt count 0>>
    <set-var
      malist = <get-var *date*::<get-var this-month>>
      this-time = <add this-time <alist-get-var malist seconds>>>
    <date::parse-date <date <get-var this-time>> date>
    <set-var this-month = <get-var date::month>>
    <decrement count>
  </while>
  <get-var this-time>
</defun>

<defun date::skip-months-backward count &optional time>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Return the seconds since the epoch representing the date which is
  ;;; exactly the first of the month which is <var count> months before
  ;;; the month represented by <var time>.
  ;;;
  ;;; The <i>epoch</i> is defined as Jan 01, 1970, 00:00:00.
  ;;;
  ;;; <var time> defaults to the current time.
  <defvar time <time>>
  <defvar count 1>
  <while <gt count 0>>
    <set-var
      month-date = <date::month-start-string <get-var time>>
      time = <date::seconds-since-epoch <get-var month-date>>
      time = <sub time 2>>
    <decrement count>
  </while>
  <set-var
    month-date = <date::month-start-string <get-var time>>
    time = <date::seconds-since-epoch <get-var month-date>>>
  <sub time 1>
</defun>

<defun date::start-of-today time>
  <set-var
    date-string=<date::format-time "DDD MON DD hh:mm:ss YYYY" <get-var time>>>
  <subst-in-var date-string "[0-9][0-9]:[0-9][0-9]:[0-9][0-9]" "00:00:00">
  <page-debug <get-var-once date-string>>
  <date::seconds-since-epoch <get-var-once date-string>>
  ;;;  <sub time <mod time *date*::seconds-per-day>>
</defun>

<defun date::skip-days-backward count &optional time>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Return the seconds since the epoch representing the date which is
  ;;; exactly the first second of the day which is <var count> days before
  ;;; the day represented by <var time>.
  ;;;
  ;;; The <i>epoch</i> is defined as Jan 01, 1970, 00:00:00.
  ;;;
  ;;; <var time> defaults to the current time.
  <defvar time <time>>
  <defvar count 1>
  <if <lt count 0> 
      <return <date::skip-days-forward <get-var time> <sub 0 count>>>>
  <set-var time = <date::start-of-today <get-var time>>>
  <while <gt count 0>>
    <decrement time by=<get-var <get-var *date*::seconds-per-day>>>
    <decrement count>
  </while>
</defun>

<defun date::skip-days-forward count &optional time>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Return the seconds since the epoch representing the date which is
  ;;; exactly the first second of the day which is <var count> days after
  ;;; the day represented by <var time>.
  ;;;
  ;;; The <i>epoch</i> is defined as Jan 01, 1970, 00:00:00.
  ;;;
  ;;; <var time> defaults to the current time.
  <defvar time <time>>
  <defvar count 1>
  <if <lt count 0> 
      <return <date::skip-days-backward <get-var time> <sub 0 count>>>>
  <set-var time = <date::start-of-today <get-var time>>>
  <while <gt count 0>>
    <increment time by=<get-var <get-var *date*::seconds-per-day>>>
    <decrement count>
  </while>
</defun>

<defun date::month-table-centering-around &optional time count
  &key bgcolor formatter>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Display a table of <var COUNT> months, centered around <var TIME>.
  ;;;
  ;;; <var TIME> defaults to the current time, while <var COUNT> defaults
  ;;; to <code>3</code>.
  ;;; <html-complete-example>
  ;;; <date::month-table-centering-around>
  ;;; </html-complete-example>
  <defvar time <time>>
  <defvar count 3>
  <defvar bgcolor=#<color::hex-value AntiqueWhite>>
  <set-var
    beg-time = <date::skip-months-backward <div count 2> <get-var time>>
    end-time = <date::skip-months-forward <sub count <div count 2> 1>
					  <get-var time>>>
  <while <lt beg-time end-time>>
    <array-append
     <date::month-table <get-var beg-time>
			show-year=t formatter=<get-var formatter>>
     months>
    <increment beg-time by=<date::seconds-in-month <get-var date::month>>>
  </while>

  <table border=3 bgcolor=<get-var bgcolor>>
    <set-var col = 0 cols=3>
    <foreach elt months iter=i>
      <if <eq 0 <mod i 3>>
	  <concat
	   <if <get-var-once in-row> <concat "<" "/tr>">>
	   <set-var in-row=true>
	   <concat "<" "tr valign=top>">>>
      <td><get-var-once elt></td>
    </foreach>
    </tr>
  </table>
</defun>

<defun pluralize num word &key english>
  <concat
   "<get-var-once num> <get-var-once word>"
   <if <neq <get-var-once num> 1>  s>>
</defun>

<defun date::seconds-to-human-readable seconds>
  ;;; DOC_SECTION (DATES-AND-TIMES)
  ;;; Display the number of seconds passed in <var seconds> as a human readable
  ;;; string of text, representing elapsed time.
  ;;; For example:
  ;;; <complete-exampe>
  ;;; <date::seconds-to-human-readable 8734>
  ;;; </complete-example>
  <set-var remainder = <round <get-var-once seconds>>>
  <subst-in-var remainder "\\..*$" "">
  <defvar remainder 0>
  <when <gt remainder *date*::seconds-per-year>>
    <set-var
      years = <div remainder *date*::seconds-per-year>
      remainder = <sub remainder <mul years *date*::seconds-per-year>>>
  </when>

  <when <gt remainder <mul *date*::seconds-per-day 30>>>
    <set-var
      months = <div remainder <mul *date*::seconds-per-day 30>>
      remainder=<sub remainder <mul months <mul *date*::seconds-per-day 30>>>>
  </when>

  <when <gt remainder *date*::seconds-per-day>>
    <set-var
      days = <div remainder *date*::seconds-per-day>
      remainder=<sub remainder <mul days *date*::seconds-per-day>>>
  </when>

  <when <gt remainder 3600>>
    <set-var
      hours = <div remainder 3600>
      remainder=<sub remainder <mul hours 3600>>>
  </when>

  <when <gt remainder 60>>
    <set-var
      minutes = <div remainder 60>
      remainder=<sub remainder <mul minutes 60>>>
  </when>

  <if <neq 0 years>
      <array-append <pluralize <get-var-once years> year> result>>
  <if <neq 0 months>
      <array-append <pluralize <get-var-once months> month> result>>
  <if <neq 0 days>
      <array-append <pluralize <get-var-once days> day> result>>
  <if <neq 0 hours>
      <array-append <pluralize <get-var-once hours> hour> result>>
  <if <neq 0 minutes>
      <array-append <pluralize <get-var-once minutes> minute> result>>
  <if <neq 0 seconds>
      <array-append <pluralize <get-var-once remainder> second> result>>
  <defvar result "0 seconds">
  <comma-separated result>
</defun>

;;;
;;; Julian day numbers.
<defun date::julian-day-of-date date-string>
  <defvar date-string <date>>
  <set-var alist = <date::parse-date <get-var date-string>>>
  <set-var month = <alist-get-var alist month>>
  <foreach dalist *date*::month-info iter=x>
    <if <string-eq <alist-get-var dalist short-name> <get-var-once month>>
	<break>
      <increment days by=<alist-get-var dalist days>>>
  </foreach>
  <if <and <gt x 2>
	   <date::is-leap-year? <alist-get-var alist year>>>
      <increment days>>
  <pad <add days <alist-get-var alist day>> 3 pad-char=0 align=right>
</defun>
