Преглед на файлове

HPCC-8572 Std.Date functionality

Time support added to Std.Date; new timelib plugin; stringlib modifications to support time; updated tests for new Std.Date functionality.

Signed-off-by: Dan S. Camper <dan.camper@lexisnexis.com>
Dan S. Camper преди 10 години
родител
ревизия
c13f848db3

Файловите разлики са ограничени, защото са твърде много
+ 978 - 115
ecllibrary/std/Date.ecl


+ 164 - 24
ecllibrary/teststd/Date/TestDate.ecl

@@ -6,33 +6,173 @@ IMPORT Std.Date;
 
 EXPORT TestDate := MODULE
 
+  SHARED vCreateDateTimeFromSeconds := ROW(Date.CreateDateTimeFromSeconds(917872496)); // Feb 1, 1999 @ 12:34:56
+  SHARED vCreateDateFromSeconds := ROW(Date.CreateDateFromSeconds(917872496)); // Feb 1, 1999
+  SHARED vCreateTimeFromSeconds := ROW(Date.CreateTimeFromSeconds(917872496)); // 12:34:56
+  SHARED vCreateTime := ROW(Date.CreateTime(12,34,56)); // 12:34:56
+  SHARED vCreateDateTime := ROW(Date.CreateDateTime(1999,2,1,12,34,56)); // Feb 1, 1999 @ 12:34:56
+  SHARED vDate := Date.CurrentDate(); // UTC
+  SHARED vToday := Date.Today(); // Local
+  SHARED vTime := Date.CurrentTime(); // UTC
+  SHARED vTimeLocal := Date.CurrentTime(TRUE); // Local
+  SHARED vSeconds := Date.CurrentSeconds(); // UTC
+  SHARED vSecondsLocal := Date.CurrentSeconds(TRUE); // Local
+  SHARED vTimestamp := Date.CurrentTimestamp(); // UTC
+  SHARED vTimestampLocal := Date.CurrentTimestamp(TRUE); // Local
+  SHARED vLocalTimeZoneOffset := Date.LocalTimeZoneOffset();
+
   EXPORT TestConstant := [
-    ASSERT(NOT date.IsLeapYear(1900), CONST);
-    ASSERT(date.IsLeapYear(1904), CONST);
-    ASSERT(NOT date.IsLeapYear(2100), CONST);
-    ASSERT(date.IsLeapYear(2000), CONST);
-    ASSERT(NOT date.IsLeapYear(1901), CONST);
-    ASSERT(date.FromDaysSince1900(0) = 19000101, CONST);
-    ASSERT(date.ToGregorianDate(1) = 00010101, CONST);
-    ASSERT(date.DaysSince1900(1900,1,1)=0, CONST);
-    ASSERT(date.FromGregorianYMD(1,1,1)=1, CONST);
-    ASSERT(date.ToJulianDate(1) = 00010101, CONST);
-    ASSERT(date.FromJulianYMD(1,1,1)=1, CONST);
-    ASSERT(date.MonthsBetween(19700101,19701231)=11, CONST);
-    ASSERT(date.MonthsBetween(19701231,19710101)=0, CONST);
-    ASSERT(date.MonthsBetween(19701231,19711231)=12, CONST);
-    ASSERT(date.MonthsBetween(19711231,19701231)=-12, CONST);
-    ASSERT(date.MonthsBetween(19700606,19700706)=1, CONST);
-    ASSERT(date.MonthsBetween(19700606,19700705)=0, CONST);
-    ASSERT(date.MonthsBetween(19700606,19700607)=0, CONST);
+    ASSERT(Date.FromDaysSince1900(0) = 19000101, CONST);
+    ASSERT(Date.ToGregorianDate(1) = 00010101, CONST);
+    ASSERT(Date.DaysSince1900(1900,1,1)=0, CONST);
+    ASSERT(Date.FromGregorianYMD(1,1,1)=1, CONST);
+    ASSERT(Date.ToJulianDate(1) = 00010101, CONST);
+    ASSERT(Date.FromJulianYMD(1,1,1)=1, CONST);
+
+    ASSERT(Date.Year(19990201) = 1999, CONST);
+    ASSERT(Date.Month(19990201) = 2, CONST);
+    ASSERT(Date.Day(19990201) = 1, CONST);
+
+    ASSERT(Date.Hour(123456) = 12, CONST);
+    ASSERT(Date.Minute(123456) = 34, CONST);
+    ASSERT(Date.Second(123456) = 56, CONST);
+
+    ASSERT(Date.DateFromParts(1999,2,1) = 19990201, CONST);
+    ASSERT(Date.TimeFromParts(12,34,56) = 123456, CONST);
+
+    ASSERT(NOT Date.IsLeapYear(1900), CONST);
+    ASSERT(Date.IsLeapYear(1904), CONST);
+    ASSERT(NOT Date.IsLeapYear(2100), CONST);
+    ASSERT(Date.IsLeapYear(2000), CONST);
+    ASSERT(NOT Date.IsLeapYear(1901), CONST);
+
+    ASSERT(Date.IsDateLeapYear(20000201) = TRUE, CONST);
+    ASSERT(Date.IsDateLeapYear(20010201) = FALSE, CONST);
+    ASSERT(Date.IsDateLeapYear(21000201) = FALSE, CONST);
+
+    ASSERT(Date.IsJulianLeapYear(2000) = TRUE, CONST);
+    ASSERT(Date.IsJulianLeapYear(2001) = FALSE, CONST);
+
+    ASSERT(Date.YearsBetween(20010615,20020615) = 1, CONST);
+    ASSERT(Date.YearsBetween(20010615,20020614) = 0, CONST);
+    ASSERT(Date.YearsBetween(20020615,20010615) = -1, CONST);
+
+    ASSERT(Date.MonthsBetween(19700101,19701231)=11, CONST);
+    ASSERT(Date.MonthsBetween(19701231,19710101)=0, CONST);
+    ASSERT(Date.MonthsBetween(19701231,19711231)=12, CONST);
+    ASSERT(Date.MonthsBetween(19711231,19701231)=-12, CONST);
+    ASSERT(Date.MonthsBetween(19700606,19700706)=1, CONST);
+    ASSERT(Date.MonthsBetween(19700606,19700705)=0, CONST);
+    ASSERT(Date.MonthsBetween(19700606,19700607)=0, CONST);
+
+    ASSERT(Date.DaysBetween(20010615,20020615) = 365, CONST);
+    ASSERT(Date.DaysBetween(20010615,20020614) = 364, CONST);
+    ASSERT(Date.DaysBetween(20020615,20010615) = -365, CONST);
+
     ASSERT(TRUE, CONST)
   ];
 
