Introduction

I decided to create a function that generated expiry info for web cookies and remembered an APL+Win workspace that did something similar. To cut a long story short I ended up taking a subset of the many date functions in that workspace and translating them to J. With the author's permission I've made them available here in case others find them useful. Apart from anything else the exercise was a useful learning experience for me, especially starting to come to terms with calling DLLs.

The functions would most probably benefit from added Jification and it would be nice if the GetTimeZoneInfo verb could be made to work across platforms. Unfortunately my Linux/Mac skills are not up to the task.

Download the dcdates.ijs script or, use the Literate version below.

The information below has been modified only slightly from that accompanying the original workspace.

Usage and Background

Most routines will accept an array of any size, shape, or rank and return an array of the same (or similar) dimensions. Operations on arrays of dates are fast and encouraged. All date routines (except dJulian and dGregorian) use the Julian Day Number style of date for simplicity. The YYYYMMDD format is only useful when passing dates to and from other systems.

Dates in the (calendar) form of YYYYMMDD are single 8-digit numeric values where groups of digits are used to describe the parts of the date. MM is the month number (01-12) and DD is the day number of the month (01-31). The year (YYYY) may vary from _4712 to 9999. If the year is negative, this is indicated by making the entire date a negative number, but without affecting the month or day digits (i.e. they still appear to read as the correct decimal values). Negative dates (years) are used to indicate dates prior to 1 A.D. Since there was no year 0 (A.D. or B.C.), the year number (YYYY) of 0 is used to represent the calendar year 1 B.C. A year number (YYYY) of _1 is used to represent the year 2 B.C., and so on. Dates are calculated properly back to the accepted standard starting date of January 1, 4713 B.C. (_47120101), and theoretically beyond that as well.

Important note: Dates from 15 Oct 1582 and later are assumed to represent dates in the Gregorian (reform) calendar and dates from 4 Oct 1582 and earlier are assumed to represent dates in the (proleptic) Julian calendar, even those that are prior to the official adoption of the Julian calendar in 46 B.C. Other variations and complications in the use of historical dates (such as those surrounding and immediately following the adoption of the Julian calendar system -- 46 B.C. to 4 A.D., or the variable date of switchover to the Gregorian calendar) are summarily ignored.

Julian Day Number

The "Julian Day Number" (also called the Julian Day or JD, but *not* the Julian date) form of the dates (JJJJJJJ) is the standard way of consistently measuring dates regardless of the local calendar in use at any given time. Its origin is at (Julian proleptic date) January 1, 4713 B.C. where the JD = 0. In many Julian Day Number systems (especially those used by astronomers), dates were actually assigned fractional numbers so that a time of day might be so indicated. In such systems, the whole value of the Julian Day represents noon on that day, so the previous midnight would be .5 days less and the following midnight would be .5 days more. Such fractional day numbers are not supported by most of these routines and all dates should be considered to be the whole number (noontime for astronomers) value for that day. However, dJulian and dGregorian have been modified to allow special "timestamp" formats for convenience, and these can accept and produce special fractional-day values which start at *midnight* (and thus are 12 hours different than astronomical day-fractions noted above).

The conversion routines were modified slightly so that a Julian Day (JJJJJJJ) of zero is returned and accepted when the calendar date (YYYYMMDD) is zero. This is to facilitate the handling of "null" (e.g. empty, undefined) dates by use of the value zero in either format. Unfortunately, this means that the exact (Julian proleptic) date of January 1, 4713 B.C. (_47120101) is not available for use since it also equates to day number zero. This is not expected to pose any difficulties to programmers in the real world, however.

References

The basis for the date conversion algorithms was derived from a detailed mathematical analysis by Peter Baum in 1998 and additional formulas and algorithms were derived from the current "calendar FAQ". At this time, Peter's information is available from his web site at and the FAQ is usually multi-posted to the following Usenet news groups: <sci.astro>, <soc.history>, <sci.answers>, <soc.answers>, and <news.answers>. Please consult these extensive sources of information for more details on the inner workings of the algorithms and the historical use of calendars through the centuries.

