(*
Class: WayDate
Package: com.waysysweb.util
Author: W. Shaffer
Date: 28-Nov-1997
Description:
This structure provides specifications for algorithms for manipulating
dates.
Maintenance:
Date Author Description
----------- ------ ----------------------------------------------------------------
28-Nov-1997 Shaffer File created
23-Feb-1998 Shaffer Day Positions added to support time zones
12-Mar-1998 Shaffer Day Position functions added to support daylight savings time
30-Jan-2000 Shaffer Adjustment to accommodate Version 1.44 of Moscow ML
31-May-2003 Shaffer Addition of age and currentAge functions
01-Aug-2005 Shaffer Addition of daysInPolicyYear for insurance applications
04-Apr-2006 Shaffer Added minDate function
05-Apr-2006 Shaffer Added anniversary methods
11-Nov-2006 Shaffer Moved holiday functions to separate structure
07-Jan-2007 Shaffer Moved insurance date manipulations to separate structure
*)
structure WAYDATE = struct
(*============ Exceptions ==============================================*)
exception DateError;
(*============ Types ==============================================*)
type Month = int;
type Day = int;
type Year = int;
type DayOfYear = int;
(* Days of the Week *)
datatype DayOfWeek = Sunday | Monday | Tuesday | Wednesday | Thursday |
Friday | Saturday;
(*============ Constants ==============================================*)
(* Maximum allowed year *)
val MaxYear : Year = 3999;
(* Minimum allowed year *)
val MinYear : Year = 1601;
(* Convert Day of Week to int *)
fun numDayOfWeek(Sunday) : int = 0
| numDayOfWeek(Monday) : int = 1
| numDayOfWeek(Tuesday) : int = 2
| numDayOfWeek(Wednesday) : int = 3
| numDayOfWeek(Thursday) : int = 4
| numDayOfWeek(Friday) : int = 5
| numDayOfWeek(Saturday) : int = 6;
(* Convert int to Day of Week *)
fun dayOfWeekEnum(0) = Sunday
| dayOfWeekEnum(1) = Monday
| dayOfWeekEnum(2) = Tuesday
| dayOfWeekEnum(3) = Wednesday
| dayOfWeekEnum(4) = Thursday
| dayOfWeekEnum(5) = Friday
| dayOfWeekEnum(6) = Saturday
| dayOfWeekEnum(_) = raise DateError;
(* Days of the month: non-leap year *)
fun daysNonLeapYear(1) = 31 (* January *)
| daysNonLeapYear(2) = 28 (* February *)
| daysNonLeapYear(3) = 31 (* March *)
| daysNonLeapYear(4) = 30 (* April *)
| daysNonLeapYear(5) = 31 (* May *)
| daysNonLeapYear(6) = 30 (* June *)
| daysNonLeapYear(7) = 31 (* July *)
| daysNonLeapYear(8) = 31 (* August *)
| daysNonLeapYear(9) = 30 (* September*)
| daysNonLeapYear(10)= 31 (* October *)
| daysNonLeapYear(11)= 30 (* November *)
| daysNonLeapYear(12)= 31 (* December *)
| daysNonLeapYear(_) = raise DateError;
(* Days of the month: leap year *)
fun daysLeapYear(1) = 31 (* January *)
| daysLeapYear(2) = 29 (* February *)
| daysLeapYear(3) = 31 (* March *)
| daysLeapYear(4) = 30 (* April *)
| daysLeapYear(5) = 31 (* May *)
| daysLeapYear(6) = 30 (* June *)
| daysLeapYear(7) = 31 (* July *)
| daysLeapYear(8) = 31 (* August *)
| daysLeapYear(9) = 30 (* September*)
| daysLeapYear(10)= 31 (* October *)
| daysLeapYear(11)= 30 (* November *)
| daysLeapYear(12)= 31 (* December *)
| daysLeapYear(_) = raise DateError;
(* The abbreviations of months *)
fun mnthAbbrev(1) = "Jan"
| mnthAbbrev(2) = "Feb"
| mnthAbbrev(3) = "Mar"
| mnthAbbrev(4) = "Apr"
| mnthAbbrev(5) = "May"
| mnthAbbrev(6) = "Jun"
| mnthAbbrev(7) = "Jul"
| mnthAbbrev(8) = "Aug"
| mnthAbbrev(9) = "Sep"
| mnthAbbrev(10) = "Oct"
| mnthAbbrev(11) = "Nov"
| mnthAbbrev(12) = "Dec"
| mnthAbbrev(_) = raise DateError;
(* Is leap year *)
fun isLeapYear(yr : Year) : bool =
if ( (yr mod 400) = 0) orelse (( (yr mod 4) = 0) andalso ((yr mod 100) <> 0))
then true
else false;
(* Days in year *)
fun daysInYear(yr : Year) : DayOfYear =
if isLeapYear(yr)
then 366
else 365;
val MaxDate = (12, 31, MaxYear);
val MinDate = (01, 01, MinYear);
(*============ Properties ==============================================*)
(* Valid month *)
fun isMonth(mnth : Month) : bool =
if (mnth > 0) andalso (mnth <= 12)
then true
else false;
(* Valid year *)
fun isYear(yr : Year) : bool =
if (yr >= MinYear) andalso (yr <= MaxYear)
then true
else false;
(* Days in month *)
fun daysInMonth(mnth : Month, yr : Year) : Day =
if isLeapYear(yr)
then daysLeapYear(mnth)
else daysNonLeapYear(mnth);
(* Is valid day *)
fun isValidDay(mnth : Month, dy : Day, yr : Year) : bool =
if (dy > 0) andalso (dy <= daysInMonth(mnth, yr))
then true
else false;
(* Valid date *)
fun isValidDate(mnth : Month, dy : Day, yr : Year) : bool =
if isMonth(mnth) andalso isYear(yr) andalso isValidDay(mnth, dy, yr)
then true
else false;
(* Is day of year *)
fun isDayOfYear(dayOfYear : DayOfYear, yr : Year) : bool =
if (dayOfYear > 0) andalso (dayOfYear <= daysInYear(yr))
then true
else false;
(*============ Attributes ==============================================*)
type WayDate = Month * Day * Year;
(*============ Invariant ==============================================*)
fun inv_WayDate(mn : Month, dy : Day, yr : Year) : bool =
isValidDate(mn, dy, yr);
(*============ Initialization ===========================================*)
(* Create a date *)
(* Precondition: (none) *)
fun create(mnth : Month, dy : Day, yr : Year) : WayDate =
if isValidDate(mnth, dy, yr)
then (mnth, dy, yr)
else raise DateError;
(* Return today's date *)
fun today() : WayDate =
let
val aDate =
Date.fromTimeLocal(TIME.now());
fun fromMonth(DATE.Jan) = 1
| fromMonth(DATE.Feb) = 2
| fromMonth(DATE.Mar) = 3
| fromMonth(DATE.Apr) = 4
| fromMonth(DATE.May) = 5
| fromMonth(DATE.Jun) = 6
| fromMonth(DATE.Jul) = 7
| fromMonth(DATE.Aug) = 8
| fromMonth(DATE.Sep) = 9
| fromMonth(DATE.Oct) = 10
| fromMonth(DATE.Nov) = 11
| fromMonth(DATE.Dec) = 12;
in
create(fromMonth(Date.month aDate), Date.day aDate, Date.year aDate)
end;
(*============ Operations ==============================================*)
(* Obtain attributes *)
fun getDay(_, dy, _) : Day = dy; (* day part of date *)
fun getMonth(mnth, _, _) : Month = mnth; (* month part of date*)
fun getYear(_, _, yr) : Year = yr; (* year part of date *)
(* Sum the days of the month *)
fun sumDaysOfMonth( 1, yr : Year) : int = daysInMonth(1, yr)
| sumDaysOfMonth(mnth : Month, yr : Year) : int =
daysInMonth(mnth, yr) + sumDaysOfMonth(mnth-1, yr);
(* Day of year *)
fun dayOfYear( 1, dy : Day, _ ) : DayOfYear =
dy
| dayOfYear(mnth : Month, dy : Day, yr : Year) : DayOfYear =
sumDaysOfMonth(mnth - 1, yr) + dy;
(* Days in prior years *)
fun daysInPriorYears(1601) : int = 0
| daysInPriorYears(yr : Year) : int =
daysInYear(yr - 1) + daysInPriorYears(yr - 1);
(* Add one day to the date *)
fun increment(12, 31, 3999) : WayDate =
raise DateError
| increment(12, 31, yr : Year) : WayDate =
(1, 1, yr + 1)
| increment(mnth : Month, dy : Day, yr : Year) : WayDate =
if dy = daysInMonth(mnth, yr)
then (mnth + 1, 1, yr)
else (mnth , dy + 1, yr);
(* Subtract one day from the date *)
fun decrement(1, 1, 1601) : WayDate =
raise DateError
| decrement(1, 1, yr : Month) : WayDate =
(12, 31, yr - 1)
| decrement(mnth : Month, 1, yr : Year) : WayDate =
(mnth - 1, daysInMonth(mnth - 1, yr), yr)
| decrement(mnth : Month, dy : Day, yr : Year) : WayDate =
(mnth, dy - 1, yr);
(* Add a number of days to a date *)
fun add(date1 : WayDate, value : int) : WayDate =
if value = 0
then date1
else
if value < 0
then add(decrement(date1), value + 1)
else add(increment(date1), value - 1);
(* Convert Julian to Gregorian Date *)
fun convertGregorian(dayInYear : DayOfYear, yr : Year) : WayDate =
if isDayOfYear(dayInYear, yr)
then add(create(1, 1, yr), dayInYear - 1)
else raise DateError;
(* Compare *)
fun compare(date1 : WayDate, date2 : WayDate) : order =
let
val yr1 = getYear(date1)
val yr2 = getYear(date2)
val mnth1 = getMonth(date1)
val mnth2 = getMonth(date2)
val dy1 = getDay(date1)
val dy2 = getDay(date2)
in
if yr1 < yr2 then LESS
else if yr1 = yr2 andalso mnth1 < mnth2 then LESS
else if yr1 = yr2 andalso mnth1 = mnth2 andalso dy1 < dy2 then LESS
else if yr1 = yr2 andalso mnth1 = mnth2 andalso dy1 = dy2 then EQUAL
else GREATER
end;
(* isEqual - are two dates equal *)
fun isEqual(date1 : WayDate, date2 : WayDate) : bool =
if (compare(date1, date2) = EQUAL) then true
else false;
(* Return the earliest of two dates *)
fun minDate(date1 : WayDate, date2 : WayDate) : WayDate =
case compare(date1, date2) of
LESS => date1
| EQUAL => date1
| GREATER => date2;
(* Return the later of two dates *)
fun maxDate(date1 : WayDate, date2 : WayDate) : WayDate =
case compare(date1, date2) of
LESS => date2
| EQUAL => date2
| GREATER => date1;
(* is in period *)
fun isInPeriod(begin_date : WayDate, end_date : WayDate, date1 : WayDate) : bool =
let
val bgn_cmp = compare(begin_date, date1);
val end_cmp = compare(end_date , date1);
in
if bgn_cmp = GREATER then false
else if end_cmp = LESS then false
else true
end;
(* Date Difference: date1 - date2 *)
fun difference(date1 : WayDate, date2 : WayDate) : int =
case compare(date1, date2) of
EQUAL => 0
| LESS => difference(increment(date1), date2) - 1
| GREATER => difference(decrement(date1), date2) + 1;
(* Day of week *)
(* This specification is based on 1-1-1601 being a Monday (1) *)
(* Precondition: isValidDate(getMonth(date1), getDay(date1), getYear(date1)) *)
fun dayOfWeek(date1 : WayDate) : DayOfWeek =
dayOfWeekEnum((difference(date1, (1, 1, MinYear)) + 1) mod 7);
(* Convert a date to a string *)
fun toString(mnth : Month, dy : Day, yr : Year) : string =
makestring(dy) ^ "-" ^ mnthAbbrev(mnth) ^ "-" ^ makestring(yr);
end (* WAYDATE *)