+  EXPORT TestDynamicFunctions := [
+    ASSERT(Date.SecondsFromParts(1999,2,1,12,34,56,FALSE) = 917872496);     // UTC
+
+    ASSERT(Date.SecondsToParts(917872496).year = 1999);
+    ASSERT(Date.SecondsToParts(917872496).month = 2);
+    ASSERT(Date.SecondsToParts(917872496).day = 1);
+    ASSERT(Date.SecondsToParts(917872496).hour = 12);
+    ASSERT(Date.SecondsToParts(917872496).minute = 34);
+    ASSERT(Date.SecondsToParts(917872496).second = 56);
+
+    ASSERT(Date.DayOfWeek(20140130) = 5);   // 5=Thursday
+
+    ASSERT(Date.AdjustDate(20000130, month_delta:=1) = 20000301);
+    ASSERT(Date.AdjustDate(20000130, month_delta:=1, day_delta:=-1) = 20000229);
+    ASSERT(Date.AdjustDate(20000229, year_delta:=1) = 20010301);
+    ASSERT(Date.AdjustDate(20000229, year_delta:=-1) = 19990301);
+
+    ASSERT(Date.AdjustDateBySeconds(20140130, 172800) = 20140201);
+
+    ASSERT(Date.AdjustTime(180000, hour_delta:=7) = 010000);
+    ASSERT(Date.AdjustTime(180000, minute_delta:=420) = 010000);
+    ASSERT(Date.AdjustTime(180000, second_delta:=-86400) = 180000);
+
+    ASSERT(Date.AdjustTimeBySeconds(180000, 86400) = 180000);
+    ASSERT(Date.AdjustTimeBySeconds(180000, -86400) = 180000);
+
+    ASSERT(Date.AdjustSeconds(917872496, hour_delta:=1) = 917876096);
+
+    ASSERT(Date.AdjustCalendar(20140130, month_delta:=1) = 20140228);
+
+    ASSERT(Date.AdjustCalendar(20000229, year_delta:=1) = 20010228);
+    ASSERT(Date.AdjustCalendar(20000229, year_delta:=4) = 20040229);
+
+    ASSERT(Date.IsValidDate(vDate, 2014, 2050));
+    ASSERT(Date.IsValidDate(vToday, 2014, 2050));
+
+    ASSERT(Date.IsValidTime(vTime));
+    ASSERT(Date.IsValidTime(vTimeLocal));
+
+    ASSERT(vSeconds + vLocalTimeZoneOffset = vSecondsLocal);
+
+    ASSERT(vSeconds = TRUNCATE(vTimestamp));
+    ASSERT(vSeconds + vLocalTimeZoneOffset = TRUNCATE(vTimestampLocal));
+
+    // IsLocalDaylightSavingsInEffect() -- not possible to check without pinning both cluster location and date
+
+    ASSERT(Date.DatesForMonth(20141215).startDate = 20141201);
+    ASSERT(Date.DatesForMonth(20141215).endDate = 20141231);
+
+    ASSERT(Date.DatesForWeek(20141030).startDate = 20141026);
+    ASSERT(Date.DatesForWeek(20141030).endDate = 20141101);
+
+    ASSERT(Date.IsValidDate(20141030) = TRUE);
+    ASSERT(Date.IsValidDate(20000229) = TRUE);
+    ASSERT(Date.IsValidDate(20010229) = FALSE);
+
+    ASSERT(Date.IsValidTime(123456) = TRUE);
+    ASSERT(Date.IsValidTime(123465) = FALSE);
+
+    ASSERT(vCreateDateTimeFromSeconds.year = 1999);
+    ASSERT(vCreateDateTimeFromSeconds.month = 2);
+    ASSERT(vCreateDateTimeFromSeconds.day = 1);
+    ASSERT(vCreateDateTimeFromSeconds.hour = 12);
+    ASSERT(vCreateDateTimeFromSeconds.minute = 34);
+    ASSERT(vCreateDateTimeFromSeconds.second = 56);
+
+    ASSERT(vCreateDateFromSeconds.year = 1999);
+    ASSERT(vCreateDateFromSeconds.month = 2);
+    ASSERT(vCreateDateFromSeconds.day = 1);
+
+    ASSERT(vCreateTimeFromSeconds.hour = 12);
+    ASSERT(vCreateTimeFromSeconds.minute = 34);
+    ASSERT(vCreateTimeFromSeconds.second = 56);
+
+    ASSERT(vCreateTime.hour = 12);
+    ASSERT(vCreateTime.minute = 34);
+    ASSERT(vCreateTime.second = 56);
+
+    ASSERT(vCreateDateTime.year = 1999);
+    ASSERT(vCreateDateTime.month = 2);
+    ASSERT(vCreateDateTime.day = 1);
+    ASSERT(vCreateDateTime.hour = 12);
+    ASSERT(vCreateDateTime.minute = 34);
+    ASSERT(vCreateDateTime.second = 56);
+
+    ASSERT(Date.TimeFromTimeRec(vCreateTime) = 123456);
+
+    ASSERT(Date.DateFromDateTimeRec(vCreateDateTime) = 19990201);
+
+    ASSERT(Date.TimeFromDateTimeRec(vCreateDateTime) = 123456);
+
+    ASSERT(Date.SecondsFromDateTimeRec(vCreateDateTime) = 917872496);
+
+    ASSERT(TRUE)
+  ];
+
   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);
+    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];
 
@@ -59,10 +199,10 @@ EXPORT TestDate := MODULE
 
     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)));
+       result1 := ASSERT(next, Date.ToGregorianDate(next.day) = Date.DateFromDateRec(next.gregorian));
+       result2 := ASSERT(result1, next.day = Date.FromGregorianDate(Date.DateFromDateRec(next.gregorian)));
+       result3 := ASSERT(result2, Date.ToJulianDate(next.day) = Date.DateFromDateRec(next.julian));
+       result4 := ASSERT(result3, next.day = Date.FromJulianDate(Date.DateFromDateRec(next.julian)));
        RETURN result4;
     END;
 
@@ -71,6 +211,6 @@ EXPORT TestDate := MODULE
     EXPORT Test01 := OUTPUT(x);
   END;
 
-  EXPORT Main := [EVALUATE(TestConstant), EVALUATE(TestDynamic)];
+  EXPORT Main := [EVALUATE(TestConstant), EVALUATE(TestDynamicFunctions), EVALUATE(TestDynamic)];
 
 END;

+ 57 - 24
ecllibrary/teststd/Date/TestFormat.ecl

@@ -6,36 +6,69 @@ IMPORT Std.Date;
 
 EXPORT TestFormat := MODULE
 
