From 38f8cf3544615efabc0913ebd00fdf6053cf294d Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Thu, 14 Dec 2006 03:36:00 +0000 Subject: [PATCH] Implement DATESUM, DATEDIFF functions. Patch #5637. --- configure.ac | 2 +- doc/expressions.texi | 85 ++++++-- src/data/ChangeLog | 4 + src/data/calendar.c | 11 + src/data/calendar.h | 2 + src/language/expressions/ChangeLog | 23 ++ src/language/expressions/helpers.c | 279 ++++++++++++++++++++++++ src/language/expressions/helpers.h | 5 + src/language/expressions/operations.def | 13 +- tests/ChangeLog | 4 + tests/expressions/expressions.sh | 257 ++++++++++++++++++++++ 11 files changed, 658 insertions(+), 27 deletions(-) diff --git a/configure.ac b/configure.ac index 0295d7fa..38eee691 100644 --- a/configure.ac +++ b/configure.ac @@ -73,7 +73,7 @@ AC_DEFINE(FPREP_IEEE754, 1, AC_C_BIGENDIAN AC_FUNC_VPRINTF -AC_CHECK_FUNCS([__setfpucw isinf isnan finite getpid feholdexcept round]) +AC_CHECK_FUNCS([__setfpucw isinf isnan finite getpid feholdexcept round trunc]) AC_PROG_LN_S diff --git a/doc/expressions.texi b/doc/expressions.texi index c6c96dfb..540cad22 100644 --- a/doc/expressions.texi +++ b/doc/expressions.texi @@ -260,6 +260,7 @@ The sections below describe each function in detail. * String Functions:: CONCAT INDEX LENGTH LOWER LPAD LTRIM NUMBER RINDEX RPAD RTRIM STRING SUBSTR UPCASE * Time & Date:: CTIME.xxx DATE.xxx TIME.xxx XDATE.xxx + DATEDIFF DATESUM * Miscellaneous Functions:: LAG YRMODA VALUELABEL * Statistical Distribution Functions:: PDF CDF SIG IDF RV NPDF NCDF @end menu @@ -708,6 +709,7 @@ Most time and date functions will not accept earlier dates. * Date Extraction:: XDATE.@{DATE HOUR JDAY MDAY MINUTE MONTH QUARTER SECOND TDAY TIME WEEK WKDAY YEAR@} +* Time & Date Arithmetic:: DATEDIFF DATESUM @end menu @node Time & Date Concepts @@ -741,27 +743,6 @@ given below correspond with the numeric PSPP dates given: 24 Aug 1995 13,028,601,600 @end example -@cindex time, mathematical properties of -@cindex mathematics, applied to times & dates -@cindex dates, mathematical properties of -@noindent -Ordinary arithmetic operations on dates and times often produce -sensible results. Adding a time to, or subtracting one from, a date -produces a new date that much earlier or later. The difference of two -dates yields the time between those dates. Adding two times produces -the combined time. Multiplying a time by a scalar produces a time -that many times longer. Since times and dates are just numbers, the -ordinary addition and subtraction operators are employed for these -purposes. - -Adding two dates does not produce a useful result. - -As the table shows, dates and times may have very large values. Thus, -it is not a good idea to take powers of these values; also, the -accuracy of some procedures may be affected. If necessary, convert -times or dates in seconds to some other unit, like days or years, -before performing analysis. - @node Time Construction @subsubsection Functions that Produce Times @cindex times, constructing @@ -1020,6 +1001,68 @@ Returns the year (as an integer 1582 or greater) corresponding to @var{date}. @end deftypefn +@node Time & Date Arithmetic +@subsubsection Time and Date Arithmetic + +@cindex time, mathematical properties of +@cindex mathematics, applied to times & dates +@cindex dates, mathematical properties of +@noindent +Ordinary arithmetic operations on dates and times often produce +sensible results. Adding a time to, or subtracting one from, a date +produces a new date that much earlier or later. The difference of two +dates yields the time between those dates. Adding two times produces +the combined time. Multiplying a time by a scalar produces a time +that many times longer. Since times and dates are just numbers, the +ordinary addition and subtraction operators are employed for these +purposes. + +Adding two dates does not produce a useful result. + +Dates and times may have very large values. Thus, +it is not a good idea to take powers of these values; also, the +accuracy of some procedures may be affected. If necessary, convert +times or dates in seconds to some other unit, like days or years, +before performing analysis. + +PSPP supplies a few functions for date arithmetic: + +@deftypefn {Function} {} DATEDIFF (@var{date1}, @var{date2}, @var{unit}) +Returns the span of time from @var{date1} to @var{date2} in terms of +@var{unit}, which must be a quoted string, one of @samp{years}, +@samp{quarters}, @samp{months}, @samp{weeks}, @samp{days}, +@samp{hours}, @samp{minutes}, and @samp{seconds}. The result is an +integer, truncated toward zero. + +One year is considered to span from a given date to the same month, +day, and time of day the next year. Thus, from Jan.@tie{}1 of one +year to Jan.@tie{}1 the next year is considered to be a full year, but +Feb.@tie{}29 of a leap year to the following Feb.@tie{}28 is not. +Similarly, one month spans from a given day of the month to the same +day of the following month. Thus, there is never a full month from +Jan.@tie{}31 of a given year to any day in the following February. +@end deftypefn + +@deftypefn {Function} {} DATESUM (@var{date}, @var{quantity}, @var{unit}[, @var{method}]) +Returns @var{date} advanced by the given @var{quantity} of the +specified @var{unit}, which must be one of the strings @samp{years}, +@samp{quarters}, @samp{months}, @samp{weeks}, @samp{days}, +@samp{hours}, @samp{minutes}, and @samp{seconds}. + +When @var{unit} is @samp{years}, @samp{quarters}, or @samp{months}, +only the integer part of @var{quantity} is considered. Adding one of +these units can cause the day of the month to exceed the number of +days in the month. In this case, the @var{method} comes into +play: if it is omitted or specified as @samp{closest} (as a quoted +string), then the resulting day is the last day of the month; +otherwise, if it is specified as @samp{rollover}, then the extra days +roll over into the following month. + +When @var{unit} is @samp{weeks}, @samp{days}, @samp{hours}, +@samp{minutes}, or @samp{seconds}, the @var{quantity} is not rounded +to an integer and @var{method}, if specified, is ignored. +@end deftypefn + @node Miscellaneous Functions @subsection Miscellaneous Functions @cindex functions, miscellaneous diff --git a/src/data/ChangeLog b/src/data/ChangeLog index dacd90ac..e8874962 100644 --- a/src/data/ChangeLog +++ b/src/data/ChangeLog @@ -1,3 +1,7 @@ +Wed Dec 13 19:30:11 2006 Ben Pfaff + + * calendar.c (calendar_days_in_month): New function. + Mon Dec 11 07:53:39 2006 Ben Pfaff * value-labels.c (hash_int_val_lab): Only hash as many bytes as diff --git a/src/data/calendar.c b/src/data/calendar.c index 3a38f126..c3d9d8e6 100644 --- a/src/data/calendar.c +++ b/src/data/calendar.c @@ -210,3 +210,14 @@ calendar_offset_to_mday (int ofs) calendar_offset_to_gregorian (ofs, &y, &m, &d, &yd); return d; } + +/* Returns the number of days in the specified month. */ +int +calendar_days_in_month (int y, int m) +{ + static const int days_per_month[12] + = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + assert (m >= 1 && m <= 12); + return m == 2 && is_leap_year (y) ? 29 : days_per_month[m - 1]; +} diff --git a/src/data/calendar.h b/src/data/calendar.h index f46e71fe..06d2fd3a 100644 --- a/src/data/calendar.h +++ b/src/data/calendar.h @@ -12,4 +12,6 @@ int calendar_offset_to_mday (int ofs); int calendar_offset_to_yday (int ofs); int calendar_offset_to_wday (int ofs); +int calendar_days_in_month (int y, int m); + #endif /* calendar.h */ diff --git a/src/language/expressions/ChangeLog b/src/language/expressions/ChangeLog index cd1ef123..a55b977f 100644 --- a/src/language/expressions/ChangeLog +++ b/src/language/expressions/ChangeLog @@ -1,3 +1,26 @@ +Wed Dec 13 19:33:52 2006 Ben Pfaff + +Wed Dec 13 19:30:26 2006 Ben Pfaff + + Implement support for DATESUM, DATEDIFF expression functions. See + patch #5637. + + * helpers.c (enum date_unit): New enum. + [!HAVE_TRUNC] (trunc): New function. + (recognize_unit): New function. + (year_diff): New function. + (month_diff): New function. + (quarter_diff): New function. + (date_unit_duration): New function. + (expr_date_difference): New function. + (enum date_sum_method): New function. + (recognize_method): New function. + (add_months): New function. + (expr_date_sum): New function. + + * operations.def: Implement DATESUM, DATEDIFF functions. Mark + VALUELABEL no_abbrev. + Sun Dec 10 16:49:33 2006 Ben Pfaff * operations.def: Implement VALUELABEL function. Add DATEDIFF, diff --git a/src/language/expressions/helpers.c b/src/language/expressions/helpers.c index bddc7126..a07282be 100644 --- a/src/language/expressions/helpers.c +++ b/src/language/expressions/helpers.c @@ -2,6 +2,7 @@ #include "helpers.h" #include #include +#include #include #include "private.h" @@ -116,6 +117,284 @@ expr_yrmoda (double year, double month, double day) return expr_ymd_to_ofs (year, month, day); } + +/* A date unit. */ +enum date_unit + { + DATE_YEARS, + DATE_QUARTERS, + DATE_MONTHS, + DATE_WEEKS, + DATE_DAYS, + DATE_HOURS, + DATE_MINUTES, + DATE_SECONDS + }; + +#ifndef HAVE_TRUNC +/* Return X rounded toward zero. */ +static double +trunc (double x) +{ + return x >= 0.0 ? floor (x) : ceil (x); +} +#endif /* !HAVE_TRUNC */ + +/* Stores in *UNIT the unit whose name is NAME. + Return success. */ +static enum date_unit +recognize_unit (struct substring name, enum date_unit *unit) +{ + struct unit_name + { + enum date_unit unit; + const struct substring name; + }; + static const struct unit_name unit_names[] = + { + { DATE_YEARS, SS_LITERAL_INITIALIZER ("years") }, + { DATE_QUARTERS, SS_LITERAL_INITIALIZER ("quarters") }, + { DATE_MONTHS, SS_LITERAL_INITIALIZER ("months") }, + { DATE_WEEKS, SS_LITERAL_INITIALIZER ("weeks") }, + { DATE_DAYS, SS_LITERAL_INITIALIZER ("days") }, + { DATE_HOURS, SS_LITERAL_INITIALIZER ("hours") }, + { DATE_MINUTES, SS_LITERAL_INITIALIZER ("minutes") }, + { DATE_SECONDS, SS_LITERAL_INITIALIZER ("seconds") }, + }; + const int unit_name_cnt = sizeof unit_names / sizeof *unit_names; + + const struct unit_name *un; + + for (un = unit_names; un < &unit_names[unit_name_cnt]; un++) + if (ss_equals_case (un->name, name)) + { + *unit = un->unit; + return true; + } + + msg (SE, _("Unrecognized date unit \"%.*s\". " + "Valid date units are \"years\", \"quarters\", \"months\", " + "\"weeks\", \"days\", \"hours\", \"minutes\", and \"seconds\"."), + (int) ss_length (name), ss_data (name)); + return false; +} + +/* Returns the number of whole years from DATE1 to DATE2, + where a year is defined as the same or later month, day, and + time of day. */ +static int +year_diff (double date1, double date2) +{ + int y1, m1, d1, yd1; + int y2, m2, d2, yd2; + int diff; + + assert (date2 >= date1); + calendar_offset_to_gregorian (date1 / DAY_S, &y1, &m1, &d1, &yd1); + calendar_offset_to_gregorian (date2 / DAY_S, &y2, &m2, &d2, &yd2); + + diff = y2 - y1; + if (diff > 0) + { + int yd1 = 32 * m1 + d1; + int yd2 = 32 * m2 + d2; + if (yd2 < yd1 + || (yd2 == yd1 && fmod (date2, DAY_S) < fmod (date1, DAY_S))) + diff--; + } + return diff; +} + +/* Returns the number of whole months from DATE1 to DATE2, + where a month is defined as the same or later day and time of + day. */ +static int +month_diff (double date1, double date2) +{ + int y1, m1, d1, yd1; + int y2, m2, d2, yd2; + int diff; + + assert (date2 >= date1); + calendar_offset_to_gregorian (date1 / DAY_S, &y1, &m1, &d1, &yd1); + calendar_offset_to_gregorian (date2 / DAY_S, &y2, &m2, &d2, &yd2); + + diff = ((y2 * 12) + m2) - ((y1 * 12) + m1); + if (diff > 0 + && (d2 < d1 + || (d2 == d1 && fmod (date2, DAY_S) < fmod (date1, DAY_S)))) + diff--; + return diff; +} + +/* Returns the number of whole quarter from DATE1 to DATE2, + where a quarter is defined as three months. */ +static int +quarter_diff (double date1, double date2) +{ + return month_diff (date1, date2) / 3; +} + +/* Returns the number of seconds in the given UNIT. */ +static int +date_unit_duration (enum date_unit unit) +{ + switch (unit) + { + case DATE_WEEKS: + return WEEK_S; + + case DATE_DAYS: + return DAY_S; + + case DATE_HOURS: + return H_S; + + case DATE_MINUTES: + return MIN_S; + + case DATE_SECONDS: + return 1; + + default: + NOT_REACHED (); + } +} + +/* Returns the span from DATE to DATE2 in terms of UNIT_NAME. */ +double +expr_date_difference (double date1, double date2, struct substring unit_name) +{ + enum date_unit unit; + + if (!recognize_unit (unit_name, &unit)) + return SYSMIS; + + switch (unit) + { + case DATE_YEARS: + return (date2 >= date1 + ? year_diff (date1, date2) + : -year_diff (date2, date1)); + + case DATE_QUARTERS: + return (date2 >= date1 + ? quarter_diff (date1, date2) + : -quarter_diff (date2, date1)); + + case DATE_MONTHS: + return (date2 >= date1 + ? month_diff (date1, date2) + : -month_diff (date2, date1)); + + case DATE_WEEKS: + case DATE_DAYS: + case DATE_HOURS: + case DATE_MINUTES: + case DATE_SECONDS: + return trunc ((date2 - date1) / date_unit_duration (unit)); + } + + NOT_REACHED (); +} + +/* How to deal with days out of range for a given month. */ +enum date_sum_method + { + SUM_ROLLOVER, /* Roll them over to the next month. */ + SUM_CLOSEST /* Use the last day of the month. */ + }; + +/* Stores in *METHOD the method whose name is NAME. + Return success. */ +static bool +recognize_method (struct substring method_name, enum date_sum_method *method) +{ + if (ss_equals_case (method_name, ss_cstr ("closest"))) + { + *method = SUM_CLOSEST; + return true; + } + else if (ss_equals_case (method_name, ss_cstr ("rollover"))) + { + *method = SUM_ROLLOVER; + return true; + } + else + { + msg (SE, _("Invalid DATESUM method. " + "Valid choices are \"closest\" and \"rollover\".")); + return false; + } +} + +/* Returns DATE advanced by the given number of MONTHS, with + day-of-month overflow resolved using METHOD. */ +static double +add_months (double date, int months, enum date_sum_method method) +{ + int y, m, d, yd; + double output; + + calendar_offset_to_gregorian (date / DAY_S, &y, &m, &d, &yd); + y += months / 12; + m += months % 12; + if (m < 1) + { + m += 12; + y--; + } + else if (m > 12) + { + m -= 12; + y++; + } + assert (m >= 1 && m <= 12); + + if (method == SUM_CLOSEST && d > calendar_days_in_month (y, m)) + d = calendar_days_in_month (y, m); + + output = calendar_gregorian_to_offset (y, m, d, expr_error, NULL); + if (output != SYSMIS) + output = (output * DAY_S) + fmod (date, DAY_S); + return output; +} + +/* Returns DATE advanced by the given QUANTITY of units given in + UNIT_NAME, with day-of-month overflow resolved using + METHOD_NAME. */ +double +expr_date_sum (double date, double quantity, struct substring unit_name, + struct substring method_name) +{ + enum date_unit unit; + enum date_sum_method method; + + if (!recognize_unit (unit_name, &unit) + || !recognize_method (method_name, &method)) + return SYSMIS; + + switch (unit) + { + case DATE_YEARS: + return add_months (date, trunc (quantity) * 12, method); + + case DATE_QUARTERS: + return add_months (date, trunc (quantity) * 3, method); + + case DATE_MONTHS: + return add_months (date, trunc (quantity), method); + + case DATE_WEEKS: + case DATE_DAYS: + case DATE_HOURS: + case DATE_MINUTES: + case DATE_SECONDS: + return date + quantity * date_unit_duration (unit); + } + + NOT_REACHED (); +} int compare_string (const struct substring *a, const struct substring *b) diff --git a/src/language/expressions/helpers.h b/src/language/expressions/helpers.h index 46ed5e4a..b8fadc9d 100644 --- a/src/language/expressions/helpers.h +++ b/src/language/expressions/helpers.h @@ -45,6 +45,7 @@ static inline double check_errno (double x) #define H_MIN 60. /* Minutes per hour. */ #define MIN_S 60. /* Seconds per minute. */ #define WEEK_DAY 7. /* Days per week. */ +#define WEEK_S (WEEK_DAY * DAY_S) /* Seconds per week. */ extern const struct substring empty_string; @@ -55,6 +56,10 @@ double expr_ymd_to_ofs (double year, double month, double day); double expr_wkyr_to_date (double wk, double yr); double expr_yrday_to_date (double yr, double day); double expr_yrmoda (double year, double month, double day); +double expr_date_difference (double date1, double date2, + struct substring unit); +double expr_date_sum (double date, double quantity, struct substring unit_name, + struct substring method_name); struct substring alloc_string (struct expression *, size_t length); struct substring copy_string (struct expression *, diff --git a/src/language/expressions/operations.def b/src/language/expressions/operations.def index 6f0bd0e1..35f5d366 100644 --- a/src/language/expressions/operations.def +++ b/src/language/expressions/operations.def @@ -319,10 +319,13 @@ function XDATE.WKDAY (date >= DAY_S) = calendar_offset_to_wday (date / DAY_S); function XDATE.YEAR (date >= DAY_S) = calendar_offset_to_year (date / DAY_S); // Date arithmetic functions. -function DATEDIFF (date1, date2, string unit) = unimplemented; -function DATESUM (date, quantity, string unit) = unimplemented; -function DATESUM (date, quantity, string unit, string roll_over) - = unimplemented; +no_abbrev function DATEDIFF (date1 >= DAY_S, date2 >= DAY_S, string unit) + = expr_date_difference (date1, date2, unit); +no_abbrev function DATESUM (date, quantity, string unit) + = expr_date_sum (date, quantity, unit, ss_cstr ("closest")); +no_abbrev function DATESUM (date, quantity, string unit, string method) + = expr_date_sum (date, quantity, unit, method); + // String functions. string function CONCAT (string a[n]) @@ -613,7 +616,7 @@ absorb_miss string function SUBSTR (string s, ofs, cnt) return empty_string; } -absorb_miss no_opt string function VALUELABEL (var v) +absorb_miss no_opt no_abbrev string function VALUELABEL (var v) expression e; case c; { diff --git a/tests/ChangeLog b/tests/ChangeLog index 6e13ff08..fe424a1e 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,7 @@ +Wed Dec 13 19:34:29 2006 Ben Pfaff + + * expressions/expressions.sh: Test DATEDIFF, DATESUM functions. + Sun Dec 10 16:52:04 2006 Ben Pfaff * automake.mk: Add new test. diff --git a/tests/expressions/expressions.sh b/tests/expressions/expressions.sh index 70049e13..88c9bafa 100755 --- a/tests/expressions/expressions.sh +++ b/tests/expressions/expressions.sh @@ -1261,6 +1261,263 @@ xdate.year(date.mdy(2,25,96) + time.hms(21,30,57)) => 1996.00 xdate.year(date.mdy(11,10,2038) + time.hms(22,30,4)) => 2038.00 xdate.year(date.mdy(7,18,2094) + time.hms(1,56,51)) => 2094.00 +datediff(date.mdy(6,10,1648), date.mdy(6,30,1680), 'years') => 32.00 +datediff(date.mdy(6,30,1680), date.mdy(7,24,1716), 'years') => 36.00 +datediff(date.mdy(7,24,1716), date.mdy(6,19,1768), 'years') => 51.00 +datediff(date.mdy(6,19,1768), date.mdy(8,2,1819), 'years') => 51.00 +datediff(date.mdy(8,2,1819), date.mdy(3,27,1839), 'years') => 19.00 +datediff(date.mdy(3,27,1839), date.mdy(4,19,1903), 'years') => 64.00 +datediff(date.mdy(4,19,1903), date.mdy(8,25,1929), 'years') => 26.00 +datediff(date.mdy(8,25,1929), date.mdy(9,29,1941), 'years') => 12.00 +datediff(date.mdy(9,29,1941), date.mdy(4,19,1943), 'years') => 1.00 +datediff(date.mdy(4,19,1943), date.mdy(10,7,1943), 'years') => 0.00 +datediff(date.mdy(10,7,1943), date.mdy(3,17,1992), 'years') => 48.00 +datediff(date.mdy(3,17,1992), date.mdy(2,25,1996), 'years') => 3.00 +datediff(date.mdy(9,29,41), date.mdy(2,25,1996), 'years') => 54.00 +datediff(date.mdy(9,29,41), date.mdy(4,19,43), 'years') => 1.00 +datediff(date.mdy(4,19,43), date.mdy(10,7,43), 'years') => 0.00 +datediff(date.mdy(10,7,43), date.mdy(3,17,92), 'years') => 48.00 +datediff(date.mdy(3,17,92), date.mdy(2,25,96), 'years') => 3.00 +datediff(date.mdy(2,25,96), date.mdy(11,10,2038), 'years') => 42.00 +datediff(date.mdy(11,10,2038), date.mdy(7,18,2094), 'years') => 55.00 +datediff(date.mdy(2,29,1900), date.mdy(2,29,1904), 'years') => 3.00 +datediff(date.mdy(2,29,1904), date.mdy(2,29,1908), 'years') => 4.00 +datediff(date.mdy(2,29,1900), date.mdy(2,28,1903), 'years') => 2.00 + +datediff(date.mdy(6,10,1648), date.mdy(6,30,1680), 'quarters') => 128.00 +datediff(date.mdy(6,30,1680), date.mdy(7,24,1716), 'quarters') => 144.00 +datediff(date.mdy(7,24,1716), date.mdy(6,19,1768), 'quarters') => 207.00 +datediff(date.mdy(6,19,1768), date.mdy(8,2,1819), 'quarters') => 204.00 +datediff(date.mdy(8,2,1819), date.mdy(3,27,1839), 'quarters') => 78.00 +datediff(date.mdy(3,27,1839), date.mdy(4,19,1903), 'quarters') => 256.00 +datediff(date.mdy(4,19,1903), date.mdy(8,25,1929), 'quarters') => 105.00 +datediff(date.mdy(8,25,1929), date.mdy(9,29,1941), 'quarters') => 48.00 +datediff(date.mdy(9,29,1941), date.mdy(4,19,1943), 'quarters') => 6.00 +datediff(date.mdy(4,19,1943), date.mdy(10,7,1943), 'quarters') => 1.00 +datediff(date.mdy(10,7,1943), date.mdy(3,17,1992), 'quarters') => 193.00 +datediff(date.mdy(3,17,1992), date.mdy(2,25,1996), 'quarters') => 15.00 +datediff(date.mdy(9,29,41), date.mdy(2,25,1996), 'quarters') => 217.00 +datediff(date.mdy(9,29,41), date.mdy(4,19,43), 'quarters') => 6.00 +datediff(date.mdy(4,19,43), date.mdy(10,7,43), 'quarters') => 1.00 +datediff(date.mdy(10,7,43), date.mdy(3,17,92), 'quarters') => 193.00 +datediff(date.mdy(3,17,92), date.mdy(2,25,96), 'quarters') => 15.00 +datediff(date.mdy(2,25,96), date.mdy(11,10,2038), 'quarters') => 170.00 +datediff(date.mdy(11,10,2038), date.mdy(7,18,2094), 'quarters') => 222.00 +datediff(date.mdy(2,29,1900), date.mdy(2,29,1904), 'quarters') => 15.00 +datediff(date.mdy(2,29,1904), date.mdy(2,29,1908), 'quarters') => 16.00 +datediff(date.mdy(2,29,1900), date.mdy(2,28,1903), 'quarters') => 11.00 + +datediff(date.mdy(6,10,1648), date.mdy(6,30,1680), 'months') => 384.00 +datediff(date.mdy(6,30,1680), date.mdy(7,24,1716), 'months') => 432.00 +datediff(date.mdy(7,24,1716), date.mdy(6,19,1768), 'months') => 622.00 +datediff(date.mdy(6,19,1768), date.mdy(8,2,1819), 'months') => 613.00 +datediff(date.mdy(8,2,1819), date.mdy(3,27,1839), 'months') => 235.00 +datediff(date.mdy(3,27,1839), date.mdy(4,19,1903), 'months') => 768.00 +datediff(date.mdy(4,19,1903), date.mdy(8,25,1929), 'months') => 316.00 +datediff(date.mdy(8,25,1929), date.mdy(9,29,1941), 'months') => 145.00 +datediff(date.mdy(9,29,1941), date.mdy(4,19,1943), 'months') => 18.00 +datediff(date.mdy(4,19,1943), date.mdy(10,7,1943), 'months') => 5.00 +datediff(date.mdy(10,7,1943), date.mdy(3,17,1992), 'months') => 581.00 +datediff(date.mdy(3,17,1992), date.mdy(2,25,1996), 'months') => 47.00 +datediff(date.mdy(9,29,41), date.mdy(2,25,1996), 'months') => 652.00 +datediff(date.mdy(9,29,41), date.mdy(4,19,43), 'months') => 18.00 +datediff(date.mdy(4,19,43), date.mdy(10,7,43), 'months') => 5.00 +datediff(date.mdy(10,7,43), date.mdy(3,17,92), 'months') => 581.00 +datediff(date.mdy(3,17,92), date.mdy(2,25,96), 'months') => 47.00 +datediff(date.mdy(2,25,96), date.mdy(11,10,2038), 'months') => 512.00 +datediff(date.mdy(11,10,2038), date.mdy(7,18,2094), 'months') => 668.00 +datediff(date.mdy(2,29,1900), date.mdy(2,29,1904), 'months') => 47.00 +datediff(date.mdy(2,29,1904), date.mdy(2,29,1908), 'months') => 48.00 +datediff(date.mdy(2,29,1900), date.mdy(2,28,1903), 'months') => 35.00 + +datediff(date.mdy(6,10,1648), date.mdy(6,30,1680), 'weeks') => 1672.00 +datediff(date.mdy(6,30,1680), date.mdy(7,24,1716), 'weeks') => 1881.00 +datediff(date.mdy(7,24,1716), date.mdy(6,19,1768), 'weeks') => 2708.00 +datediff(date.mdy(6,19,1768), date.mdy(8,2,1819), 'weeks') => 2667.00 +datediff(date.mdy(8,2,1819), date.mdy(3,27,1839), 'weeks') => 1025.00 +datediff(date.mdy(3,27,1839), date.mdy(4,19,1903), 'weeks') => 3342.00 +datediff(date.mdy(4,19,1903), date.mdy(8,25,1929), 'weeks') => 1375.00 +datediff(date.mdy(8,25,1929), date.mdy(9,29,1941), 'weeks') => 631.00 +datediff(date.mdy(9,29,1941), date.mdy(4,19,1943), 'weeks') => 81.00 +datediff(date.mdy(4,19,1943), date.mdy(10,7,1943), 'weeks') => 24.00 +datediff(date.mdy(10,7,1943), date.mdy(3,17,1992), 'weeks') => 2527.00 +datediff(date.mdy(3,17,1992), date.mdy(2,25,1996), 'weeks') => 205.00 +datediff(date.mdy(9,29,41), date.mdy(2,25,1996), 'weeks') => 2838.00 +datediff(date.mdy(9,29,41), date.mdy(4,19,43), 'weeks') => 81.00 +datediff(date.mdy(4,19,43), date.mdy(10,7,43), 'weeks') => 24.00 +datediff(date.mdy(10,7,43), date.mdy(3,17,92), 'weeks') => 2527.00 +datediff(date.mdy(3,17,92), date.mdy(2,25,96), 'weeks') => 205.00 +datediff(date.mdy(2,25,96), date.mdy(11,10,2038), 'weeks') => 2228.00 +datediff(date.mdy(11,10,2038), date.mdy(7,18,2094), 'weeks') => 2905.00 +datediff(date.mdy(2,29,1900), date.mdy(2,29,1904), 'weeks') => 208.00 +datediff(date.mdy(2,29,1904), date.mdy(2,29,1908), 'weeks') => 208.00 +datediff(date.mdy(2,29,1900), date.mdy(2,28,1903), 'weeks') => 156.00 + +datediff(date.mdy(6,10,1648), date.mdy(6,30,1680), 'days') => 11708.00 +datediff(date.mdy(6,30,1680), date.mdy(7,24,1716), 'days') => 13172.00 +datediff(date.mdy(7,24,1716), date.mdy(6,19,1768), 'days') => 18958.00 +datediff(date.mdy(6,19,1768), date.mdy(8,2,1819), 'days') => 18670.00 +datediff(date.mdy(8,2,1819), date.mdy(3,27,1839), 'days') => 7177.00 +datediff(date.mdy(3,27,1839), date.mdy(4,19,1903), 'days') => 23398.00 +datediff(date.mdy(4,19,1903), date.mdy(8,25,1929), 'days') => 9625.00 +datediff(date.mdy(8,25,1929), date.mdy(9,29,1941), 'days') => 4418.00 +datediff(date.mdy(9,29,1941), date.mdy(4,19,1943), 'days') => 567.00 +datediff(date.mdy(4,19,1943), date.mdy(10,7,1943), 'days') => 171.00 +datediff(date.mdy(10,7,1943), date.mdy(3,17,1992), 'days') => 17694.00 +datediff(date.mdy(3,17,1992), date.mdy(2,25,1996), 'days') => 1440.00 +datediff(date.mdy(9,29,41), date.mdy(2,25,1996), 'days') => 19872.00 +datediff(date.mdy(9,29,41), date.mdy(4,19,43), 'days') => 567.00 +datediff(date.mdy(4,19,43), date.mdy(10,7,43), 'days') => 171.00 +datediff(date.mdy(10,7,43), date.mdy(3,17,92), 'days') => 17694.00 +datediff(date.mdy(3,17,92), date.mdy(2,25,96), 'days') => 1440.00 +datediff(date.mdy(2,25,96), date.mdy(11,10,2038), 'days') => 15599.00 +datediff(date.mdy(11,10,2038), date.mdy(7,18,2094), 'days') => 20339.00 +datediff(date.mdy(2,29,1900), date.mdy(2,29,1904), 'days') => 1460.00 +datediff(date.mdy(2,29,1904), date.mdy(2,29,1908), 'days') => 1461.00 +datediff(date.mdy(2,29,1900), date.mdy(2,28,1903), 'days') => 1094.00 + +datediff(date.mdy(6,30,1680), date.mdy(6,10,1648), 'years') => -32.00 +datediff(date.mdy(7,24,1716), date.mdy(6,30,1680), 'years') => -36.00 +datediff(date.mdy(6,19,1768), date.mdy(7,24,1716), 'years') => -51.00 +datediff(date.mdy(8,2,1819), date.mdy(6,19,1768), 'years') => -51.00 +datediff(date.mdy(3,27,1839), date.mdy(8,2,1819), 'years') => -19.00 +datediff(date.mdy(4,19,1903), date.mdy(3,27,1839), 'years') => -64.00 +datediff(date.mdy(8,25,1929), date.mdy(4,19,1903), 'years') => -26.00 +datediff(date.mdy(9,29,1941), date.mdy(8,25,1929), 'years') => -12.00 +datediff(date.mdy(4,19,1943), date.mdy(9,29,1941), 'years') => -1.00 +datediff(date.mdy(10,7,1943), date.mdy(4,19,1943), 'years') => 0.00 +datediff(date.mdy(3,17,1992), date.mdy(10,7,1943), 'years') => -48.00 +datediff(date.mdy(2,25,1996), date.mdy(3,17,1992), 'years') => -3.00 +datediff(date.mdy(2,25,1996), date.mdy(9,29,41), 'years') => -54.00 +datediff(date.mdy(4,19,43), date.mdy(9,29,41), 'years') => -1.00 +datediff(date.mdy(10,7,43), date.mdy(4,19,43), 'years') => 0.00 +datediff(date.mdy(3,17,92), date.mdy(10,7,43), 'years') => -48.00 +datediff(date.mdy(2,25,96), date.mdy(3,17,92), 'years') => -3.00 +datediff(date.mdy(11,10,2038), date.mdy(2,25,96), 'years') => -42.00 +datediff(date.mdy(7,18,2094), date.mdy(11,10,2038), 'years') => -55.00 +datediff(date.mdy(2,29,1904), date.mdy(2,29,1900), 'years') => -3.00 +datediff(date.mdy(2,29,1908), date.mdy(2,29,1904), 'years') => -4.00 +datediff(date.mdy(2,28,1903), date.mdy(2,29,1900), 'years') => -2.00 + +datediff(date.mdy(6,30,1680), date.mdy(6,10,1648), 'months') => -384.00 +datediff(date.mdy(7,24,1716), date.mdy(6,30,1680), 'months') => -432.00 +datediff(date.mdy(6,19,1768), date.mdy(7,24,1716), 'months') => -622.00 +datediff(date.mdy(8,2,1819), date.mdy(6,19,1768), 'months') => -613.00 +datediff(date.mdy(3,27,1839), date.mdy(8,2,1819), 'months') => -235.00 +datediff(date.mdy(4,19,1903), date.mdy(3,27,1839), 'months') => -768.00 +datediff(date.mdy(8,25,1929), date.mdy(4,19,1903), 'months') => -316.00 +datediff(date.mdy(9,29,1941), date.mdy(8,25,1929), 'months') => -145.00 +datediff(date.mdy(4,19,1943), date.mdy(9,29,1941), 'months') => -18.00 +datediff(date.mdy(10,7,1943), date.mdy(4,19,1943), 'months') => -5.00 +datediff(date.mdy(3,17,1992), date.mdy(10,7,1943), 'months') => -581.00 +datediff(date.mdy(2,25,1996), date.mdy(3,17,1992), 'months') => -47.00 +datediff(date.mdy(2,25,1996), date.mdy(9,29,41), 'months') => -652.00 +datediff(date.mdy(4,19,43), date.mdy(9,29,41), 'months') => -18.00 +datediff(date.mdy(10,7,43), date.mdy(4,19,43), 'months') => -5.00 +datediff(date.mdy(3,17,92), date.mdy(10,7,43), 'months') => -581.00 +datediff(date.mdy(2,25,96), date.mdy(3,17,92), 'months') => -47.00 +datediff(date.mdy(11,10,2038), date.mdy(2,25,96), 'months') => -512.00 +datediff(date.mdy(7,18,2094), date.mdy(11,10,2038), 'months') => -668.00 +datediff(date.mdy(2,29,1904), date.mdy(2,29,1900), 'months') => -47.00 +datediff(date.mdy(2,29,1908), date.mdy(2,29,1904), 'months') => -48.00 +datediff(date.mdy(2,28,1903), date.mdy(2,29,1900), 'months') => -35.00 + +datediff(date.mdy(6,30,1680), date.mdy(6,10,1648), 'quarters') => -128.00 +datediff(date.mdy(7,24,1716), date.mdy(6,30,1680), 'quarters') => -144.00 +datediff(date.mdy(6,19,1768), date.mdy(7,24,1716), 'quarters') => -207.00 +datediff(date.mdy(8,2,1819), date.mdy(6,19,1768), 'quarters') => -204.00 +datediff(date.mdy(3,27,1839), date.mdy(8,2,1819), 'quarters') => -78.00 +datediff(date.mdy(4,19,1903), date.mdy(3,27,1839), 'quarters') => -256.00 +datediff(date.mdy(8,25,1929), date.mdy(4,19,1903), 'quarters') => -105.00 +datediff(date.mdy(9,29,1941), date.mdy(8,25,1929), 'quarters') => -48.00 +datediff(date.mdy(4,19,1943), date.mdy(9,29,1941), 'quarters') => -6.00 +datediff(date.mdy(10,7,1943), date.mdy(4,19,1943), 'quarters') => -1.00 +datediff(date.mdy(3,17,1992), date.mdy(10,7,1943), 'quarters') => -193.00 +datediff(date.mdy(2,25,1996), date.mdy(3,17,1992), 'quarters') => -15.00 +datediff(date.mdy(2,25,1996), date.mdy(9,29,41), 'quarters') => -217.00 +datediff(date.mdy(4,19,43), date.mdy(9,29,41), 'quarters') => -6.00 +datediff(date.mdy(10,7,43), date.mdy(4,19,43), 'quarters') => -1.00 +datediff(date.mdy(3,17,92), date.mdy(10,7,43), 'quarters') => -193.00 +datediff(date.mdy(2,25,96), date.mdy(3,17,92), 'quarters') => -15.00 +datediff(date.mdy(11,10,2038), date.mdy(2,25,96), 'quarters') => -170.00 +datediff(date.mdy(7,18,2094), date.mdy(11,10,2038), 'quarters') => -222.00 +datediff(date.mdy(2,29,1904), date.mdy(2,29,1900), 'quarters') => -15.00 +datediff(date.mdy(2,29,1908), date.mdy(2,29,1904), 'quarters') => -16.00 +datediff(date.mdy(2,28,1903), date.mdy(2,29,1900), 'quarters') => -11.00 + +# DATESUM with non-leap year +ctime.days(datesum(date.mdy(1,31,1900), 1, 'months') - date.mdy(1,1,1900)) => 58.00 +ctime.days(datesum(date.mdy(1,31,1900), 2, 'months') - date.mdy(1,1,1900)) => 89.00 +ctime.days(datesum(date.mdy(1,31,1900), 3, 'months') - date.mdy(1,1,1900)) => 119.00 +ctime.days(datesum(date.mdy(1,31,1900), 4, 'months') - date.mdy(1,1,1900)) => 150.00 +ctime.days(datesum(date.mdy(1,31,1900), 5.4, 'months') - date.mdy(1,1,1900)) => 180.00 +ctime.days(datesum(date.mdy(1,31,1900), 6, 'months') - date.mdy(1,1,1900)) => 211.00 +ctime.days(datesum(date.mdy(1,31,1900), 7, 'months') - date.mdy(1,1,1900)) => 242.00 +ctime.days(datesum(date.mdy(1,31,1900), 8, 'months') - date.mdy(1,1,1900)) => 272.00 +ctime.days(datesum(date.mdy(1,31,1900), 9, 'months') - date.mdy(1,1,1900)) => 303.00 +ctime.days(datesum(date.mdy(1,31,1900), 10, 'months') - date.mdy(1,1,1900)) => 333.00 +ctime.days(datesum(date.mdy(1,31,1900), 11, 'months') - date.mdy(1,1,1900)) => 364.00 +ctime.days(datesum(date.mdy(1,31,1900), 12, 'months') - date.mdy(1,1,1900)) => 395.00 +ctime.days(datesum(date.mdy(1,31,1900), 13.9, 'months') - date.mdy(1,1,1900)) => 423.00 +ctime.days(datesum(date.mdy(1,31,1900), 1, 'months', 'rollover') - date.mdy(1,1,1900)) => 61.00 +ctime.days(datesum(date.mdy(1,31,1900), 2, 'months', 'rollover') - date.mdy(1,1,1900)) => 89.00 +ctime.days(datesum(date.mdy(1,31,1900), 3.2, 'months', 'rollover') - date.mdy(1,1,1900)) => 120.00 +ctime.days(datesum(date.mdy(1,31,1900), 4, 'months', 'rollover') - date.mdy(1,1,1900)) => 150.00 +ctime.days(datesum(date.mdy(1,31,1900), 5, 'months', 'rollover') - date.mdy(1,1,1900)) => 181.00 +ctime.days(datesum(date.mdy(1,31,1900), 6, 'months', 'rollover') - date.mdy(1,1,1900)) => 211.00 +ctime.days(datesum(date.mdy(1,31,1900), 7, 'months', 'rollover') - date.mdy(1,1,1900)) => 242.00 +ctime.days(datesum(date.mdy(1,31,1900), 8, 'months', 'rollover') - date.mdy(1,1,1900)) => 273.00 +ctime.days(datesum(date.mdy(1,31,1900), 9, 'months', 'rollover') - date.mdy(1,1,1900)) => 303.00 +ctime.days(datesum(date.mdy(1,31,1900), 10, 'months', 'rollover') - date.mdy(1,1,1900)) => 334.00 +ctime.days(datesum(date.mdy(1,31,1900), 11, 'months', 'rollover') - date.mdy(1,1,1900)) => 364.00 +ctime.days(datesum(date.mdy(1,31,1900), 12, 'months', 'rollover') - date.mdy(1,1,1900)) => 395.00 +ctime.days(datesum(date.mdy(1,31,1900), 13, 'months', 'rollover') - date.mdy(1,1,1900)) => 426.00 + +# DATESUM with leap year +ctime.days(datesum(date.mdy(1,31,1904), 1, 'months') - date.mdy(1,1,1904)) => 59.00 +ctime.days(datesum(date.mdy(1,31,1904), 2.5, 'months') - date.mdy(1,1,1904)) => 90.00 +ctime.days(datesum(date.mdy(1,31,1904), 3, 'months') - date.mdy(1,1,1904)) => 120.00 +ctime.days(datesum(date.mdy(1,31,1904), 4.9, 'months') - date.mdy(1,1,1904)) => 151.00 +ctime.days(datesum(date.mdy(1,31,1904), 5.1, 'months') - date.mdy(1,1,1904)) => 181.00 +ctime.days(datesum(date.mdy(1,31,1904), 6, 'months') - date.mdy(1,1,1904)) => 212.00 +ctime.days(datesum(date.mdy(1,31,1904), 7, 'months') - date.mdy(1,1,1904)) => 243.00 +ctime.days(datesum(date.mdy(1,31,1904), 8, 'months') - date.mdy(1,1,1904)) => 273.00 +ctime.days(datesum(date.mdy(1,31,1904), 9, 'months') - date.mdy(1,1,1904)) => 304.00 +ctime.days(datesum(date.mdy(1,31,1904), 10, 'months') - date.mdy(1,1,1904)) => 334.00 +ctime.days(datesum(date.mdy(1,31,1904), 11, 'months') - date.mdy(1,1,1904)) => 365.00 +ctime.days(datesum(date.mdy(1,31,1904), 12, 'months') - date.mdy(1,1,1904)) => 396.00 +ctime.days(datesum(date.mdy(1,31,1904), 13, 'months') - date.mdy(1,1,1904)) => 424.00 +ctime.days(datesum(date.mdy(1,31,1904), 1, 'months', 'rollover') - date.mdy(1,1,1904)) => 61.00 +ctime.days(datesum(date.mdy(1,31,1904), 2, 'months', 'rollover') - date.mdy(1,1,1904)) => 90.00 +ctime.days(datesum(date.mdy(1,31,1904), 3, 'months', 'rollover') - date.mdy(1,1,1904)) => 121.00 +ctime.days(datesum(date.mdy(1,31,1904), 4, 'months', 'rollover') - date.mdy(1,1,1904)) => 151.00 +ctime.days(datesum(date.mdy(1,31,1904), 5, 'months', 'rollover') - date.mdy(1,1,1904)) => 182.00 +ctime.days(datesum(date.mdy(1,31,1904), 6, 'months', 'rollover') - date.mdy(1,1,1904)) => 212.00 +ctime.days(datesum(date.mdy(1,31,1904), 7, 'months', 'rollover') - date.mdy(1,1,1904)) => 243.00 +ctime.days(datesum(date.mdy(1,31,1904), 8, 'months', 'rollover') - date.mdy(1,1,1904)) => 274.00 +ctime.days(datesum(date.mdy(1,31,1904), 9, 'months', 'rollover') - date.mdy(1,1,1904)) => 304.00 +ctime.days(datesum(date.mdy(1,31,1904), 10, 'months', 'rollover') - date.mdy(1,1,1904)) => 335.00 +ctime.days(datesum(date.mdy(1,31,1904), 11, 'months', 'rollover') - date.mdy(1,1,1904)) => 365.00 +ctime.days(datesum(date.mdy(1,31,1904), 12, 'months', 'rollover') - date.mdy(1,1,1904)) => 396.00 +ctime.days(datesum(date.mdy(1,31,1904), 13, 'months', 'rollover') - date.mdy(1,1,1904)) => 427.00 + +ctime.days(datesum(date.mdy(6,10,1648), 1, 'weeks') - date.mdy(6,10,1648)) => 7.00 +ctime.days(datesum(date.mdy(6,30,1680), 2.5, 'weeks') - date.mdy(6,30,1680)) => 17.50 +ctime.days(datesum(date.mdy(7,24,1716), -3, 'weeks') - date.mdy(7,24,1716)) => -21.00 +ctime.days(datesum(date.mdy(6,19,1768), 4, 'weeks') - date.mdy(6,19,1768)) => 28.00 +ctime.days(datesum(date.mdy(8,2,1819), 5, 'weeks') - date.mdy(8,2,1819)) => 35.00 + +ctime.days(datesum(date.mdy(6,10,1648), 1, 'days') - date.mdy(6,10,1648)) => 1.00 +ctime.days(datesum(date.mdy(6,30,1680), 2.5, 'days') - date.mdy(6,30,1680)) => 2.50 +ctime.days(datesum(date.mdy(7,24,1716), -3, 'days') - date.mdy(7,24,1716)) => -3.00 +ctime.days(datesum(date.mdy(6,19,1768), 4, 'days') - date.mdy(6,19,1768)) => 4.00 +ctime.days(datesum(date.mdy(8,2,1819), 5, 'days') - date.mdy(8,2,1819)) => 5.00 + +ctime.days(datesum(date.mdy(6,10,1648), 1, 'hours') - date.mdy(6,10,1648)) => 0.04 +ctime.days(datesum(date.mdy(6,30,1680), 2.5, 'hours') - date.mdy(6,30,1680)) => 0.10 +ctime.days(datesum(date.mdy(6,19,1768), -4, 'hours') - date.mdy(6,19,1768)) => -0.17 +ctime.days(datesum(date.mdy(8,2,1819), 5, 'hours') - date.mdy(8,2,1819)) => 0.21 + # These test values are from Applied Statistics, Algorithm AS 310. 1000 * ncdf.beta(.868,10,20,150) => 937.66 1000 * ncdf.beta(.9,10,10,120) => 730.68 -- 2.30.2