Literate version

«dcdates.ijs»= script index
NB. Julian day, date and time utilities

NB. Describe        n Description of Script and handling Julian days
NB. CookieExpires   v Format a web cookie's EXPIRES= parameter
NB. dGregorian      v convert a "Julian Day Number" to a Gregorian-style date (YYYYMMDD).
NB. dIStartDT       v given year, returns date that Daylight Saving Time starts
NB. dIStartST       v given year, returns date that Standard Time starts
NB. dJulian         v convert a Gregorian-style date (YYYYMMDD) to a "Julian Day Number".
NB. dSpell          v Format a date (a Julian Date Number) in a given format
NB. getTimeZoneInfo v function to return Windows time zone info
NB. tLocal          v Converts a UTC (GMT) date/time to Local one.
NB. tSpell          v Format a time (in seconds) in a given format.
NB. tUTC            v Converts a localized date/time to UTC (GMT) one.
«Require»  
«Describe»
«CookieExpires»
«dGregorian»
«handle Daylight saving time»
«dJulian»
«dSpell»
«getTimeZoneInfo»
«tLocal»
«tSpell»
«tUTC»

MS0Date and Linux0DateTime are constants useful for translating to and from dates generated by Microsoft applications (such as Excel, Access, or many VB-language routines) and Linux-style dates respectively. A Microsoft date can be directly added to MS0Date to get a Julian Day Number (JJJJJJJ). Or, MS0Date can be subtracted from a Julian Day Number (JJJJJJJ) to produce a Microsoft date. Be careful of missing dates as these are handled in different ways.

It would be useful to calculate and add the constant for the J dates script in the standard library.

«Require»= dependencies and constants
require 'dll winapi'

MS0Date=: 2415019
Linux0DateTime=: 2440588