-  SHARED DateFormats := ['%d %b %Y', '%Y %b %d', '%Y%m%d', '%Y-%m-%d', '%d/%m/%Y'];
+  SHARED DateFormats := ['%d %b %Y', '%Y %b %d', '%Y%m%d', '%Y-%m-%d', '%d/%m/%Y', '%m/%d/%Y'];
+  SHARED TimeFormats := ['%H%M%S', '%T', '%R'];
 
   EXPORT TestConstant := [
-    ASSERT(date.ToString(19700101, '%Y-%m-%d') = '1970-01-01', CONST);
-    ASSERT(date.ToString(19700101, '%d/%m/%y') = '01/01/70', CONST);
-    ASSERT(date.ToString(20110302, '%d %b %Y') = '02 Mar 2011', CONST);
-    ASSERT(date.ToString(20111202, '%d %B %Y') = '02 December 2011', CONST);
-
-    ASSERT(date.FromString('19700001', '%Y%m%d') = 0, CONST);
-    ASSERT(date.FromString('19701000', '%Y%m%d') = 0, CONST);
-    ASSERT(date.FromString('19700101', '%Y%m%d') = 19700101, CONST);
-    ASSERT(date.FromString('68011', '%y%m%d') = 20680101, CONST);
-    ASSERT(date.FromString('69011', '%y%m%d') = 19690101, CONST);
-    ASSERT(date.FromString('1 \t Dec   2056', '%d %b %Y') = 20561201, CONST);
-    ASSERT(date.FromString('1 \t December  1862', '%d %b %Y') = 18621201, CONST);
-    ASSERT(date.FromString('31 \t jAN 12', '%d %b %Y') = 120131, CONST);
-    ASSERT(date.FromString('1 \t De   2056', '%d %b %Y') = 0, CONST);
-    ASSERT(date.FromString('1December1', '%d%b%Y') = 00011201, CONST);
-//    ASSERT(date.MatchDateString('1dec2011',DateFormats) = 20111201, CONST);
+    ASSERT(Date.FromStringToDate('19700001', '%Y%m%d') = 0, CONST);
+    ASSERT(Date.FromStringToDate('19701000', '%Y%m%d') = 0, CONST);
+    ASSERT(Date.FromStringToDate('19700101', '%Y%m%d') = 19700101, CONST);
+    ASSERT(Date.FromStringToDate('68011', '%y%m%d') = 20680101, CONST);
+    ASSERT(Date.FromStringToDate('69011', '%y%m%d') = 19690101, CONST);
+    ASSERT(Date.FromStringToDate('1 \t Dec   2056', '%d %b %Y') = 20561201, CONST);
+    ASSERT(Date.FromStringToDate('1 \t December  1862', '%d %b %Y') = 18621201, CONST);
+    ASSERT(Date.FromStringToDate('31 \t jAN 12', '%d %b %Y') = 120131, CONST);
+    ASSERT(Date.FromStringToDate('1 \t De   2056', '%d %b %Y') = 0, CONST);
+    ASSERT(Date.FromStringToDate('1December1', '%d%b%Y') = 00011201, CONST);
+    ASSERT(Date.FromStringToDate('1970-02-01', '%F') = 19700201, CONST);
+
+    ASSERT(Date.FromStringToTime('12:34:56', '%H:%M:%S') = 123456, CONST);
+    ASSERT(Date.FromStringToTime('12:34:56', '%T') = 123456, CONST);
+    ASSERT(Date.FromStringToTime('12:34', '%R') = 123400, CONST);
+
     ASSERT(TRUE)
   ];
 
   EXPORT TestDynamic := [
-    ASSERT(date.MatchDateString('1dec2011',DateFormats) = 20111201);
-    ASSERT(date.MatchDateString('2011dec1',DateFormats) = 20111201);
-    ASSERT(date.MatchDateString('1 december 2011',DateFormats) = 20111201);
-    ASSERT(date.MatchDateString('2011\tdecem\t01',DateFormats) = 20111201);
-    ASSERT(date.MatchDateString('20111201',DateFormats) = 20111201);
-    ASSERT(date.MatchDateString('2011-12-01',DateFormats) = 20111201);
-    ASSERT(date.MatchDateString('1/12/2011',DateFormats) = 20111201);
+    ASSERT(Date.MatchDateString('1dec2011',DateFormats) = 20111201);
+    ASSERT(Date.MatchDateString('2011dec1',DateFormats) = 20111201);
+    ASSERT(Date.MatchDateString('1 december 2011',DateFormats) = 20111201);
+    ASSERT(Date.MatchDateString('2011\tdecem\t01',DateFormats) = 20111201);
+    ASSERT(Date.MatchDateString('20111201',DateFormats) = 20111201);
+    ASSERT(Date.MatchDateString('2011-12-01',DateFormats) = 20111201);
+    ASSERT(Date.MatchDateString('1/12/2011',DateFormats) = 20111201);
+
+    ASSERT(Date.DateToString(19700101, '%Y-%m-%d') = '1970-01-01');
+    ASSERT(Date.DateToString(19700101, '%d/%m/%y') = '01/01/70');
+    ASSERT(Date.DateToString(20110302, '%d %b %Y') = '02 Mar 2011');
+    ASSERT(Date.DateToString(20111202, '%d %B %Y') = '02 December 2011');
+
+    ASSERT(Date.MatchDateString('1dec2011',DateFormats) = 20111201);
+    ASSERT(Date.MatchDateString('2011dec1',DateFormats) = 20111201);
+    ASSERT(Date.MatchDateString('1 december 2011',DateFormats) = 20111201);
+    ASSERT(Date.MatchDateString('2011\tdecem\t01',DateFormats) = 20111201);
+    ASSERT(Date.MatchDateString('20111201',DateFormats) = 20111201);
+    ASSERT(Date.MatchDateString('2011-12-01',DateFormats) = 20111201);
+    ASSERT(Date.MatchDateString('1/12/2011',DateFormats) = 20111201);
+
+    ASSERT(Date.MatchTimeString('123456',TimeFormats) = 123456);
+    ASSERT(Date.MatchTimeString('12:34:56',TimeFormats) = 123456);
+    ASSERT(Date.MatchTimeString('12:34',TimeFormats) = 123400);
+
+    ASSERT(Date.DateToString(19990201,'%F') = '1999-02-01');
+
+    ASSERT(Date.TimeToString(123456,'%T') = '12:34:56');
+
+    ASSERT(Date.SecondsToString(917872496,'%FT%T') = '1999-02-01T12:34:56');
+
+    ASSERT(Date.ConvertDateFormat('1/12/2011','%m/%d/%Y','%F') = '2011-01-12');
+
+    ASSERT(Date.ConvertTimeFormat('123456','%H%M%S','%T') = '12:34:56');
+
+    ASSERT(Date.ConvertDateFormatMultiple('1/31/2011',DateFormats,'%F') = '2011-01-31');
+
+    ASSERT(Date.ConvertTimeFormatMultiple('123456',TimeFormats,'%T') = '12:34:56');
+
     ASSERT(TRUE)
   ];
 

+ 1 - 0
plugins/CMakeLists.txt

@@ -19,6 +19,7 @@ add_subdirectory (fileservices)
 add_subdirectory (logging)
 add_subdirectory (parselib)
 add_subdirectory (stringlib)
+add_subdirectory (timelib)
 add_subdirectory (unicodelib)
 add_subdirectory (workunitservices)
 add_subdirectory (proxies)

+ 85 - 1
plugins/stringlib/stringlib.cpp

@@ -83,7 +83,9 @@ static const char * EclDefinition =
 "  SET OF STRING SplitWords(const string src, const string _separator, BOOLEAN allow_blanks) : c, pure,entrypoint='slSplitWords'; \n"
 "  STRING CombineWords(set of string src, const string _separator) : c, pure,entrypoint='slCombineWords'; \n"
 "  UNSIGNED4 StringToDate(const string src, const varstring format) : c, pure,entrypoint='slStringToDate'; \n"
+"  UNSIGNED4 StringToTimeOfDay(const string src, const varstring format) : c, pure,entrypoint='slStringToTimeOfDay'; \n"
 "  UNSIGNED4 MatchDate(const string src, set of varstring formats) : c, pure,entrypoint='slMatchDate'; \n"
