Bläddra i källkod

Initial commit of a date library module

Signed-off-by: Gavin Halliday <gavin.halliday@lexisnexis.com>
Gavin Halliday 13 år sedan
förälder
incheckning
5ac8921e35
2 ändrade filer med 363 tillägg och 0 borttagningar
  1. 295 0
      ecllibrary/std/Date.ecl
  2. 68 0
      ecllibrary/teststd/Date/TestDate.ecl

+ 295 - 0
ecllibrary/std/Date.ecl

@@ -0,0 +1,295 @@
+/*##############################################################################
+## Copyright (c) 2011 HPCC Systems.  All rights reserved.
+############################################################################## */
+
+EXPORT Date := MODULE
+
+// Three different date representations are defined
+// A record stucture with the different elements separated out.
+EXPORT Date_rec := RECORD
+    INTEGER2   year;
+    UNSIGNED1   month;
+    UNSIGNED1   day;
+END;
+
+//An unsigned number holding a date in the decimal form YYYYMMDD.  This type doesn't support dates before 1AD.
+EXPORT Date_t := UNSIGNED4;
+
+//A number of elapsed days.  Origin depends on the function called.
+EXPORT Days_t := UNSIGNED4;
+
+/*
+ * Extracts the year from a date type.
+ *
+ * @param date          The date.
+ * @return              An integer representing the year.
+ */
+
+EXPORT Year(Date_t date) := date DIV 10000;
+
+/*
+ * Extracts the month from a date type.
+ *
+ * @param date          The date.
+ * @return              An integer representing the year.
+ */
+
+EXPORT Month(Date_t date) := (date DIV 100) % 100;
+
+/*
+ * Extracts the day of the month from a date type.
+ *
+ * @param date          The date.
+ * @return              An integer representing the year.
+ */
+
+EXPORT Day(Date_t date) := date % 100;
+
+/*
+ * Combines year, month day to create a date type.
+ *
+ * @param year          The year (0-9999).
+ * @param month         The month (1-12).
+ * @param day           The day (1..daysInMonth).
+ * @return              A date created by combining the fields.
+ */
+
+EXPORT DateFromParts(INTEGER2 year, UNSIGNED1 month, UNSIGNED1 day) := ( year * 100 + month ) * 100 + day;
+
+/*
+ * Tests whether the year is a leap year in the Gregorian calendar (Or proleptic Gregorian).
+ *
+ * @param year          The year (0-9999).
+ * @return              True if the year is a leap year.
+ */
+
+EXPORT IsLeapYear(INTEGER2 year) := (year % 4 = 0) AND ((year % 100 != 0) OR (year % 400 = 0));
+
+SHARED YearDelta := +4800;          // Offset the years by 4800 so dates up to -4713 work
+
+SHARED GregorianDateOrigin := -1753469;      // 1 Jan 1AD = 1
+
+/*
+ * Combines year, month, day in the Gregorian calendar to create the number days since 31st December 1BC.
+ *
+ * @param year          The year (-4713..9999).
+ * @param month         The month (1-12).
+ * @param day           The day (1..daysInMonth).
+ * @return              The number of elapsed days (1 Jan 1AD = 1)
+ */
+
+EXPORT FromGregorianYMD(INTEGER2 year, UNSIGNED1 month, UNSIGNED1 day) := FUNCTION
+    //See Frequently Asked Questions about Calendars by Claus Toendering
+    a := (14 - month) DIV 12;
+    y := year + YearDelta - a;
+    m := month + 12*a - 3;
+    jd := day + (153 * m + 2) DIV 5 + 365 * y + y DIV 4 - y DIV 100 + y DIV 400;
+    RETURN jd + (GregorianDateOrigin-1);
+END;
+
+/*
+ * Converts the number days since 31st December 1BC to a date in the Gregorian calendar.
+ *
+ * @param days          The number of elapsed days (1 Jan 1AD = 1)
+ * @return              Module containing Year, Month, Day in the Gregorian calendar
+ */
+
+EXPORT ToGregorianYMD(Days_t days) := FUNCTION
+    //See Fliegel and van Flandern (1968) and other quoted sources (e.g., http://www.ortelius.de/kalender/calc_en.php)
+    //Process as 4, 100 and 400 year cycles.
+    daysIn4Years := 3*365+366;
+    daysIn100Years := 25*daysIn4Years-1;
+    daysIn400Years := 4*daysIn100Years+1;
+
+    //Calulate days in each of the cycles.
+    adjustedDays := days - GregorianDateOrigin;
+    num400Years := adjustedDays div daysIn400Years;
+    rem400Years := adjustedDays % daysIn400Years;
+
+    num100Years := ((rem400Years div daysIn100Years + 1) * 3) DIV 4;
+    rem100Years := rem400Years - num100Years * daysIn100Years;
+
+    num4Years := rem100Years div daysIn4Years;
+    rem4Years := rem100Years % daysIn4Years;
+
+    years := ((rem4Years div 365 + 1) * 3) DIV 4;
+    numdays := rem4Years - years * 365;
+
+    //Now calculate the actual year, month day
+    y := num400Years * 400 + num100Years * 100 + num4Years * 4 + years;
+    m := (numdays * 5 + 308) div 153 - 2;
+    d := numdays - (m + 4) * 153 div 5 + 122;
+    result := MODULE
+        EXPORT year := (y + (m + 2) div 12) - YearDelta;
+        EXPORT month := (m + 2) % 12 + 1;
+        EXPORT day := d + 1;
+    END;
+    return result;
+END;
+
+/*
+ * Converts a date in the Gregorian calendar to the number days since 31st December 1BC.
+ *
+ * @param date          The date (using the Gregorian calendar)
+ * @return              The number of elapsed days (1 Jan 1AD = 1)
+ */
+
+EXPORT FromGregorianDate(Date_t date) := DEFINE FromGregorianYMD(Year(date), Month(date), Day(date));
+
+/*
+ * Converts the number days since 31st December 1BC to a date in the Gregorian calendar.
+ *
+ * @param days          The number of elapsed days (1 Jan 1AD = 1)
+ * @return              A date_t in the Gregorian calendar
+ */
+
+EXPORT ToGregorianDate(Days_t days) := DEFINE FUNCTION
+    date := ToGregorianYMD(days);
+    RETURN DateFromParts(date.year, date.month, date.day);
+END;
+
+day_of_year(INTEGER2 month, UNSIGNED1 day) := CHOOSE( month+1,0,0,31,59,90,120,151,181,212,243,273,304,334 ) + day;
+
+/*
+ * Extracts the day of the year from a date in the Gregorian calendar.
+ *
+ * @param date          The date
+ * @return              The day of the year (1..366)
+ */
+
+EXPORT DayOfYear(Date_t d) := day_of_year(Month(d),Day(d)) + IF(IsLeapYear(Year(d)) AND Month(d) > 2, 1, 0);
+
+SHARED JulianDateOrigin := -1753505;                // 1 Jan 1AD = 1
+
+/*
+ * Tests whether the year is a leap year in the Julian calendar.
+ *
+ * @param year          The year (0-9999).
+ * @return              True if the year is a leap year.
+ */
+
+EXPORT IsJulianLeapYear(INTEGER2 year) := (year % 4 = 0);
+
+/*
+ * Combines year, month, day in the Julian calendar to create the number days since 31st December 1BC.
+ *
+ * @param year          The year (-4800..9999).
+ * @param month         The month (1-12).
+ * @param day           The day (1..daysInMonth).
+ * @return              The number of elapsed days (1 Jan 1AD = 1)
+ */
+
+EXPORT FromJulianYMD(INTEGER2 year, UNSIGNED1 month, UNSIGNED1 day) := FUNCTION
+    //See Frequently Asked Questions about Calendars by Claus Toendering
+    a := (14 - month) DIV 12;
+    y := year + YearDelta - a;
+    m := month + 12*a - 3;
+    jd := day + (153 * m + 2) DIV 5 + 365 * y + y DIV 4;
+    RETURN jd + (JulianDateOrigin-1);
+END;
+
+/*
+ * Converts the number days since 31st December 1BC to a date in the Julian calendar.
+ *
+ * @param days          The number of elapsed days (1 Jan 1AD = 1)
+ * @return              Module containing Year, Month, Day in the Julian calendar
+ */
+
+EXPORT ToJulianYMD(Days_t days) := FUNCTION
+    //See Frequently Asked Questions about Calendars by Claus Toendering
+    daysIn4Years := 3*365+366;
+    c := days - JulianDateOrigin;
+    d := (4 * c + 3) DIV daysIn4Years;
+    e := c - ((daysIn4Years * d) DIV 4);
+    m := (5 * e + 2) DIV 153;
+    result := MODULE
+        EXPORT day := e - ((153 * m + 2) DIV 5) + 1;
+        EXPORT month := m + 3 - 12 * (m DIV 10);
+        EXPORT year := d - YearDelta + (m DIV 10);
+    END;
+    RETURN result;
+END;
+
+/*
+ * Converts a date in the Julian calendar to the number days since 31st December 1BC.
+ *
+ * @param date          The date (using the Julian calendar)
+ * @return              The number of elapsed days (1 Jan 1AD = 1)
+ */
+
+EXPORT FromJulianDate(Date_t date) := DEFINE FromJulianYMD(Year(date), Month(date), Day(date));
+
+/*
+ * Converts the number days since 31st December 1BC to a date in the Julian calendar.
+ *
+ * @param days          The number of elapsed days (1 Jan 1AD = 1)
+ * @return              A date_t in the Julian calendar
+ */
+
+EXPORT ToJulianDate(Days_t days) := DEFINE FUNCTION
+    date := ToJulianYMD(days);
+    RETURN DateFromParts(date.year, date.month, date.day);
+END;
+
+SHARED Date1900Delta := 693596;      // 1 Jan 1900 = 0
+
+/*
+ * Returns the number of days since 1st January 1900 (using the Gregorian Calendar)
+ *
+ * @param year          The year (-4713..9999).
+ * @param month         The month (1-12).
+ * @param day           The day (1..daysInMonth).
+ * @return              The number of elapsed days since 1st January 1900
+ */
+
+EXPORT DaysSince1900(INTEGER2 year, UNSIGNED1 month, UNSIGNED1 day) := FromGregorianYMD(year, month, day) - Date1900Delta;
+
+/*
+ * Returns the number of days since 1st January 1900 (using the Gregorian Calendar)
+ *
+ * @param date          The date
+ * @return              The number of elapsed days since 1st January 1900
+ */
+
+EXPORT ToDaysSince1900(Date_t date) := DaysSince1900(Year(date),Month(date),Day(date));
+
+/*
+ * Converts the number days since 1st January 1900 to a date in the Julian calendar.
+ *
+ * @param days          The number of elapsed days since 1st Jan 1900
+ * @return              A date_t in the Julian calendar
+ */
+
+EXPORT FromDaysSince1900(Days_t days) := ToGregorianDate(days + Date1900Delta);
+
+/*
+ * Returns the current date
+ *
+ * @return              A date_t representing the current date.
+ */
+import lib_stringlib.StringLib;
+EXPORT Today() := (date_t)StringLib.GetDateYYYYMMDD();
+
+/*
+ * A transform to create a date_rec from the individual elements
+ *
+ * @param year          The year
+ * @param month         The month (1-12).
+ * @param day           The day (1..daysInMonth).
+ * @return              A transform that creates a date_rec containing the date.
+ */
+EXPORT Date_rec CreateDate(INTEGER2 year, UNSIGNED1 month, UNSIGNED1 day) := TRANSFORM
+    SELF.year := year;
+    SELF.month := month;
+    SELF.day := day;
+END;
+
+/*
+ * Combines the fields from a date_rec to create a date_t
+ *
+ * @param date          The row containing the date.
+ * @return              A date_t representing the combined values.
+ */
+EXPORT DateFromRec(date_rec date) := ( date.year * 100 + date.month ) * 100 + date.day;
+
+END;