«CookieExpires»=
NB.*CookieExpires v Format a web cookie's EXPIRES= parameter
NB. Date/time argument may be provided as any of:
NB.   6!:0 '' form (3, 5, 6 $)
NB.   +6!:0 '' form (adds to 6!:0 '' if year < 100)
NB.   YYYYMMDD[.HHMMSS] form
NB.   dJulian form (integer or fractional)
NB.   DDD[.HHMMSS] to add days/hours to current time
NB. Date/time argument is assumed to be in local time (will convert to GMT)
NB. Result is text of the form:
NB.   'expires=Wed, 01-May-2005 00:00:00 GMT'
NB. Reverse conversion is also available.  Supply the expiration character
NB. vector in standard form and it will be converted back to (local) 6!:0 '' form.
NB. ('expires=', 'GMT', and day-of-week are optional.  No checking is done.)
NB. Written 6 May 2004 by Davin Church of Creative Software Design
NB. Modified 10 October 2004 by Davin Church of Creative Software Design
NB. Translated May 2007 from APL+Win to J by Ric Sherlock
CookieExpires=: 3 : 0
  ts=. y
  if. ' '={.0$ts do. NB. *****Reverse conversion from text
    t=. ;:'expires= Expires= EXPIRES=' NB. Allow prefix with capitalizations
    ts=. ((${.t)*(<($t){.ts)e. t)}.ts NB. Remove prefix
    t=. ' GMT'
    ts=. ((-$t)*t-:(-$t){.ts)}.ts  NB. Remove suffix
    ts=. (>:(','e.ts)*ts i.',')}.ts  NB. Remove day-of-week
    ts=. 6{.(ts e.' -:,')<;._1 ts NB. Chop into individual pieces
    t=. ;: 'Jan Feb Mar Apr May Jun Jul Aug Sep Aug Oct Nov Dec'
    ts=.(<>:t i. 1{ts) (1)}ts NB. Convert month to numeric form
    ts=.> ({.each ".each 0 2 3 4 5{ts) (2 0 3 4 5)}ts NB. Convert text to numbers and reorder
    ts=. ((0{ts)+2000*100>0{ts) (0)}ts NB. Accept squirrelly 2-digit year
    NB. That gives it to us in GMT - now convert to local time
    exp=. 2 dGregorian tLocal dJulian ts NB. And that gives us our result
    
  else.   NB. *****Normal formatting of date given
    if. 3<:#ts do. NB. using 6!:0 '' format
      if. ({.ts)<100 do. ts=. (6!:0 '')+6{.ts end. NB. timestamp offset form
      date=. dJulian 3{.ts
      time=. 60#.3{.3}.ts
    else. NB. some singleton form
      date=. <.{.ts
      time=. 1|{.ts
      if. date<10000 do. NB. Allow short offsets from "now"
        ts=. (dJulian 6!:0 '')+date+(60#.(3#100)#:<.0.5+time*1000000)%86400
        date=. <.{.ts
        time=. 1|{.ts
      end.
      if. date>:10000000 do.
        date=. dJulian date
        time=. 60#.(3#100)#.<.0.5+time*1000000
      else.
        time=. <.0.5+86400*time
      end.
    end.
  NB. Convert to UTC/GMT (including DST for given date)
    date=. <.ts=. tUTC date+time%86400
    time=. <.0.5+86400*1|ts
  NB. Format as text
    date=. 'ddd, dd-mmm-yyyy' dSpell date
    time=. 'hh:mm:ss' tSpell time
    exp=. 'expires=',date,' ',time,' GMT'
  end.
)

«dGregorian»= to Gregorian date
NB.*dGregorian v convert a "Julian Day Number" to a Gregorian-style date (YYYYMMDD).
NB.  If optional left argument is specified as a numeric 0 (default), then
NB.  the result will have the year, month, and day separated into distinct
NB.  elements in 3 columns YYYY MM DD. If it is 1 then YYYYMMDD.
NB.  If the left argument is specified as a numeric 2, then the result will
NB.  have an expanded date as above, but will include 6 columns/elements
NB.  to represent a timestamp in 6!:0 format.
NB.  The input values may optionally specify a timestamp value by providing
NB.  a fractional number with the fraction representing a particular time of
NB.  day (starting at midnight) as a fraction of a whole day.  If a time is
NB.  specified in this way, then the returned result will also be fractional
NB.  and return the timestamp in the form YYYYMMDD.HHMMSSTTT, or as a
NB.  7-column matrix (in 6!:0 form) if the left argument is 2 (see above).
NB.  Warning: The YYYYMMDD.HHMMSSTTT format cannot be precisely stored by the
NB.  computer and may manifest millisecond rounding errors (up or down) which
NB.  can adversely effect computation or display of the resulting values.
NB.  Any rank & shape array accepted.  See "Describe" for more details.
NB.   Constants: 1721117 is 1-Jan-0001; 2299160 is 4-Oct-1582
NB.   Constants: 584400 is (10 + 29-Feb-1600) - 1-Jan-0001
NB. Written in 1998 by Davin Church of Creative Software Design
NB. Modified January 2003 by Davin Church of Creative Software Design
NB. Translated May 2007 from APL+Win to J by Ric Sherlock
dGregorian=: 3 : 0
  0 dGregorian y
  :
  JJJ=. y
  nz=. JJJ~:0
  if. f=. 1 e. ,0~:FFF=. 1||JJJ do.
    JJJ=. (*JJJ)*<.|JJJ
  end.
  
  JJJ=. (JJJ+10*JJJ>2299160)-1721117
  j1600=. 0>.(j=. JJJ-0.25)-584400
  
  t=. >.0.75*<.j1600%36524.25
  Y=. <.(t+j)%365.25
  d=. t+JJJ-<.Y*365.25
  
  M=. (<:d){(31 30 31 30 31 31 30 31 30 31 31 29#2+>:i.12)
  D=. d-(<:M){0 0 0 31 61 92 122 153 184 214 245 275 306 337
  Y=. Y+t=. M>12
  M=. M-12*t
  
  if. +./t=. 0 2 e. {.x do.
    YMD=. nz*Y,"0 1 M,."1 D NB. matrix form
    if. 1{t do. NB. add optional time to it
      YMD,"1 nz*(0 60 60)#: 0.001*<.0.5+FFF*86400000
    end.
  else.
    YMD=. nz*(_1^Y<:0)*(10000*|Y)+(100*M)+D NB. Normal form
    if. f do. NB. Add the optional time to it (if any time was supplied)
      FFF=. (0 100 100 1000)#.(0 60 60 1000)#:<.0.5+FFF*86400000
      YMD=. (*YMD)*(|YMD)+FFF%1000000000
    end.
  end.
)

«handle Daylight saving time»=
NB.*dIStartDT v given year, returns date that Daylight Saving Time starts
NB. uses current Windows system rules
NB. Written in 1998 by Davin Church of Creative Software Design
NB. Translated May 2007 from APL+Win to J by Ric Sherlock
dIStartDT=: 3 : 0
  year=. y
  rule=. 6{:: getTimeZoneInfo ''
  if. 0=1{rule do. NB. Daylight Time not used
    date=. 0
  elseif. 0=0{rule do. NB. Use day-of-month rule
    date=. 1 dJulian (_1^year<0)*(1+100*1{rule)+10000*|year
    date=. (7*(3{rule)-1)+date+7|1+(2{rule)-1+7|1+date
    date=. date-7*(1{rule)~:100|<.(1 dGregorian date)%100
  elseif. do. NB. Use fixed-date rule
    date=. (year=0{rule)*1 dJulian 100#:0 1 3{rule
  end.
)

NB.*dIStartST v given year, returns date that Standard Time starts
NB. uses current Windows system rules
NB. Written in 1998 by Davin Church of Creative Software Design
NB. Translated May 2007 from APL+Win to J by Ric Sherlock
dIStartST=: 3 : 0
  year=. y
  rule=. 3{:: getTimeZoneInfo ''
  if. 0=1{rule do. NB. Daylight Time not used
    date=. 0
  elseif. 0=0{rule do. NB. Use day-of-month rule
    date=. 1 dJulian (_1^year<0)*(1+100*1{rule)+10000*|year
    date=. (7*(3{rule)-1)+date+7|1+(2{rule)-1+7|1+date
    date=. date-7*(1{rule)~:100|<.(1 dGregorian date)%100
  elseif. do. NB. Use fixed-date rule
    date=. (year=0{rule)*1 dJulian 100#:0 1 3{rule
  end.
)

«dJulian»= to Julian day
NB.*dJulian v convert a Gregorian-style date (YYYYMMDD) to a "Julian Day Number".
NB. Dates may optionally include a "time" portion to produce a "timestamp"
NB. value.  Standard integers are extended to allow non-integer
NB. values in the format (YYYYMMDD.HHMMSSTTT).  If this optional
NB. format is used, the result will not be an integer and the fractional
NB. portion will be the numeric fraction of a day representing that time.
NB. Warning: The YYYYMMDD.HHMMSSTTT format cannot be precisely stored by the
NB. computer.  Such inputs are pre-rounded to a multiple of 10 milliseconds
NB. to prevent erroneous rounding during processing.
NB.  Constants: 1721117 is 1-Jan-0001; 2299160 is 4-Oct-1582
NB.
NB. Original APL written in 1998 by Davin Church of Creative Software Design
NB. Modified July 2005 by Davin Church of Creative Software Design
NB. Translated May 2007 from APL+Win to J by Ric Sherlock
dJulian=: 3 : 0
  0 dJulian y
  :
  YMD=. y
  if. x=0 do.
    HMS=. (0 60 60#. 3{."1 (3}."1 YMD))%86400
    YMD=. (_1^0>{."1 YMD)*100 #. 3{."1 |YMD
  elseif. 1 e. ,0~:HMS=. <.0.5+100000000*1||YMD do. NB. did they give us a timestamp?
    YMD=. <.YMD
    HMS=. (0 60 60 1000)#.(0 100 100 1000 #: HMS*10)%86400000
  end.
  Y=. (*YMD)*<.|YMD%10000
  M=. <.(YMD=. 10000||YMD)%100
  Y=. Y-12<M=. M+12*M<:2
  JJJ=. (100|YMD)+ (<:M){0 0 0 31 61 92 122 153 184 214 245 275 306 337
  y1600=. 0>.Y-1600
  JJJ=. JJJ+1721117+(<.Y*365.25)-(<.y1600%100)-<.y1600%400
  JJJ=. HMS+(YMD~:0)*JJJ-10*JJJ>2299160
)

«dSpell»= custom date format
NB.*dSpell v Format a date (a Julian Date Number) in a given format
NB. Specify the date format to be used with the following codes:
NB.      d: 1   dd: 01   ddd: Sun   dddd: Sunday
NB.      m: 1   mm: 01   mmm: Jan   mmmm: January
NB.             yy: 99              yyyy: 1999
NB. Any rank & shape array accepted.  See "Describe" for more details.
NB. Written in 1998 by Davin Church of Creative Software Design
NB. Translated May 2007 from APL+Win to J by Ric Sherlock
dSpell=: 3 : 0
  'mmmm d, yyyy' dSpell y
  :
  codes=. ;:'d dd ddd dddd m mm mmm mmmm yy yyyy'
  pic=. x
  t=. 2</\0, s=. pic='\'
  var=. -.}:0,s=. s*.~:/\s~:t &#^:_1 (2~:/\0,t#~:/\}:0,s)  NB. Handle '\\*'s
  
  pic=. ((a.i.pic)+(-/a.i.'aA')*var*.pic e.'DMY'){a.  NB. force to l/c
  pic=. (t=. 1,2~:/\(-.s)#(3*-.var)>.'dmy'i.pic) <;.1 (-.s)#pic  NB. Cut into tokens
  var=. (pic e. codes)*. -. ;0 e.&.> (t <;.1 (-.s)#var) NB. mark sections as vars
  
  s=. *YMD=. ,1 dGregorian <.y
  YMD=. |: 0 100 100 #:|YMD
  YMD=. (s*0{YMD) (0)}YMD
  t=. 2{YMD
  values=. (": each t);<(}.each ": each 100+t) NB. Format values for all codes
  
  s=. ;:'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'
  t=. (7|1+<.y){s
  values=. values,(3{. each t);<t NB. Embed weekday
  
  t=. 1{YMD
  values=. values, (": each t);<(}.each ": each 100 + t)
  s=. ;:'January February March April May June July August September October November December'
  t=. (0>.<:t){s
  values=. values, (3{.each t);<t
  s=. 0>:t=. 0{YMD
  t=. |t-s
  
  s=. s{('';' BC')
  t=. (": each 10000+t),each s
  values=. values,(3}.each t) ; <(1}.each t)
  
  t=. (#codes)+ i.$pic
  t=. (codes i. var#pic) (I.var)}t
  
  text=. ($y)$>,each each /t{(values,(#1{::values)$each <each pic)
  
  text=. ((0~:y)*each$each text)$each text
  if. (0$0)-:$y do. text=. >text end.
  text
)

The following verb gets the timezone information as stored by Windows. This information is key to any of the other verbs related to converting local time to UTC/GMT time and back. It would be nice if this verb could be extended to work other platforms too.

«getTimeZoneInfo»= Windows api call
NB.*getTimeZoneInfo v function to return Windows time zone info
NB. returns 8-item boxed vector
NB.    Daylight saving status (0 unknown, 1 standarddate, 2 daylightdate)
NB.    Bias; StarndardName; StandardDate; StandardBias; DaylightName; DaylightBias
NB. eg. getTimeZoneInfo ''
NB. Based on APL+Win func written by Davin Church of Creative Software Design
NB. Written May 2007 by Ric Sherlock
getTimeZoneInfo=: 3 : 0
  'info buffer'=. 'GetTimeZoneInformation'win32api <(,43#0)
  buffer=. (1 (<:+/\ 1 16 4 1 16 4 1)}43#0) <;.2 buffer NB. 4 byte J integers
  uctext=. 6&u: each 2&ic each 1 4 {buffer NB. get unicode text for StandardName & DaylightName
  inul=. uctext i. each 0{a. NB. find first NUL in each
  buffer=. (inul{.each uctext) (1 4)}buffer NB. amend buffer
  buffer=. (_1&ic each 2&ic each 2 5{buffer) (2 5)}buffer  NB.convert SYSTEMTIME structures
  info;buffer
)

«tLocal»= GMT to Local
NB.*tLocal v Converts a UTC (GMT) date/time to Local one.
NB. input & output date/time values are floating point Julian days.
NB. Fractional portion is time as a fractional day (e.g. X.5 is noon)
NB. Uses the current Window settings for local time definitions.
NB. Written in 1998 by Davin Church of Creative Software Design
NB. Translated May 2007 from APL+Win to J by Ric Sherlock
tLocal=: 3 : 0
  utc=. y
  rule=. getTimeZoneInfo ''
  yr=. <.(1 dGregorian <.utc)%10000
  reverse=. (dt=. dIStartDT yr)>st=. dIStartST yr NB. Southern hemisphere?
  local=. utc-({.1{::rule)%1440 NB. Use local std time to mark switchover point
  rls=. reverse*.local<st+1
  rld=. reverse*.local>dt-1 NB. Unwrap years?
  st=. (dIStartST yr+rld)+(0 60 60 1000#.4}.3{::rule)%86400000
  dt=. (dIStartDT yr-rls)+(0 60 60 1000#.4}.6{::rule)%86400000
  usedst=. (local>:dt+({.4{::rule)%1440)*.(local<st+({.7{::rule)%1440)
  local=. local-((dt~:0)*((0+usedst){4 7)>@{rule)%1440
  local=. ($y)$,local
)

«tSpell»= custom time format
NB.*tSpell v Format a time (in seconds) in a given format.
NB. Specify the time format to be used with the following codes:
NB. (either upper or lower case) to specify the formatting of days ("D"),
NB. hours ("H"), minutes ("M"), seconds ("S"), fractions of a second ("C"),
NB. or AM/PM designator ("P"):
NB.        d: 1    h: 1    m: 1    s: 1      c: 1       p: a
NB.               hh: 01  mm: 01  ss: 01    cc: 01     pp: am
NB.                              sss: 1.2  ccc: 001
NB.                                       cccc: 0001
NB. Any rank & shape array accepted.  See "Describe" for more details.
NB. Written in 1998 by Davin Church of Creative Software Design
NB. Translated May 2007 from APL+Win to J by Ric Sherlock
tSpell=: 3 : 0
  'h:mm:ss pp' tSpell y
  :
  codes=. ;:'d h hh m mm s ss sss c cc ccc p pp'
  pic=. x
  t=. 2</\0, s=. pic='\'
  var=. -.}:0,s=. s*.~:/\s~:t &#^:_1 (2~:/\0,t#~:/\}:0,s)  NB. Handle '\\*'s
  
  DCP=. 'dcp' e. var#pic=. ((a.i.pic)+(-/a.i.'aA')*var*.pic e.'DHMSCP'){a.  NB. force to l/c
  pic=. (t=. 1,2~:/\(-.s)#(3*-.var)>.'dhmscp'i.pic) <;.1 (-.s)#pic  NB. Cut into tokens
  var=. (pic e. codes)*. -. ;0 e.&.> (t <;.1 (-.s)#var) NB. mark sections as vars
  
  DHMS=. |:(0,(24*0{DCP),60 60) #: <.0>.,y
  CCC=. (<.0.5+10000*1|0>.,y)%10
  values=. ": each 0{DHMS
  t=. (2{DCP)+ (12*2{DCP)|(1{DHMS)-2{DCP NB. Format values
  values=. values; (": each t);<(_2{. each (": each 100+ t),each (2* t>99)$ each '*')
  t=. 2{DHMS
  values=. values,(": each t);<(}.each ": each 100+t)
  t=. 3{DHMS
  values=. values,(": each t);<(}.each ": each 100+t)
  t=. ": each t+CCC%1000
  values=. values,< (('.'={.each t)$ each '0'),each t
  values=. values,<"1 |:}.each ":each 10 100 1000+"1 <.0.5+CCC%"0 1 (100 10 1)
  t=. (12<:24|1{DHMS){ ;:'am pm'
  values=. values,(1{.each t);<t
  t=. (#codes)+ i.$pic
  t=. (codes i. var#pic) (I.var)}t
  text=. ($y)$>,each each /t{(values,(#1{::values)$each <each pic)
  
  text=. ((y>:0)*each$each text)$each text
  if. (0$0)-:$y do. text=. >text end.
  text
)

«tUTC»= Local to GMT
NB.*tUTC v Converts a localized date/time to UTC (GMT) one.
NB. input & output date/time values are floating point Julian days.
NB. Fractional portion is time as a fractional day (e.g. X.5 is noon)
NB. Uses the current Window settings for local time definitions.
NB. Written in 1998 by Davin Church of Creative Software Design
NB. Translated May 2007 from APL+Win to J by Ric Sherlock
tUTC=: 3 : 0
  local=. y
  rule=. getTimeZoneInfo ''
  yr=. <.(1 dGregorian <.local)%10000
  reverse=. (dt=. dIStartDT yr)>st=. dIStartST yr
  rls=. reverse*.local<st+1
  rld=. reverse*.local>dt-1 NB. Unwrap years?
  st=. (dIStartST yr+rld)+(0 60 60 1000#.4}.3{::rule)%86400000
  dt=. (dIStartDT yr-rls)+(0 60 60 1000#.4}.6{::rule)%86400000
  usedst=. (local>:dt)*.(local<st)
  utc=. local+(({.1{::rule)+(dt~:0)*((0+usedst){4 7)>@{rule)%1440
  utc=. ($y)$,utc
)

«Describe»= Script description
Describe=: 0 : 0
This script contains a subset of the date functions written for APL+Win
by Davin Church of Creative Software Design and distributed in his Dates
workspace. They were translated to J by Ric Sherlock. Any errors are likely
to have been introduced during translation. The functions would most
probably benefit from added Jification. The description that follows is
modified only slightly from Davin's original.

Most routines will accept an array of any size, shape, or rank and return an
array of the same (or similar) dimensions.  Operations on arrays of dates are
fast and encouraged.  All date routines (except dJulian and dGregorian) use
the Julian Day Number style of date for simplicity.  The YYYYMMDD format is
only useful when passing dates to and from other systems.

Dates in the (calendar) form of YYYYMMDD are single 8-digit numeric values
where groups of digits are used to describe the parts of the date.  MM is
the month number (01-12) and DD is the day number of the month (01-31).  The
year (YYYY) may vary from _4712 to 9999.  If the year is negative, this is
indicated by making the entire date a negative number, but without affecting
the month or day digits (i.e. they still appear to read as the correct decimal
values).  Negative dates (years) are used to indicate dates prior to 1 A.D.
Since there was no year 0 (A.D. or B.C.), the year number (YYYY) of 0 is used
to represent the calendar year 1 B.C.  A year number (YYYY) of _1 is used to
represent the year 2 B.C., and so on.  Dates are calculated properly back to
the accepted standard starting date of January 1, 4713 B.C. (_47120101), and
theoretically beyond that as well.
Important note:  Dates from 15 Oct 1582 and later are assumed to represent
dates in the Gregorian (reform) calendar and dates from 4 Oct 1582 and earlier
are assumed to represent dates in the (proleptic) Julian calendar, even those
that are prior to the official adoption of the Julian calendar in 46 B.C.
Other variations and complications in the use of historical dates (such as
those surrounding and immediately following the adoption of the Julian calendar
system -- 46 B.C. to 4 A.D., or the variable date of switchover to the
Gregorian calendar) are summarily ignored.

The "Julian Day Number" (also called the Julian Day or JD, but *not* the Julian
date) form of the dates (JJJJJJJ) is the standard way of consistently measuring
dates regardless of the local calendar in use at any given time.  Its origin is
at (Julian proleptic date) January 1, 4713 B.C. where the JD = 0.  In many
Julian Day Number systems (especially those used by astronomers), dates were
actually assigned fractional numbers so that a time of day might be so
indicated.  In such systems, the whole value of the Julian Day represents noon
on that day, so the previous midnight would be .5 days less and the following
midnight would be .5 days more.  Such fractional day numbers are not supported
by most of these routines and all dates should be considered to be the whole
number (noontime for astronomers) value for that day.  However, dJulian and
dGregorian have been modified to allow special "timestamp" formats for
convenience, and these can accept and produce special fractional-day values
which start at *midnight* (and thus are 12 hours different than astronomical
day-fractions noted above).

The conversion routines were modified slightly so that a Julian Day (JJJJJJJ)
of zero is returned and accepted when the calendar date (YYYYMMDD) is zero.
This is to facilitate the handling of "null" (e.g. empty, undefined) dates by
use of the value zero in either format.  Unfortunately, this means that the
exact (Julian proleptic) date of January 1, 4713 B.C. (_47120101) is not
available for use since it also equates to day number zero.  This is not
expected to pose any difficulties to programmers in the real world, however.

A global variable called MS0Date may be found in this workspace and is useful
for translating to and from dates generated by Microsoft applications
(such as Excel, Access, or many VB-language routines).  A Microsoft date can
be directly added to MS0Date to get a Julian Day Number (JJJJJJJ).  Or, MS0Date
can be subtracted from a Julian Day Number (JJJJJJJ) to produce a Microsoft
date.  Be careful of missing dates as these are handled in different ways.
A global variable called Linux0DateTime is also available for performing
similar translations to and from Linux-style dates (which are usually stored
in seconds).  Other constants may be computed for any other special needs.

The basis for the date conversion algorithms was derived from a detailed
mathematical analysis by Peter Baum in 1998 and additional formulas and
algorithms were derived from the current "calendar FAQ".  At this time, Peter's
information is available from his web site at <vsg.cape.com/~pbaum> and the
FAQ is usually multi-posted to the following Usenet news groups: <sci.astro>,
<soc.history>, <sci.answers>, <soc.answers>, and <news.answers>.  Please consult
these extensive sources of information for more details on the inner workings of
the algorithms and the historical use of calendars through the centuries.

Many structural changes were made to the formulas and algorithms in the process
of coding them first in APL and then in J.  All rights thus obtained are reserved
and the final code is copyright 1998-2005 by Creative Software Design.  But these
functions are intended to be freely used in commercial applications -- see this
workspace's "ReadMe" variable for more details.
)

-- RicSherlock 2007-05-24 09:06:44


CategoryLiterate

Scripts/JulianDayDate (last edited 2009-09-13 00:24:51 by RicSherlock)