+"  UNSIGNED4 MatchTimeOfDay(const string src, set of varstring formats) : c, pure,entrypoint='slMatchTimeOfDay'; \n"
 "  STRING FormatDate(UNSIGNED4 date, const varstring format) : c, pure,entrypoint='slFormatDate'; \n"
 "  STRING StringRepeat(const string src, unsigned4 n) : c, pure,entrypoint='slStringRepeat'; \n"
 "END;";
@@ -1320,7 +1322,7 @@ STRINGLIB_API void STRINGLIB_CALL slCombineWords(size32_t & __lenResult, void *
 
 //--------------------------------------------------------------------------------------------------------------------
 
-inline bool readValue(unsigned & value, size32_t & _offset, size32_t lenStr, const char * str, unsigned max)
+inline bool readValue(unsigned & value, size32_t & _offset, size32_t lenStr, const char * str, unsigned max, bool spaceIsZero = false)
 {
     unsigned total = 0;
     unsigned offset = _offset;
@@ -1332,6 +1334,8 @@ inline bool readValue(unsigned & value, size32_t & _offset, size32_t lenStr, con
         char next = str[offset+i];
         if (next >= '0' && next <= '9')
             total = total * 10 + (next - '0');
+    	else if (next == ' ' && spaceIsZero)
+            total = total * 10;
         else
             break;
     }
@@ -1384,6 +1388,44 @@ static const char * simple_strptime(size32_t lenStr, const char * str, const cha
         {
             switch (*curFormat++)
             {
+            // Recursive cases
+            case 'F':
+            	{
+            		const char*	newPtr = simple_strptime(lenStr-offset, str+offset, "%Y-%m-%d", tm);
+            		
+            		if (!newPtr)
+            			return NULL;
+            		offset = newPtr - str;
+            	}
+            	break;
+            case 'D':
+            	{
+            		const char*	newPtr = simple_strptime(lenStr-offset, str+offset, "%m/%d/%y", tm);
+            		
+            		if (!newPtr)
+            			return NULL;
+            		offset = newPtr - str;
+            	}
+            	break;
+            case 'R':
+            	{
+            		const char*	newPtr = simple_strptime(lenStr-offset, str+offset, "%H:%M", tm);
+            		
+            		if (!newPtr)
+            			return NULL;
+            		offset = newPtr - str;
+            	}
+            	break;
+            case 'T':
+            	{
+            		const char*	newPtr = simple_strptime(lenStr-offset, str+offset, "%H:%M:%S", tm);
+            		
+            		if (!newPtr)
+            			return NULL;
+            		offset = newPtr - str;
+            	}
+            	break;
+            // Non-recursive cases
             case 't':
                 while ((offset < lenStr) && isspace(src[offset]))
                     offset++;
@@ -1408,6 +1450,11 @@ static const char * simple_strptime(size32_t lenStr, const char * str, const cha
                     return NULL;
                 tm->tm_mday = value;
                 break;
+            case 'e':
+                if (!readValue(value, offset, lenStr, str, 2, true) || (value < 1) || (value > 31))
+                    return NULL;
+                tm->tm_mday = value;
+                break;
             case 'b':
             case 'B':
             case 'h':
@@ -1420,6 +1467,11 @@ static const char * simple_strptime(size32_t lenStr, const char * str, const cha
                     return NULL;
                 tm->tm_hour = value;
                 break;
+            case 'k':
+                if (!readValue(value, offset, lenStr, str, 2, true)|| (value > 24))
+                    return NULL;
+                tm->tm_hour = value;
+                break;
             case 'M':
                 if (!readValue(value, offset, lenStr, str, 2)|| (value > 59))
                     return NULL;
@@ -1457,6 +1509,12 @@ inline unsigned makeDate(const tm & tm)
     return (tm.tm_year + 1900) * 10000 + (tm.tm_mon + 1) * 100 + tm.tm_mday;
 }
 
+
+inline unsigned makeTimeOfDay(const tm & tm)
+{
+    return (tm.tm_hour * 10000) + (tm.tm_min * 100) + tm.tm_sec;
+}
+
 inline void extractDate(tm & tm, unsigned date)
 {
     tm.tm_year = (date / 10000) - 1900;
@@ -1475,6 +1533,15 @@ STRINGLIB_API unsigned STRINGLIB_CALL slStringToDate(size32_t lenS, const char *
     return 0;
 }
 
+STRINGLIB_API unsigned STRINGLIB_CALL slStringToTimeOfDay(size32_t lenS, const char * s, const char * fmtin)
+{
+    struct tm tm;
+    memset(&tm, 0, sizeof(tm));
+    if (simple_strptime(lenS, s, fmtin, &tm))
+        return makeTimeOfDay(tm);
+    return 0;
+}
+
 
 STRINGLIB_API unsigned STRINGLIB_CALL slMatchDate(size32_t lenS, const char * s, bool isAllFormats, unsigned lenFormats, const void * _formats)
 {
@@ -1492,6 +1559,23 @@ STRINGLIB_API unsigned STRINGLIB_CALL slMatchDate(size32_t lenS, const char * s,
     return 0;
 }
 
+
+STRINGLIB_API unsigned STRINGLIB_CALL slMatchTimeOfDay(size32_t lenS, const char * s, bool isAllFormats, unsigned lenFormats, const void * _formats)
+{
+    struct tm tm;
+    memset(&tm, 0, sizeof(tm));
+
+    const char * formats = (const char *)_formats;
+    for (unsigned off=0; off < lenFormats; )
+    {
+        const char * curFormat = formats+off;
+        if (simple_strptime(lenS, s, curFormat, &tm))
+            return makeTimeOfDay(tm);
+        off += strlen(curFormat) + 1;
+    }
+    return 0;
+}
+
 STRINGLIB_API void STRINGLIB_CALL slFormatDate(size32_t & __lenResult, char * & __result, unsigned date, const char * format)
 {
     size32_t len = 0;

+ 2 - 0
plugins/stringlib/stringlib.hpp

@@ -85,7 +85,9 @@ STRINGLIB_API unsigned STRINGLIB_CALL slCountWords(size32_t lenSrc, const char *
 STRINGLIB_API void STRINGLIB_CALL slSplitWords(bool & __isAllResult, size32_t & __lenResult, void * & __result, size32_t lenSrc, const char * src, size32_t lenSeparator, const char * separator, bool allowBlankItems);
 STRINGLIB_API void STRINGLIB_CALL slCombineWords(size32_t & __lenResult, void * & __result, bool isAllSrc, size32_t lenSrc, const char * src, size32_t lenSeparator, const char * separator, bool allowBlankItems);
 STRINGLIB_API unsigned STRINGLIB_CALL slStringToDate(size32_t lenS, const char * s, const char * fmtin);
+STRINGLIB_API unsigned STRINGLIB_CALL slStringToTimeOfDay(size32_t lenS, const char * s, const char * fmtin);
 STRINGLIB_API unsigned STRINGLIB_CALL slMatchDate(size32_t lenS, const char * s, bool isAllFormats, unsigned lenFormats, const void * _formats);
+STRINGLIB_API unsigned STRINGLIB_CALL slMatchTimeOfDay(size32_t lenS, const char * s, bool isAllFormats, unsigned lenFormats, const void * _formats);
 STRINGLIB_API void STRINGLIB_CALL slFormatDate(size32_t & __lenResult, char * & __result, unsigned date, const char * format);
 STRINGLIB_API void STRINGLIB_CALL slStringRepeat(unsigned & tgtLen, char * & tgt, unsigned srcLen, const char * src, unsigned n);
 }

+ 48 - 0
plugins/timelib/CMakeLists.txt

@@ -0,0 +1,48 @@
+################################################################################
+#    HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+################################################################################
+
+
+# Component: timelib 
+
+#####################################################
+# Description:
+# ------------
+#    Cmake Input File for timelib
+#####################################################
+
+set ( toolsdir "${HPCC_SOURCE_DIR}/tools" )
+
+
+project( timelib ) 
+
+set (    SRCS 
+         timelib.cpp 
+    )
+
+include_directories ( 
+         ./../../system/include 
+         ./../../system/jlib
+         ./../../rtl/include
+         ./../../rtl/eclrtl
+    )
+
+ADD_DEFINITIONS( -D_USRDLL -DTIMELIB_EXPORTS )
+
+HPCC_ADD_LIBRARY( timelib SHARED ${SRCS} )
+install ( TARGETS timelib DESTINATION plugins )
+target_link_libraries ( timelib
+         eclrtl
+    )

+ 26 - 0
plugins/timelib/sourcedoc.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+################################################################################
+#    HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+################################################################################
+-->
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN" "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd">
+<section>
+    <title>plugins/timelib</title>
+
+    <para>
+        The plugins/timelib directory contains the sources for the plugins/timelib library.
+    </para>
+</section>

+ 659 - 0
plugins/timelib/timelib.cpp

@@ -0,0 +1,659 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#include <platform.h>
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <eclrtl.hpp>
+
+#ifdef _WINDOWS
+    #include <sys/timeb.h>
+#endif
+
+#include "timelib.hpp"
+
+static const char * compatibleVersions[] = {
+    NULL };
+
+#define TIMELIB_VERSION "TIMELIB 1.0.0"
+
+static const char * EclDefinition =
+"export TMPartsRec := RECORD \n"
+"  UNSIGNED4 v; \n"
+"END;"
+"export TimeLib := SERVICE\n"
+"  integer4 SecondsFromParts(integer2 year, unsigned1 month, unsigned1 day, unsigned1 hour, unsigned1 minute, unsigned1 second, boolean is_local_time) : c,pure,entrypoint='tlSecondsFromParts'; \n"
+"  DATASET(TMPartsRec) SecondsToParts(INTEGER8 seconds) : c,pure,entrypoint='tlSecondsToParts'; \n"
+"  UNSIGNED2 GetDayOfYear(INTEGER2 year, UNSIGNED1 month, UNSIGNED1 day) : c,pure,entrypoint='tlGetDayOfYear'; \n"
+"  UNSIGNED1 GetDayOfWeek(INTEGER2 year, UNSIGNED1 month, UNSIGNED1 day) : c,pure,entrypoint='tlGetDayOfWeek'; \n"
+"  STRING DateToString(UNSIGNED4 date, VARSTRING format) : c,pure,entrypoint='tlDateToString'; \n"
+"  STRING TimeToString(UNSIGNED3 time, VARSTRING format) : c,pure,entrypoint='tlTimeToString'; \n"
+"  STRING SecondsToString(INTEGER4 seconds, VARSTRING format) : c,pure,entrypoint='tlSecondsToString'; \n"
+"  UNSIGNED4 AdjustDate(UNSIGNED4 date, INTEGER2 year_delta, INTEGER4 month_delta, INTEGER4 day_delta) : c,pure,entrypoint='tlAdjustDate'; \n"
+"  UNSIGNED4 AdjustDateBySeconds(UNSIGNED4 date, INTEGER4 seconds_delta) : c,pure,entrypoint='tlAdjustDateBySeconds'; \n"
+"  UNSIGNED3 AdjustTime(UNSIGNED3 time, INTEGER2 hour_delta, INTEGER4 minute_delta, INTEGER4 second_delta) : c,pure,entrypoint='tlAdjustTime'; \n"
+"  UNSIGNED3 AdjustTimeBySeconds(UNSIGNED3 time, INTEGER4 seconds_delta) : c,pure,entrypoint='tlAdjustTimeBySeconds'; \n"
+"  INTEGER4 AdjustSeconds(INTEGER4 seconds, INTEGER2 year_delta, INTEGER4 month_delta, INTEGER4 day_delta, INTEGER2 hour_delta, INTEGER4 minute_delta, INTEGER4 second_delta) : c,pure,entrypoint='tlAdjustSeconds'; \n"
+"  UNSIGNED4 AdjustCalendar(UNSIGNED4 date, INTEGER2 year_delta, INTEGER4 month_delta, INTEGER4 day_delta) : c,pure,entrypoint='tlAdjustCalendar'; \n"
+"  BOOLEAN IsLocalDaylightSavingsInEffect() : c,pure,entrypoint='tlIsLocalDaylightSavingsInEffect'; \n"
+"  INTEGER4 LocalTimeZoneOffset() : c,pure,entrypoint='tlLocalTimeZoneOffset'; \n"
+"  UNSIGNED4 CurrentDate(BOOLEAN in_local_time) : c,pure,entrypoint='tlCurrentDate'; \n"
+"  UNSIGNED4 CurrentTime(BOOLEAN in_local_time) : c,pure,entrypoint='tlCurrentTime'; \n"
+"  INTEGER4 CurrentSeconds(BOOLEAN in_local_time) : c,pure,entrypoint='tlCurrentSeconds'; \n"
+"  REAL8 CurrentTimestamp(BOOLEAN in_local_time) : c,pure,entrypoint='tlCurrentTimestamp'; \n"
+"  UNSIGNED4 GetLastDayOfMonth(UNSIGNED4 date) : c,pure,entrypoint='tlGetLastDayOfMonth'; \n"
+"  DATASET(TMPartsRec) DatesForWeek(UNSIGNED4 date) : c,pure,entrypoint='tlDatesForWeek'; \n"
+"END;";
+
+TIMELIB_API bool getECLPluginDefinition(ECLPluginDefinitionBlock *pb)
+{
+    if (pb->size == sizeof(ECLPluginDefinitionBlockEx))
+    {
+        ECLPluginDefinitionBlockEx * pbx = (ECLPluginDefinitionBlockEx *) pb;
+        pbx->compatibleVersions = compatibleVersions;
+    }
+    else if (pb->size != sizeof(ECLPluginDefinitionBlock))
+        return false;
+    pb->magicVersion = PLUGIN_VERSION;
+    pb->version = TIMELIB_VERSION;
+    pb->moduleName = "lib_timelib";
+    pb->ECL = EclDefinition;
+    pb->flags = PLUGIN_IMPLICIT_MODULE | PLUGIN_MULTIPLE_VERSIONS;
+    pb->description = "TimeLib time manipulation library";
+    return true;
+}
+
+IPluginContext * parentCtx = NULL;
+
+TIMELIB_API void setPluginContext(IPluginContext * _ctx) { parentCtx = _ctx; }
+
+//------------------------------------------------------------------------------
+
+void tlMakeTimeStructFromUTCSeconds(time_t seconds, struct tm* timeInfo)
+{
+    #ifdef _WINDOWS
+        // gmtime is thread-safe under Windows
+        memcpy(timeInfo,gmtime(&seconds),sizeof(&timeInfo));
+    #else
+        gmtime_r(&seconds,timeInfo);
+    #endif
+}
+
+void tlInsertDateIntoTimeStruct(struct tm* timeInfo, unsigned int date)
+{
+    unsigned int    year = date / 10000;
+    unsigned int    month = (date - (year * 10000)) / 100;
+    unsigned int    day = date - (year * 10000) - (month * 100);
+
+    timeInfo->tm_year = year - 1900;
+    timeInfo->tm_mon = month - 1;
+    timeInfo->tm_mday = day;
+}
+
+unsigned int tlExtractDateFromTimeStruct(struct tm* timeInfo)
+{
+    unsigned int    result = 0;
+
+    result = (timeInfo->tm_year + 1900) * 10000;
+    result += (timeInfo->tm_mon + 1) * 100;
+    result += timeInfo->tm_mday;
+
+    return result;
+}
+
+void tlInsertTimeIntoTimeStruct(struct tm* timeInfo, unsigned int time)
+{
+    unsigned int    hour = time / 10000;
+    unsigned int    minute = (time - (hour * 10000)) / 100;
+    unsigned int    second = time - (hour * 10000) - (minute * 100);
+
+    timeInfo->tm_hour = hour;
+    timeInfo->tm_min = minute;
+    timeInfo->tm_sec = second;
+}
+
+unsigned int tlExtractTimeFromTimeStruct(struct tm* timeInfo)
+{
+    unsigned int    result = 0;
+
+    result = timeInfo->tm_hour * 10000;
+    result += timeInfo->tm_min * 100;
+    result += timeInfo->tm_sec;
+
+    return result;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API time_t TIMELIB_CALL tlSecondsFromParts(int year, unsigned int month, unsigned int day, unsigned int hour, unsigned int minute, unsigned int second, bool is_local_time)
+{
+    struct tm       timeInfo;
+    time_t          the_time = 0;
+
+    memset(&timeInfo,0,sizeof(timeInfo));
+
+    // Push each time part value into the tm struct
+    timeInfo.tm_sec = second;
+    timeInfo.tm_min = minute;
+    timeInfo.tm_hour = hour;
+    timeInfo.tm_mday = day;
+    timeInfo.tm_mon = month - 1;
+    timeInfo.tm_year = year - 1900;
+
+    // Get the initial time components; note that mktime assumes local time
+    the_time = mktime(&timeInfo);
+
+    if (!is_local_time)
+    {
+        // Adjust for time zone offset
+        the_time += timeInfo.tm_gmtoff;
+    }
+
+    return the_time;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API void TIMELIB_CALL tlSecondsToParts(size32_t &__lenResult, void* &__result, time_t seconds)
+{
+    struct tm       timeInfo;
+
+    tlMakeTimeStructFromUTCSeconds(seconds, &timeInfo);
+
+    __lenResult = sizeof(unsigned int) * 7;
+    __result = CTXMALLOC(parentCtx, __lenResult);
+
+    // Actually write the output values one at a time
+    unsigned int*   out = reinterpret_cast<unsigned int*>(__result);
+
+    out[0] = timeInfo.tm_sec;
+    out[1] = timeInfo.tm_min;
+    out[2] = timeInfo.tm_hour;
+    out[3] = timeInfo.tm_mday;
+    out[4] = timeInfo.tm_mon;
+    out[5] = timeInfo.tm_year;
+    out[6] = timeInfo.tm_wday;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API unsigned int TIMELIB_CALL tlGetDayOfYear(short year, unsigned short month, unsigned short day)
+{
+    struct tm       timeInfo;
+
+    memset(&timeInfo,0,sizeof(timeInfo));
+
+    // Push each time part value into the tm struct
+    timeInfo.tm_mday = day;
+    timeInfo.tm_mon = month - 1;
+    timeInfo.tm_year = year - 1900;
+
+    mktime(&timeInfo);
+
+    return timeInfo.tm_yday;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API unsigned int TIMELIB_CALL tlGetDayOfWeek(short year, unsigned short month, unsigned short day)
+{
+    struct tm       timeInfo;
+
+    memset(&timeInfo,0,sizeof(timeInfo));
+
+    // Push each time part value into the tm struct
+    timeInfo.tm_mday = day;
+    timeInfo.tm_mon = month - 1;
+    timeInfo.tm_year = year - 1900;
+
+    mktime(&timeInfo);
+
+    return timeInfo.tm_wday;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API void TIMELIB_CALL tlDateToString(size32_t &__lenResult, char* &__result, unsigned int date, const char* format)
+{
+    struct tm       timeInfo;
+    size_t          kBufferSize = 256;
+    char            buffer[kBufferSize];
+
+    memset(&timeInfo,0,sizeof(timeInfo));
+    tlInsertDateIntoTimeStruct(&timeInfo,date);
+
+    __lenResult = strftime(buffer,kBufferSize,format,&timeInfo);
+    __result = NULL;
+
+    if (__lenResult > 0)
+    {
+        __result = reinterpret_cast<char*>(CTXMALLOC(parentCtx, __lenResult));
+        memcpy(__result,buffer,__lenResult);
+    }
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API void TIMELIB_CALL tlTimeToString(size32_t &__lenResult, char* &__result, unsigned int time, const char* format)
+{
+    struct tm       timeInfo;
+    size_t          kBufferSize = 256;
+    char            buffer[kBufferSize];
+
+    memset(&timeInfo,0,sizeof(timeInfo));
+    tlInsertTimeIntoTimeStruct(&timeInfo,time);
+
+    __lenResult = strftime(buffer,kBufferSize,format,&timeInfo);
+    __result = NULL;
+
+    if (__lenResult > 0)
+    {
+        __result = reinterpret_cast<char*>(rtlMalloc(__lenResult));
+        memcpy(__result,buffer,__lenResult);
+    }
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API void TIMELIB_CALL tlSecondsToString(size32_t &__lenResult, char* &__result, int seconds, const char* format)
+{
+    struct tm   timeInfo;
+    time_t      theTime = seconds;
+    size_t      kBufferSize = 256;
+    char        buffer[kBufferSize];
+
+    memset(buffer,kBufferSize,0);
+
+    #ifdef _WINDOWS
+        // gmtime is thread-safe under Windows
+        memcpy(&timeInfo,gmtime(&theTime),sizeof(timeInfo));
+    #else
+        gmtime_r(&theTime,&timeInfo);
+    #endif
+
+    __lenResult = strftime(buffer,kBufferSize,format,&timeInfo);
+    __result = NULL;
+
+    if (__lenResult > 0)
+    {
+        __result = reinterpret_cast<char*>(rtlMalloc(__lenResult));
+        memcpy(__result,buffer,__lenResult);
+    }
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API unsigned int TIMELIB_CALL tlAdjustDate(unsigned int date, short year_delta, int month_delta, int day_delta)
+{
+    struct tm       timeInfo;
+    unsigned int    result = 0;
+
+    memset(&timeInfo,0,sizeof(timeInfo));
+
+    tlInsertDateIntoTimeStruct(&timeInfo,date);
+
+    timeInfo.tm_year += year_delta;
+    timeInfo.tm_mon += month_delta;
+    timeInfo.tm_mday += day_delta;
+
+    mktime(&timeInfo);
+
+    result = tlExtractDateFromTimeStruct(&timeInfo);
+
+    return result;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API unsigned int TIMELIB_CALL tlAdjustDateBySeconds(unsigned int date, int seconds_delta)
+{
+    struct tm       timeInfo;
+    unsigned int    result = 0;
+
+    memset(&timeInfo,0,sizeof(timeInfo));
+
+    tlInsertDateIntoTimeStruct(&timeInfo,date);
+    timeInfo.tm_sec = seconds_delta;
+
+    mktime(&timeInfo);
+
+    result = tlExtractDateFromTimeStruct(&timeInfo);
+
+    return result;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API unsigned int TIMELIB_CALL tlAdjustTime(unsigned int time, short hour_delta, int minute_delta, int second_delta)
+{
+    struct tm       timeInfo;
+    unsigned int    result = 0;
+
+    memset(&timeInfo,0,sizeof(timeInfo));
+
+    tlInsertTimeIntoTimeStruct(&timeInfo,time);
+
+    timeInfo.tm_hour += hour_delta;
+    timeInfo.tm_min += minute_delta;
+    timeInfo.tm_sec += second_delta;
+
+    mktime(&timeInfo);
+
+    result = tlExtractTimeFromTimeStruct(&timeInfo);
+
+    return result;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API unsigned int TIMELIB_CALL tlAdjustTimeBySeconds(unsigned int time, int seconds_delta)
+{
+    struct tm       timeInfo;
+    unsigned int    result = 0;
+
+    memset(&timeInfo,0,sizeof(timeInfo));
+
+    tlInsertTimeIntoTimeStruct(&timeInfo,time);
+    timeInfo.tm_sec += seconds_delta;
+
+    mktime(&timeInfo);
+
+    result = tlExtractTimeFromTimeStruct(&timeInfo);
+
+    return result;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API time_t TIMELIB_CALL tlAdjustSeconds(time_t seconds, short year_delta, int month_delta, int day_delta, short hour_delta, int minute_delta, int second_delta)
+{
+    struct tm       timeInfo;
+    time_t          result = 0;
+
+    #ifdef _WINDOWS
+        // localtime is thread-safe under Windows
+        memcpy(&timeInfo,localtime(&seconds),sizeof(timeInfo));
+    #else
+        localtime_r(&seconds,&timeInfo);
+    #endif
+
+    timeInfo.tm_year += year_delta;
+    timeInfo.tm_mon += month_delta;
+    timeInfo.tm_mday += day_delta;
+    timeInfo.tm_hour += hour_delta;
+    timeInfo.tm_min += minute_delta;
+    timeInfo.tm_sec += second_delta;
+
+    result = mktime(&timeInfo);
+
+    return result;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API unsigned int TIMELIB_CALL tlAdjustCalendar(unsigned int date, short year_delta, int month_delta, int day_delta)
+{
+    struct tm       timeInfo;
+    unsigned int    year = date / 10000;
+    unsigned int    month = (date - (year * 10000)) / 100;
+    unsigned int    day = date - (year * 10000) - (month * 100);
+    int             expectedMonthVal = month + month_delta - 1;
+    unsigned int    result = 0;
+
+    // Normalize the expected month value
+    if (expectedMonthVal >= 0)
+    {
+        expectedMonthVal = expectedMonthVal % 12;
+    }
+    else
+    {
+        expectedMonthVal = 12 - (abs(expectedMonthVal) % 12);
+    }
+
+    memset(&timeInfo,0,sizeof(timeInfo));
+
+    timeInfo.tm_year = year - 1900;
+    timeInfo.tm_mon = month - 1;
+    timeInfo.tm_mday = day;
+
+    timeInfo.tm_year += year_delta;
+    timeInfo.tm_mon += month_delta;
+
+    mktime(&timeInfo);
+
+    if (timeInfo.tm_mon != expectedMonthVal)
+    {
+        // If the returned month doesn't match the expected month, we need to
+        // go back to the last day of the previous month
+        timeInfo.tm_mday = 0;
+        mktime(&timeInfo);
+    }
+
+    // Now apply the day delta
+    timeInfo.tm_mday += day_delta;
+    mktime(&timeInfo);
+
+    result = tlExtractDateFromTimeStruct(&timeInfo);
+
+    return result;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API bool TIMELIB_CALL tlIsLocalDaylightSavingsInEffect()
+{
+    struct tm       timeInfo;
+    time_t          theTime = time(NULL);
+
+    #ifdef _WINDOWS
+        // localtime is thread-safe under Windows
+        memcpy(&timeInfo,localtime(&theTime),sizeof(timeInfo));
+    #else
+        localtime_r(&theTime,&timeInfo);
+    #endif
+
+    return (timeInfo.tm_isdst == 1);
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API int TIMELIB_CALL tlLocalTimeZoneOffset()
+{
+    struct tm       timeInfo;
+    time_t          theTime = time(NULL);
+
+    #ifdef _WINDOWS
+        // localtime is thread-safe under Windows
+        memcpy(&timeInfo,localtime(&theTime),sizeof(timeInfo));
+    #else
+        localtime_r(&theTime,&timeInfo);
+    #endif
+
+    return timeInfo.tm_gmtoff;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API unsigned int TIMELIB_CALL tlCurrentDate(bool in_local_time)
+{
+    struct tm       timeInfo;
+    time_t          theTime = time(NULL);
+    unsigned int    result = 0;
+
+    // Create time parts differently depending on whether you need
+    // UTC or local time
+    if (in_local_time)
+    {
+        #ifdef _WINDOWS
+            // localtime is thread-safe under Windows
+            memcpy(&timeInfo,localtime(&theTime),sizeof(timeInfo));
+        #else
+            localtime_r(&theTime,&timeInfo);
+        #endif
+    }
+    else
+    {
+        #ifdef _WINDOWS
+            // gmtime is thread-safe under Windows
+            memcpy(&timeInfo,gmtime(&theTime),sizeof(timeInfo));
+        #else
+            gmtime_r(&theTime,&timeInfo);
+        #endif
+    }
+
+    result = tlExtractDateFromTimeStruct(&timeInfo);
+
+    return result;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API unsigned int TIMELIB_CALL tlCurrentTime(bool in_local_time)
+{
+    struct tm       timeInfo;
+    time_t          theTime = time(NULL);
+    unsigned int    result = 0;
+
+    // Create time parts differently depending on whether you need
+    // UTC or local time
+    if (in_local_time)
+    {
+        #ifdef _WINDOWS
+            // localtime is thread-safe under Windows
+            memcpy(&timeInfo,localtime(&theTime),sizeof(timeInfo));
+        #else
+            localtime_r(&theTime,&timeInfo);
+        #endif
+    }
+    else
+    {
+        #ifdef _WINDOWS
+            // gmtime is thread-safe under Windows
+            memcpy(&timeInfo,gmtime(&theTime),sizeof(timeInfo));
+        #else
+            gmtime_r(&theTime,&timeInfo);
+        #endif
+    }
+
+    result = tlExtractTimeFromTimeStruct(&timeInfo);
+
+    return result;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API time_t TIMELIB_CALL tlCurrentSeconds(bool in_local_time)
+{
+    time_t    result = time(NULL);
+
+    if (in_local_time)
+    {
+        result += tlLocalTimeZoneOffset();
+    }
+
+    return result;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API double TIMELIB_CALL tlCurrentTimestamp(bool in_local_time)
+{
+    double          result = 0.0;
+
+    #ifdef _WINDOWS
+        struct _timeb   now;
+
+        _ftime(&now);
+
+        result = now.time + (now.millitm / 1000.0);
+    #else
+        struct timeval  tv;
+
+        if (gettimeofday(&tv,NULL) == 0)
+        {
+            result = tv.tv_sec + (tv.tv_usec / 1000000.0);
+        }
+    #endif
+
+    if (in_local_time)
+    {
+        result += tlLocalTimeZoneOffset();
+    }
+
+    return result;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API unsigned int TIMELIB_CALL tlGetLastDayOfMonth(unsigned int date)
+{
+    struct tm       timeInfo;
+    unsigned int    result = 0;
+
+    memset(&timeInfo,0,sizeof(timeInfo));
+    tlInsertDateIntoTimeStruct(&timeInfo,date);
+
+    // Call mktime once to fix up any bogus data
+    mktime(&timeInfo);
+
+    // Adjust and call again
+    timeInfo.tm_mon += 1;
+    timeInfo.tm_mday = 0;
+    mktime(&timeInfo);
+
+    result = tlExtractDateFromTimeStruct(&timeInfo);
+
+    return result;
+}
+
+//------------------------------------------------------------------------------
+
+TIMELIB_API void TIMELIB_CALL tlDatesForWeek(size32_t &__lenResult, void* &__result, unsigned int date)
+{
+    struct tm       timeInfo;
+    unsigned int    weekStartResult = 0;
+    unsigned int    weekEndResult = 0;
+
+    memset(&timeInfo,0,sizeof(timeInfo));
+    tlInsertDateIntoTimeStruct(&timeInfo,date);
+
+    // Call mktime once to fix up any bogus data
+    mktime(&timeInfo);
+
+    // Adjust and call again
+    timeInfo.tm_mday -= timeInfo.tm_wday;
+    mktime(&timeInfo);
+
+    weekStartResult = tlExtractDateFromTimeStruct(&timeInfo);
+
+    // Adjust to the beginning of the week
+    timeInfo.tm_mday += 6;
+    mktime(&timeInfo);
+
+    weekEndResult = tlExtractDateFromTimeStruct(&timeInfo);
+
+    __lenResult = sizeof(unsigned int) * 2;
+    __result = CTXMALLOC(parentCtx, __lenResult);
+
+    unsigned int*   out = reinterpret_cast<unsigned int*>(__result);
+
+    out[0] = weekStartResult;
+    out[1] = weekEndResult;
+}

+ 74 - 0
plugins/timelib/timelib.hpp

@@ -0,0 +1,74 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#ifndef TIMELIB_INCL
+#define TIMELIB_INCL
+
+#ifdef _WIN32
+#define TIMELIB_CALL _cdecl
+#ifdef TIMELIB_EXPORTS
+#define TIMELIB_API __declspec(dllexport)
+#else
+#define TIMELIB_API __declspec(dllimport)
+#endif
+#else
+#define TIMELIB_CALL
+#define TIMELIB_API
+#endif
+
+#include <time.h>
+
+#include "platform.h"
+#include "hqlplugins.hpp"
+
+extern "C" {
+
+#ifdef TIMELIB_EXPORTS
+TIMELIB_API bool getECLPluginDefinition(ECLPluginDefinitionBlock *pb);
+TIMELIB_API void setPluginContext(IPluginContext * _ctx);
+#endif
+
+void tlMakeTimeStructFromUTCSeconds(time_t seconds, struct tm* timeInfo);
+void tlInsertDateIntoTimeStruct(struct tm* timeInfo, unsigned int date);
+unsigned int tlExtractDateFromTimeStruct(struct tm* timeInfo);
+void tlInsertTimeIntoTimeStruct(struct tm* timeInfo, unsigned int time);
+unsigned int tlExtractTimeFromTimeStruct(struct tm* timeInfo);
+
+TIMELIB_API time_t TIMELIB_CALL tlSecondsFromParts(int year, unsigned int month, unsigned int day, unsigned int hour, unsigned int minute, unsigned int second, bool is_local_time = false);
+TIMELIB_API void TIMELIB_CALL tlSecondsToParts(size32_t &__lenResult, void* &__result, time_t seconds);
+TIMELIB_API unsigned int TIMELIB_CALL tlGetDayOfYear(short year, unsigned short month, unsigned short day);
+TIMELIB_API unsigned int TIMELIB_CALL tlGetDayOfWeek(short year, unsigned short month, unsigned short day);
+TIMELIB_API void TIMELIB_CALL tlDateToString(size32_t &__lenResult, char* &__result, unsigned int date, const char* format);
+TIMELIB_API void TIMELIB_CALL tlTimeToString(size32_t &__lenResult, char* &__result, unsigned int time, const char* format);
+TIMELIB_API unsigned int TIMELIB_CALL tlAdjustDate(unsigned int date, short year_delta, int month_delta, int day_delta);
+TIMELIB_API void TIMELIB_CALL tlSecondsToString(size32_t &__lenResult, char* &__result, int seconds, const char* format);
+TIMELIB_API unsigned int TIMELIB_CALL tlAdjustDateBySeconds(unsigned int date, int seconds_delta);
+TIMELIB_API unsigned int TIMELIB_CALL tlAdjustTime(unsigned int time, short hour_delta, int minute_delta, int second_delta);
+TIMELIB_API unsigned int TIMELIB_CALL tlAdjustTimeBySeconds(unsigned int time, int seconds_delta);
+TIMELIB_API time_t TIMELIB_CALL tlAdjustSeconds(time_t seconds, short year_delta, int month_delta, int day_delta, short hour_delta, int minute_delta, int second_delta);
+TIMELIB_API unsigned int TIMELIB_CALL tlAdjustCalendar(unsigned int date, short year_delta, int month_delta, int day_delta);
+TIMELIB_API bool TIMELIB_CALL tlIsLocalDaylightSavingsInEffect();
+TIMELIB_API int TIMELIB_CALL tlLocalTimeZoneOffset();
+TIMELIB_API unsigned int TIMELIB_CALL tlCurrentDate(bool in_local_time);
+TIMELIB_API unsigned int TIMELIB_CALL tlCurrentTime(bool in_local_time);
+TIMELIB_API time_t TIMELIB_CALL tlCurrentSeconds(bool in_local_time);
+TIMELIB_API double TIMELIB_CALL tlCurrentTimestamp(bool in_local_time);
+TIMELIB_API unsigned int TIMELIB_CALL tlGetLastDayOfMonth(unsigned int date);
+TIMELIB_API void TIMELIB_CALL tlDatesForWeek(size32_t &__lenResult, void* &__result, unsigned int date);
+
+}
+#endif