+ 68 - 0
ecllibrary/teststd/Date/TestDate.ecl

@@ -0,0 +1,68 @@
+/*##############################################################################
+## Copyright (c) 2011 HPCC Systems.  All rights reserved.
+############################################################################## */
+
+IMPORT Std.Date;
+
+EXPORT TestDate := MODULE
+
+  EXPORT TestConstant := MODULE
+    EXPORT Test01 := ASSERT(NOT date.IsLeapYear(1900), CONST);
+    EXPORT Test02 := ASSERT(date.IsLeapYear(1904), CONST);
+    EXPORT Test03 := ASSERT(NOT date.IsLeapYear(2100), CONST);
+    EXPORT Test04 := ASSERT(date.IsLeapYear(2000), CONST);
+    EXPORT Test05 := ASSERT(NOT date.IsLeapYear(1901), CONST);
+    EXPORT Test06 := ASSERT(date.FromDaysSince1900(0) = 19000101, CONST);
+    EXPORT Test07 := ASSERT(date.ToGregorianDate(1) = 00010101, CONST);
+    EXPORT Test08 := ASSERT(date.DaysSince1900(1900,1,1)=0, CONST);
+    EXPORT Test09 := ASSERT(date.FromGregorianYMD(1,1,1)=1, CONST);
+    EXPORT Test10 := ASSERT(date.ToJulianDate(1) = 00010101, CONST);
+    EXPORT Test11 := ASSERT(date.FromJulianYMD(1,1,1)=1, CONST);
+  END;
+
+  EXPORT TestDynamic := MODULE
+    //Iterate through all lots of dates, incrementing the day and the date to check they convert correctly.
+    Date_rec := Date.Date_rec;
+    test_rec := { Date.Days_t day, Date_rec gregorian, Date_rec julian };
+    firstDate := DATASET([{ 1, ROW(Date.createDate(1,1,1)), ROW(Date.createDate(1,1,1))}], test_rec);
+    daysInLeapYearMonth := [31,29,31,30,31,30,31,31,30,31,30,31];
+    daysInNonLeapYearMonth := [31,28,31,30,31,30,31,31,30,31,30,31];
+
+    Date_rec makeNextDate(Date_rec prev, Date.IsLeapYear leapYearFunc) := TRANSFORM
+        daysInMonth(unsigned2 year) := IF(leapYearFunc(year), daysInLeapYearMonth, daysInNonLeapYearMonth);
+
+        isLastDayOfMonth :=
+               IF(leapYearFunc(prev.year),
+                 prev.day = daysInLeapYearMonth[prev.month],
+                 prev.day = daysInNonLeapYearMonth[prev.month]);
+        isLastDayOfMonth2 := prev.day = daysInMonth(prev.year)[prev.month];
+        isLastDayOfYear := prev.month=12 AND prev.day=31;
+
+        SELF.year := IF(isLastDayOfYear, prev.year+1, prev.year);
+        SELF.month := IF(isLastDayOfYear, 1, IF(isLastDayOfMonth, prev.month+1, prev.month));
+        SELF.day := IF(isLastDayOfMonth, 1, prev.day+1);
+    END;
+
+    test_rec nextRecord(test_rec prev) := TRANSFORM
+        SELF.day := prev.day + 1;
+        SELF.gregorian := ROW(makeNextDate(prev.gregorian, Date.IsLeapYear));
+        SELF.julian := ROW(makeNextDate(prev.julian, Date.IsJulianLeapYear));
+    END;
+
+    processNextDate(DATASET(test_rec) in) := FUNCTION
+       next := PROJECT(in, nextRecord(LEFT));
+       result1 := ASSERT(next, Date.ToGregorianDate(next.day) = Date.DateFromRec(next.gregorian));
+       result2 := ASSERT(result1, next.day = Date.FromGregorianDate(Date.DateFromRec(next.gregorian)));
+       result3 := ASSERT(result2, Date.ToJulianDate(next.day) = Date.DateFromRec(next.julian));
+       result4 := ASSERT(result3, next.day = Date.FromJulianDate(Date.DateFromRec(next.julian)));
+       RETURN result4;
+    END;
+
+    x := LOOP(firstDate, 1000000, processNextDate(ROWS(LEFT)));
+
+    EXPORT Test01 := OUTPUT(x);
+  END;
+
+  EXPORT Main := [EVALUATE(TestConstant), EVALUATE(TestDynamic)];
+
+END;