Module: system-internals Author: Gary Palter Synopsis: Date/Time intervals (Durations) and related mathematical functionality Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND /// We're following the SQL model of having two distinct classes of s. /// The first class represents an interval that spans months. /// The second class represents an interval that spans days. /// While the two classes are both s, they are, in essence, disjoint /// in that you can't perform arithmetic on one instance of each class. define abstract class () slot %duration-months :: = 0; slot %duration-days :: = 0; slot %duration-seconds :: = 0; slot %duration-microseconds :: = 0; end class ; define class () end class ; define class () end class ; define sealed domain \= (, ); define sealed domain \< (, ); define sealed domain \+ (, ); define sealed domain \+ (, ); define sealed domain \+ (, ); define sealed domain \- (, ); define sealed domain \- (, ); define sealed domain \- (, ); define sealed domain \* (, ); define sealed domain \* (, ); define sealed domain \/ (, ); define method make (class == , #rest init-keywords, #key years :: false-or() = #f, months :: false-or() = #f, days :: false-or() = #f, hours :: false-or() = #f, minutes :: false-or() = #f, seconds :: false-or() = #f, microseconds :: false-or() = #f, #all-keys) => (duration :: ) case years | months => if (days | hours | minutes | seconds | microseconds) error("Can't make() with both years/months and days/hours/minutes/seconds") end; apply(make, , init-keywords); days | hours | minutes | seconds | microseconds => apply(make, , init-keywords); otherwise => error ("Either years/months or days/hours/minutes/seconds must be given to make()" ) end end method make; define method initialize (duration :: , #key years :: = 0, months :: = 0, #all-keys) => (#rest objects) %duration-months(duration) := 12 * years + months; duration end method initialize; define method initialize (duration :: , #key days :: = 0, hours :: = 0, minutes :: = 0, seconds :: = 0, microseconds :: = 0, #all-keys) => (#rest objects) %duration-days(duration) := days; %duration-seconds(duration) := seconds + 60 * (minutes + 60 * hours); %duration-microseconds(duration) := microseconds; canonicalize-duration(duration) end method initialize; /// Utility functions define sealed inline method clone-duration (duration :: ) => (new :: ) make(, months: %duration-months(duration)) end method clone-duration; define sealed inline method clone-duration (duration :: ) => (new :: ) make(, days: %duration-days(duration), seconds: %duration-seconds(duration), microseconds: %duration-microseconds(duration)) end method clone-duration; /// Ensure that all fields of a have proper, canonical values by adjusting /// the other fields to compensate for out of range values produced by the operations /// defined below or supplied by the user when creating the . define inline function canonicalize-duration (duration :: ) => (duration :: ) unless (-1000000 < %duration-microseconds(duration) & %duration-microseconds(duration) < 1000000) let (seconds-change, new-microsecs) = floor/(%duration-microseconds(duration), 1000000); %duration-microseconds(duration) := new-microsecs; %duration-seconds(duration) := %duration-seconds(duration) + seconds-change end; unless (-86400 < %duration-seconds(duration) & %duration-seconds(duration) < 86400) let (days-change, new-seconds) = floor/(%duration-seconds(duration), 86400); %duration-seconds(duration) := new-seconds; %duration-days(duration) := %duration-days(duration) + days-change end; unless (negative?(%duration-seconds(duration)) = negative?(%duration-days(duration)) | zero?(%duration-seconds(duration)) | zero?(%duration-days(duration))) let negative-seconds? :: = negative?(%duration-seconds(duration)); %duration-days(duration) := %duration-days(duration) + if (negative-seconds?) -1 else 1 end; %duration-seconds(duration) := %duration-seconds(duration) + if (negative-seconds?) 86400 else -86400 end end; unless (negative?(%duration-microseconds(duration)) = negative?(%duration-seconds(duration)) | zero?(%duration-microseconds(duration)) | zero?(%duration-seconds(duration))) let negative-microseconds? :: = negative?(%duration-microseconds(duration)); %duration-seconds(duration) := %duration-seconds(duration) + if (negative-microseconds?) -1 else 1 end; %duration-microseconds(duration) := %duration-microseconds(duration) + if (negative-microseconds?) 1000000 else -1000000 end end; duration end function canonicalize-duration; /// Errors and restarts define abstract class () constant slot dae-year :: , required-init-keyword: year:; constant slot dae-month :: , required-init-keyword: month:; constant slot dae-day :: , init-keyword: day:; end class ; define abstract class (, ) end class ; define class () required keyword day:; end class ; define sealed method condition-to-string (condition :: ) => (description :: ) format-to-string("%s, %d does not have %d days", $month-names[dae-month(condition) - 1], dae-year(condition), dae-day(condition)) end method condition-to-string; define sealed method return-allowed? (condition :: ) => (return-allowed? :: ) #t end method return-allowed?; define class () end class ; define sealed method condition-to-string (condition :: ) => (description :: ) format-to-string("Use %s %d, %d", $month-names[dae-month(condition) - 1], days-in-month(dae-year(condition), dae-month(condition)), dae-year(condition)) end method condition-to-string; define sealed method return-description (condition :: ) => (description :: ) make(, year: dae-year(condition), month: dae-month(condition)) end method return-description; /// Exported interfaces define inline function encode-year/month-duration (years :: , months :: ) => (duration :: ) make(, years: years, months: months) end function encode-year/month-duration; define inline function encode-day/time-duration (days :: , hours :: , minutes :: , seconds :: , microseconds :: ) => (duration :: ) make(, days: days, hours: hours, minutes: minutes, seconds: seconds, microseconds: microseconds) end function encode-day/time-duration; define sealed generic decode-duration (duration :: ) => (#rest components :: ); define sealed method decode-duration (duration :: ) => (years :: , months :: ) truncate/(%duration-months(duration), 12) end method decode-duration; define sealed method decode-duration (duration :: ) => (days :: , hours :: , minutes :: , seconds :: , microseconds :: ) let (minutes, seconds) = truncate/(%duration-seconds(duration), 60); let (hours, minutes) = truncate/(minutes, 60); values(%duration-days(duration), hours, minutes, seconds, %duration-microseconds(duration)) end method decode-duration; define sealed method \= (x :: , y :: ) => (equals? :: ) %duration-months(x) = %duration-months(y) & %duration-days(x) = %duration-days(y) & %duration-seconds(x) = %duration-seconds(y) & %duration-microseconds(x) = %duration-microseconds(y) end method \=; define sealed method \< (x :: , y :: ) => (less? :: ) %duration-months(x) < %duration-months(y) end method \<; define sealed method \< (x :: , y :: ) => (less? :: ) %duration-days(x) < %duration-days(y) | (%duration-days(x) = %duration-days(y) & (%duration-seconds(x) < %duration-seconds(y) | (%duration-seconds(x) = %duration-seconds(y) & %duration-microseconds(x) < %duration-microseconds(y)))) end method \<; /// Ensure that a 's month and year have proper, canonical values by adjusting /// them to compensate for out of range values produced by adding a . /// If the resulting is invalid (e.g., February 30th), signal a proceedable /// error; if the user proceeds, set the to the last valid date in the month. define inline function canonicalize-date-month-year (date :: ) => (date :: ) let month :: = date-month(date) - 1; unless (-1 < month & month < 12) let (year-change, new-month) = floor/(month, 12); date-month(date) := new-month + 1; date-year(date) := date-year(date) + year-change end; unless (date-day(date) <= days-in-month(date-year(date), date-month(date))) block () signal(make(, year: date-year(date), month: date-month(date), day: date-day(date))); exception(, init-arguments: vector(year: date-year(date), month: date-month(date))) date-day(date) := days-in-month(date-year(date), date-month(date)) end; end; date end function canonicalize-date-month-year; define inline-only function add-duration-to-date (x :: , y :: ) => (date :: ) let new :: = clone-date(x); date-month(new) := date-month(new) + %duration-months(y); canonicalize-date-month-year(new); date-day(new) := date-day(new) + %duration-days(y); date-seconds(new) := date-seconds(new) + %duration-seconds(y); date-microseconds(new) := date-microseconds(new) + %duration-microseconds(y); canonicalize-date(new) end function add-duration-to-date; define sealed method \+ (x :: , y :: ) => (date :: ) add-duration-to-date(x, y) end method \+; define sealed method \+ (x :: , y :: ) => (date :: ) add-duration-to-date(y, x) end method \+; define sealed method \- (x :: , y :: ) => (duration :: ) make(, days: date-universal-date(x) - date-universal-date(y), seconds: date-universal-time(x) - date-universal-time(y), microseconds: date-microseconds(x) - date-microseconds(y)) end method \-; define sealed method \- (x :: , y :: ) => (date :: ) let new :: = clone-date(x); date-month(new) := date-month(new) - %duration-months(y); canonicalize-date-month-year(new); date-day(new) := date-day(new) - %duration-days(y); date-seconds(new) := date-seconds(new) - %duration-seconds(y); date-microseconds(new) := date-microseconds(new) - %duration-microseconds(y); canonicalize-date(new) end method \-; define inline-only function add-durations (x :: , y :: ) => (duration :: ) let new :: = clone-duration(x); %duration-months(new) := %duration-months(new) + %duration-months(y); %duration-days(new) := %duration-days(new) + %duration-days(y); %duration-seconds(new) := %duration-seconds(new) + %duration-seconds(y); %duration-microseconds(new) := %duration-microseconds(new) + %duration-microseconds(y); canonicalize-duration(new) end function add-durations; define sealed method \+ (x :: , y :: ) => (duration :: ) add-durations(x, y) end method \+; define sealed method \+ (x :: , y :: ) => (duration :: ) add-durations(x, y) end method \+; define inline-only function subtract-durations (x :: , y :: ) => (duration :: ) let new :: = clone-duration(x); %duration-months(new) := %duration-months(new) - %duration-months(y); %duration-days(new) := %duration-days(new) - %duration-days(y); %duration-seconds(new) := %duration-seconds(new) - %duration-seconds(y); %duration-microseconds(new) := %duration-microseconds(new) - %duration-microseconds(y); canonicalize-duration(new) end function subtract-durations; define sealed method \- (x :: , y :: ) => (duration :: ) subtract-durations(x, y) end method \-; define sealed method \- (x :: , y :: ) => (duration :: ) subtract-durations(x, y) end method \-; define inline-only function scale-duration (x :: , y :: ) => (duration :: ) let new :: = clone-duration(x); %duration-months(new) := round(y * %duration-months(new)); %duration-days(new) := round(y * %duration-days(new)); %duration-seconds(new) := round(y * %duration-seconds(new)); %duration-microseconds(new) := round(y * %duration-microseconds(new)); canonicalize-duration(new) end function scale-duration; define sealed method \* (x :: , y :: ) => (duration :: ) scale-duration(x, y) end method \*; define sealed method \* (x :: , y :: ) => (duration :: ) scale-duration(y, x) end method \*; define sealed method \/ (x :: , y :: ) => (duration :: ) let new :: = clone-duration(x); %duration-months(new) := round/(%duration-months(new), y); %duration-days(new) := round/(%duration-days(new), y); %duration-seconds(new) := round/(%duration-seconds(new), y); %duration-microseconds(new) := round/(%duration-microseconds(new), y); canonicalize-duration(new) end method \/;