diff options
Diffstat (limited to 'ipl/procs/datetime.icn')
-rw-r--r-- | ipl/procs/datetime.icn | 607 |
1 files changed, 607 insertions, 0 deletions
diff --git a/ipl/procs/datetime.icn b/ipl/procs/datetime.icn new file mode 100644 index 0000000..b57e49a --- /dev/null +++ b/ipl/procs/datetime.icn @@ -0,0 +1,607 @@ +############################################################################ +# +# File: datetime.icn +# +# Subject: Procedures for date and time operations +# +# Author: Robert J. Alexander and Ralph E. Griswold +# +# Date: August 9, 2000 +# +############################################################################ +# +# This file is in the public domain. +# +############################################################################ +# +# Notes: +# - the default value for function parameters named +# "hoursFromGmt" is the value of global variable +# "HoursFromGmt" if nonnull, or environment variable +# "HoursFromGmt" if set, or 0. +# - The base year from which the "seconds" representation +# of a date is calculated is by default 1970 (the ad hoc +# standard used by both Unix and MS-Windows), but can be +# changed by either setting the global variable +# "DateBaseYear" or environment variable "DateBaseYear". +# - There are some procedures not mentioned in this summary +# that are useful: DateRecToSec(), SecToDateRec(). See the +# source code for details. +# +# ClockToSec(seconds) +# converts a time in the format of &clock to seconds past +# midnight. +# +# DateLineToSec(dateline,hoursFromGmt) +# converts a date in &dateline format to seconds since start of +# dateBaseYear. +# +# DateToSec(date,hoursFromGmt) +# converts a date string in Icon &date format (yyyy/mm/dd) +# to seconds past DateBaseYear. +# +# SecToClock(seconds) +# converts seconds past midnight to a string in the format of +# &clock. +# +# SecToDate(seconds,hoursFromGmt) +# converts seconds past DateBaseYear to a string in Icon +# &date format (yyyy/mm/dd). +# +# SecToDateLine(seconds,hoursFromGmt) +# produces a date in the same format as Icon's &dateline. +# +# SecToUnixDate(seconds,hoursFromGmt) +# returns a date and time in typical UNIX format: +# Jan 14 10:24 1991. +# +# IsLeapYear(year) +# succeeds if year is a leap year, otherwise fails. +# +# calendat(j) +# returns a record with the month, day, and year corresponding +# to the Julian Date Number j. +# +# date() natural date in English. +# +# dayoweek(day, month, year) +# produces the day of the week for the given date. +# Note carefully the parameter order. +# +# full13th(year1, year2) +# generates records giving the days on which a full moon occurs +# on Friday the 13th in the range from year1 though year2. +# +# julian(m, d, y) +# returns the Julian Day Number for the specified +# month, day, and year. +# +# pom(n, phase) +# returns record with the Julian Day number of fractional +# part of the day for which the nth such phase since +# January, 1900. Phases are encoded as: +# +# 0 - new moon +# 1 - first quarter +# 2 - full moon +# 3 - last quarter# +# +# GMT is assumed. +# +# saytime() +# computes the time in natural English. If an argument is +# supplied it is used as a test value to check the operation +# the program. +# +# walltime() +# produces the number of seconds since midnight. Beware +# wrap-around when used in programs that span midnight. +# +############################################################################ +# +# See also: datefns.icn +# +############################################################################ +# +# Acknowledgement: Some of these procedures are based on an algorithm +# given in "Numerical Recipes; The Art of Scientific Computing"; +# William H. Press, Brian P. Flannery, Saul A. Teukolsky, and William +# T. Vetterling;# Cambridge University Press, 1986. +# +############################################################################ + +record date1(month, day, year) +record date2(month, year, fraction) +record jdate(number, fraction) +record DateRec(year,month,day,hour,min,sec,weekday) + +global Months,Days,DateBaseYear,HoursFromGmt + +procedure ClockToSec(seconds) #: convert &date to seconds +# +# Converts a time in the format of &clock to seconds past midnight. +# + seconds ? return ( + (1(tab(many(&digits)),move(1)) * 60 + + 1(tab(many(&digits)),move(1) | &null)) * 60 + + (tab(many(&digits)) | 0) + ) +end + +procedure DateInit() +# +# Initialize the date globals -- done automatically by calls to date +# procedures. +# + initial { + Months := ["January","February","March","April","May","June", + "July","August","September","October","November","December"] + Days := ["Sunday","Monday","Tuesday","Wednesday","Thursday", + "Friday","Saturday"] + /DateBaseYear := integer(getenv("DateBaseYear")) | 1970 + /HoursFromGmt := integer(getenv("HoursFromGmt")) | 0 + } + return +end + + +procedure DateLineToSec(dateline,hoursFromGmt) #: convert &dateline to seconds +# +# Converts a date in long form to seconds since start of DateBaseYear. +# + local day,halfday,hour,min,month,sec,year + static months + initial { + DateInit() + months := table() + months["jan"] := 1 + months["feb"] := 2 + months["mar"] := 3 + months["apr"] := 4 + months["may"] := 5 + months["jun"] := 6 + months["jul"] := 7 + months["aug"] := 8 + months["sep"] := 9 + months["oct"] := 10 + months["nov"] := 11 + months["dec"] := 12 + } + map(dateline) ? { + tab(many(' \t')) + =("sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat") & + tab(many(&letters)) | &null & tab(many(' \t,')) | &null + month := 1(tab(many(&letters)),tab(many(' \t')) | &null) + day <- integer(1(tab(many(&digits)),tab(many(' \t,')) | &null)) | &null & + year <- integer(1(tab(many(&digits)),tab(many(' \t')) | &null)) | &null & + (hour <- integer(tab(many(&digits))) & + ((=":" & min <- integer(tab(many(&digits)))) & + ((=":" & sec <- integer(tab(many(&digits)))) | &null) | &null) & + tab(many(' \t')) | &null & halfday := =("am" | "pm") | &null & + tab(many(' \t')) | &null) | &null & pos(0) + } + \month := \months[month[1+:3]] | fail + if not /(halfday | hour) then { + if hour = 12 then hour := 0 + if halfday == "pm" then + hour +:= 12 + } + return DateRecToSec(DateRec(year,month,day,hour,min,sec),hoursFromGmt) +end + +procedure DateRecToSec(dateRec,hoursFromGmt) +# +# Converts a DateRec to seconds since start of DateBaseYear. +# + local day,hour,min,month,sec,secs,year,yr + static days + initial { + DateInit() + days := [ + 0, + 2678400, + 5097600, + 7776000, + 10368000, + 13046400, + 15638400, + 18316800, + 20995200, + 23587200, + 26265600, + 28857600 + ] + } + /hoursFromGmt := HoursFromGmt + hoursFromGmt := integer(hoursFromGmt) | runerr(101,hoursFromGmt) + year := \dateRec.year | +&date[1+:4] + month := \dateRec.month | +&date[6+:2] + day := \dateRec.day | +&date[9+:2] + hour := \dateRec.hour | 0 + min := \dateRec.min | 0 + sec := \dateRec.sec | 0 + secs := 0 + every yr := DateBaseYear to year - 1 do { + secs +:= if IsLeapYear(yr) then 31622400 else 31536000 + } + if month > 2 & IsLeapYear(year) then secs +:= 86400 + return secs + days[month] + (day - 1) * 86400 + + (hour - hoursFromGmt) * 3600 + min * 60 + sec +end + +procedure DateToSec(date,hoursFromGmt) #: convert &date to seconds +# +# Converts a date in Icon &date format (yyyy/mm/dd) do seconds +# past DateBaseYear. +# + date ? return DateRecToSec(DateRec(+1(tab(find("/")),move(1)), + +1(tab(find("/")),move(1)),+tab(0)),hoursFromGmt) +end + +procedure SecToClock(seconds) #: convert seconds to &clock +# +# Converts seconds past midnight to a string in the format of &clock. +# + local sec + sec := seconds % 60 + seconds /:= 60 + return right(seconds / 60,2,"0") || ":" || right(seconds % 60,2,"0") || + ":" || right(sec,2,"0") +end + +procedure SecToDate(seconds,hoursFromGmt) #: convert seconds to &date +# +# Converts seconds past DateBaseYear to a &date in Icon date format +# (yyyy,mm,dd). +# + local r + r := SecToDateRec(seconds,hoursFromGmt) + return right(r.year,4,"0") || "/" || right(r.month,2,"0") || "/" || + right(r.day,2,"0") +end + +procedure SecToDateLine(seconds,hoursFromGmt) #: convert seconds to &dateline +# +# Produces a date in the same format as Icon's &dateline. +# + local d,hour,halfday + d := SecToDateRec(seconds,hoursFromGmt) + if (hour := d.hour) < 12 then { + halfday := "am" + } + else { + halfday := "pm" + hour -:= 12 + } + if hour = 0 then hour := 12 + return Days[d.weekday] || ", " || Months[d.month] || " " || d.day || + ", " || d.year || " " || hour || ":" || right(d.min,2,"0") || " " || + halfday +end + +procedure SecToDateRec(seconds,hoursFromGmt) +# +# Produces a date record computed from the seconds since the start of +# DateBaseYear. +# + local day,hour,min,month,secs,weekday,year + initial DateInit() + seconds := integer(seconds) | runerr(101,seconds) + /hoursFromGmt := HoursFromGmt + hoursFromGmt := integer(hoursFromGmt) | runerr(101,hoursFromGmt) + seconds +:= hoursFromGmt * 3600 + weekday := (seconds / 86400 % 7 + 4) % 7 + 1 + year := DateBaseYear + repeat { + secs := if IsLeapYear(year) then 31622400 else 31536000 + if seconds < secs then break + year +:= 1 + seconds -:= secs + } + month := 1 + every secs := + 2678400 | + (if IsLeapYear(year) then 2505600 else 2419200) | + 2678400 | + 2592000 | + 2678400 | + 2592000 | + 2678400 | + 2678400 | + 2592000 | + 2678400 | + 2592000 | + 2678400 do { + if seconds < secs then break + month +:= 1 + seconds -:= secs + } + day := seconds / 86400 + 1 + seconds %:= 86400 + hour := seconds / 3600 + seconds %:= 3600 + min := seconds / 60 + seconds %:= 60 + return DateRec(year,month,day,hour,min,seconds,weekday) +end + +procedure SecToUnixDate(seconds,hoursFromGmt) #: convert seconds to UNIX time +# +# Returns a date and time in UNIX format: Jan 14 10:24 1991 +# + local d + d := SecToDateRec(seconds,hoursFromGmt) + return Months[d.month][1+:3] || " " || d.day || " " || + d.hour || ":" || right(d.min,2,"0") || " " || d.year +end + +procedure IsLeapYear(year) #: determine if year is leap + # + # Fails unless year is a leap year. + # + return year % 4 = 0 & (year % 100 ~= 0 | year % 400 = 0) & &null +end + +procedure calendat(julian) #: Julian date + local ja, jalpha, jb, jc, jd, je, gregorian + local month, day, year + + gregorian := 2299161 + + if julian >= gregorian then { + jalpha := integer(((julian - 1867216) - 0.25) / 36524.25) + ja := julian + 1 + jalpha - integer(0.25 * jalpha) + } + else ja := julian + + jb := ja + 1524 + jc := integer(6680.0 + ((jb - 2439870) - 122.1) / 365.25) + jd := 365 * jc + integer(0.25 * jc) + je := integer((jb - jd) / 30.6001) + day := jb - jd - integer(30.6001 * je) + month := je - 1 + if month > 12 then month -:= 12 + year := jc - 4715 + if month > 2 then year -:= 1 + if year <= 0 then year -:= 1 + + return date1(month, day, year) + +end + +procedure date() #: date in natural English + + &dateline ? { + tab(find(", ") + 2) + return tab(find(" ")) + } + +end + +procedure dayoweek(day, month, year) #: day of the week +# +# The method used was adapted from a Web page by Mark Dettinger. +# URL as of 7 August 2000 was: +# http://www.informatik.uni-ulm.de/pm/mitarbeiter/mark/day_of_week.html +# + static d_code, c_code, m_code, ml_code, y, C, M, Y + + initial { + d_code := ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday"] + + c_code := table() + c_code[16] := c_code[20] := 0 + c_code[17] := c_code[21] := 6 + c_code[18] := c_code[22] := 4 + c_code[19] := c_code[23] := 2 + + m_code := table() + m_code[1] := m_code["January"] := 1 + m_code[2] := m_code["February"] := 4 + m_code[3] := m_code["March"] := 4 + m_code[4] := m_code["April"] := 0 + m_code[5] := m_code["May"] := 2 + m_code[6] := m_code["June"] := 5 + m_code[7] := m_code["July"] := 0 + m_code[8] := m_code["August"] := 3 + m_code[9] := m_code["September"] := 6 + m_code[10] := m_code["October"] := 1 + m_code[11] := m_code["November"] := 4 + m_code[12] := m_code["December"] := 6 + + ml_code := copy(m_code) + ml_code[1] := ml_code["January"] := 0 + ml_code[2] := ml_code["February"] := 3 + } + + # This can be fixed to go back to October 15, 1582. + + if year < 1600 then stop("*** can't compute day of week that far back") + + # This can be fixed to go indefinitely far into the future; the day of + # of the week repeats every 400 years. + + if year > 2299 then stop("*** can't compute day of week that far ahead") + + C := c_code[(year / 100) + 1] + y := year % 100 + Y := (y / 12) + (y % 12) + ((y % 12) / 4) + month := integer(month) + M := if (year % 4) = 0 then ml_code[month] else m_code[month] + + return d_code[(C + Y + M + day) % 7 + 1] + +end + +procedure full13th(year1, year2) #: full moons on Friday 13ths + local time_zone, jd, jday, fraction, jul + local year, month, julday, n, icon, day_of_week, c + + time_zone := -5.0 / 24.0 + + every year := year1 to year2 do { + every month := 1 to 12 do { + jday := julian(month, 13, year) + day_of_week := (jday + 1) % 7 + if day_of_week = 5 then { + n := integer(12.37 * (year - 1900 + integer((month - 0.5) / 12.0))) + icon := 0 + repeat { + jul := pom(n,2) + jd := jul.number + fraction := 24.0 * (jul.fraction + time_zone) + if (fraction < 0.0) then { + jd -:= 1 + fraction +:= 24.0 + } + if fraction > 12.0 then { + jd +:= 1 + fraction -:= 12.0 + } + else fraction +:= 12.0 + if jd = jday then { + suspend date2(month, year, fraction) + break + } + else { + c := if jday >= jd then 1 else -1 + if c = -icon then break + icon := c + n +:= c + } + } + } + } + } + +end + +procedure julian(month, day, year) #: Julian date + local jul, gregorian, ja, julian_year, julian_month + + gregorian := (15 + 31 * (10 + 12 * 1582)) + + if year = 0 then fail + if year < 0 then year +:= 1 + if month > 2 then { + julian_year := year + julian_month := month + 1 + } else { + julian_year := year - 1 + julian_month := month + 13 + } + jul := (integer(365.25 * julian_year) + integer(30.6001 * julian_month) + + day + 1720995) + if day + 31 * (month + 12 * year) >= gregorian then { + ja := integer(0.01 * julian_year) + jul +:= 2 - ja + integer(0.25 * ja) + } + + return jul + +end + +procedure pom(n, nph) #: phase of moon + local i, jd, fraction, radians + local am, as, c, t, t2, extra + + radians := &pi / 180 + + c := n + nph / 4.0 + t := c / 1236.85 + t2 := t * t + as := 359.2242 + 29.105356 * c + am := 306.0253 + 385.816918 * c + 0.010730 * t2 + jd := 2415020 + 28 * n + 7 * nph + extra := 0.75933 + 1.53058868 * c + ((1.178e-4) - (1.55e-7) * t) * t2 + + if nph = (0 | 2) then + extra +:= (0.1734 - 3.93e-4 * t) * sin(radians * as) - 0.4068 * + sin(radians * am) + else if nph = (1 | 3) then + extra +:= (0.1721 - 4.0e-4 * t) * sin(radians * as) - 0.6280 * + sin(radians * am) + else fail + + if extra >= 0 then i := integer(extra) + else i := integer(extra - 1.0) + jd +:= i + fraction := extra - i + + return jdate(integer(jd), fraction) + +end + +procedure saytime(time) #: time in natural English + local hour,min,mod,near,numbers,out,sec + # + # Extract the hours, minutes, and seconds from the time. + # + /time := &clock + time ? { + hour := integer(tab(find(":") | 0)) | fail + move(1) + min := tab(find(":") | 0) + move(1) + sec := tab(0) + } + min := integer(min) | 0 + sec := integer(sec) | 0 + # + # Now start the processing in earnest. + # + near := ["just gone","just after","nearly","almost"] + if sec > 29 then min +:= 1 # round up minutes + mod := min % 5 # where we are in 5 minute bracket + out := near[mod] || " " | "" # start building the result + if min > 32 then hour +:= 1 # we are TO the hour + min +:= 2 # shift minutes to straddle the 5-minute point + # + # Now special-case the result for Noon and Midnight hours. + # + if hour % 12 = 0 & min % 60 <= 4 then { + return if hour = 12 then out || "noon" + else out || "midnight" + } + min -:= min % 5 # find the nearest 5 mins + if hour > 12 then hour -:= 12 # get rid of 25-hour clock + else if hour = 0 then hour := 12 # .. and allow for midnight + # + # Determine the phrase to use for each 5-minute segment. + # + case min of { + 0: {} # add "o'clock" later + 60: min=0 # ditto + 5: out ||:= "five past" + 10: out ||:= "ten past" + 15: out ||:= "a quarter past" + 20: out ||:= "twenty past" + 25: out ||:= "twenty-five past" + 30: out ||:= "half past" + 35: out ||:= "twenty five to" + 40: out ||:= "twenty to" + 45: out ||:= "a quarter to" + 50: out ||:= "ten to" + 55: out ||:= "five to" + } + numbers := ["one","two","three","four","five","six", + "seven","eight","nine","ten","eleven","twelve"] + out ||:= (if *out = 0 then "" else " ") || numbers[hour] + # add the hour number + if min = 0 then out ||:= " o'clock" # .. and o'clock if exact + return out # return the final result +end + +procedure walltime() #: time since midnight + local seconds + + &clock ? { + seconds := tab(upto(':')) * 3600 # seconds in a hour + move(1) + seconds +:= tab(upto(':')) * 60 # seconds in a minute + move(1) + return seconds + tab(0) + } + +end |