Explorar o código

Add string conversion functions to date module

Signed-off-by: Gavin Halliday <gavin.halliday@lexisnexis.com>
Gavin Halliday %!s(int64=13) %!d(string=hai) anos
pai
achega
a28ed26589

+ 94 - 1
ecllibrary/std/Date.ecl

@@ -2,6 +2,10 @@
 ## Copyright (c) 2011 HPCC Systems.  All rights reserved.
 ############################################################################## */
 
+/* The functions defined in this module are provisional, and subject to change */
+
+import lib_stringlib.StringLib;
+
 EXPORT Date := MODULE
 
 // Three different date representations are defined
@@ -262,12 +266,28 @@ EXPORT ToDaysSince1900(Date_t date) := DaysSince1900(Year(date),Month(date),Day(
 
 EXPORT FromDaysSince1900(Days_t days) := ToGregorianDate(days + Date1900Delta);
 
+months_between(Date_t lower, Date_t upper) := FUNCTION
+    years := Year(upper) - Year(lower);
+    months := Month(upper) - Month(lower);
+    result := years * 12 + months;
+    RETURN result - IF (Day(upper) >= Day(lower), 0, 1);
+END;
+
+/*
+ * Calculate the number of whole months between two dates.
+ *
+ * @param from          The first date
+ * @param to            The last date
+ * @return              The number of months between them
+ */
+
+EXPORT MonthsBetween(Date_t from, Date_t to) := IF(from < to, months_between(from, to), -months_between(to, from));
+
 /*
  * Returns the current date
  *
  * @return              A date_t representing the current date.
  */
-import lib_stringlib.StringLib;
 EXPORT Today() := (date_t)StringLib.GetDateYYYYMMDD();
 
 /*
@@ -292,4 +312,77 @@ END;
  */
 EXPORT DateFromRec(date_rec date) := ( date.year * 100 + date.month ) * 100 + date.day;
 
+/*
+ * Converts a string to a date using the relevant string format.
+ *
+ * @param date_text     The string to be converted.
+ * @param format        The format of the input string.  (See documentation for strptime)
+                        e.g., http://linux.die.net/man/3/strftime
+ * @return              The date that was matched in the string.  Returns 0 if failed to match.
+ *
+ * Supported characters:
+    %b or %B    Month name (full or abbreviation)
+    %d            Day of month
+    %m            Month
+    %t            Whitespace
+    %y            year within century (00-99)
+    %Y            Full year (yyyy)
+
+Common date formats
+    American    '%m/%d/%Y'    mm/dd/yyyy
+    Euro        '%d/%m/%Y'    dd/mm/yyyy
+    Iso format    '%Y-%m-%d'    yyyy-mm-dd
+    Iso basic    '%Y%m%d'    yyyymmdd
+                '%d-%b-%Y'  dd-mon-yyyy    e.g., '21-Mar-1954'
+ */
+
+EXPORT Date_t FromString(STRING date_text, VARSTRING format) := StringLib.StringToDate(date_text, format);
+
+
+/*
+ * Matches a string against a set of date strings, and returns the Formats a date as a string.
+ *
+ * @param date_text     The string to be converted.
+ * @param formats       A set of formats to check against the string.   (See documentation for strptime)
+ * @return              The date that was matched in the string.  Returns 0 if failed to match.
+ */
+
+EXPORT Date_t MatchDateString(STRING date_text, SET OF VARSTRING formats) := StringLib.MatchDate(date_text, formats);
+
+
+/*
+ * Formats a date as a string.
+ *
+ * @param date          The date to be converted.
+ * @param format        The format the date is output in.  (See documentation for strftime)
+ * @return              Blank if date is 0, or the date output in the requested format.
+ */
+
+EXPORT STRING ToString(Date_t date, VARSTRING format) := StringLib.FormatDate(date, format);
+
+
+/*
+ * Converts a date from one format to another
+ *
+ * @param date_text     The string containing the date to be converted.
+ * @param from_format   The format the date is to be converted from.
+ * @param to_format     The format the date is to be converted to.
+ * @return              The converted string, or blank if it failed to match the format.
+ */
+
+EXPORT STRING ConvertFormat(STRING date_text, VARSTRING from_format='%m/%d/%Y', VARSTRING to_format='%Y%m%d') :=
+    StringLib.FormatDate(StringLib.StringToDate(date_text, from_format), to_format);
+
+/*
+ * Converts a date that matches one of a set of formats to another.
+ *
+ * @param date_text     The string containing the date to be converted.
+ * @param from_formats  The list of formats the date is to be converted from.
+ * @param to_format     The format the date is to be converted to.
+ * @return              The converted string, or blank if it failed to match the format.
+ */
+
+EXPORT STRING ConvertFormatMultiple(STRING date_text, SET OF VARSTRING from_formats, VARSTRING to_format='%Y%m%d') :=
+    StringLib.FormatDate(StringLib.MatchDate(date_text, from_formats), to_format);
+
 END;

+ 21 - 13
ecllibrary/teststd/Date/TestDate.ecl

@@ -6,19 +6,27 @@ 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 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(TRUE, CONST)
+  ];
 
   EXPORT TestDynamic := MODULE
     //Iterate through all lots of dates, incrementing the day and the date to check they convert correctly.

+ 44 - 0
ecllibrary/teststd/Date/TestFormat.ecl

@@ -0,0 +1,44 @@
+/*##############################################################################
+## Copyright (c) 2011 HPCC Systems.  All rights reserved.
+############################################################################## */
+
+IMPORT Std.Date;
+
+EXPORT TestFormat := MODULE
+
+  SHARED DateFormats := ['%d %b %Y', '%Y %b %d', '%Y%m%d', '%Y-%m-%d', '%d/%m/%Y'];
+
+  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(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(TRUE)
+  ];
+
+  EXPORT Main := [EVALUATE(TestConstant), EVALUATE(TestDynamic)];
+
+END;

+ 195 - 0
plugins/stringlib/stringlib.cpp

@@ -78,6 +78,9 @@ const char * EclDefinition =
 "  unsigned4 CountWords(const string src, const string _separator, BOOLEAN allow_blanks) : c, pure,entrypoint='slCountWords'; \n"
 "  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 MatchDate(const string src, set of varstring formats) : c, pure,entrypoint='slMatchDate'; \n"
+"  STRING FormatDate(UNSIGNED4 date, const varstring format) : c, pure,entrypoint='slFormatDate'; \n"
 "END;";
 
 STRINGLIB_API bool getECLPluginDefinition(ECLPluginDefinitionBlock *pb) 
@@ -1389,6 +1392,198 @@ STRINGLIB_API void STRINGLIB_CALL slCombineWords(size32_t & __lenResult, void *
     assert(target == result + sizeRequired);
 }
 
+//--------------------------------------------------------------------------------------------------------------------
+
+inline bool readValue(unsigned & value, size32_t & _offset, size32_t lenStr, const char * str, unsigned max)
+{
+    unsigned total = 0;
+    unsigned offset = _offset;
+    if (lenStr - offset < max)
+        max = lenStr - offset;
+    unsigned i=0;
+    for (; i < max; i++)
+    {
+        char next = str[offset+i];
+        if (next >= '0' && next <= '9')
+            total = total * 10 + (next - '0');
+        else
+            break;
+    }
+    if (i == 0)
+        return false;
+    value = total;
+    _offset = offset+i;
+    return true;
+}
+
+const char * const monthNames[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
+
+inline bool matchString(unsigned & value, size32_t & strOffset, size32_t lenStr, const byte * str, unsigned num, const char * const * strings, unsigned minMatch)
+{
+    unsigned startOffset = strOffset;
+    for (unsigned i =0; i < num; i++)
+    {
+        const char * cur = strings[i];
+        unsigned offset = startOffset;
+        while (offset < lenStr)
+        {
+            byte next = *cur++;
+            if (!next || toupper(next) != toupper(str[offset]))
+                break;
+            offset++;
+        }
+        if (offset - startOffset >= minMatch)
+        {
+            value = i;
+            strOffset = offset;
+            return true;
+        }
+    }
+    return false;
+}
+
+//This implements a subset of the specifiers allowed for strptime
+//Another difference is it works on a string with a separate length
+static const char * simple_strptime(size32_t lenStr, const char * str, const char * format, struct tm * tm)
+{
+    const char * curFormat = format;
+    size32_t offset = 0;
+    const byte * src = (const byte *)str;
+    unsigned value;
+
+    byte next;
+    while ((next = *curFormat++) != '\0')
+    {
+        if (next == '%')
+        {
+            switch (*curFormat++)
+            {
+            case 't':
+                while ((offset < lenStr) && isspace(src[offset]))
+                    offset++;
+                break;
+            case 'Y':
+                if (!readValue(value, offset, lenStr, str, 4))
+                    return NULL;
+                tm->tm_year = value-1900;
+                break;
+            case 'y':
+                if (!readValue(value, offset, lenStr, str, 2))
+                    return NULL;
+                tm->tm_year = value > 68 ? value : value + 100;
+                break;
+            case 'm':
+                if (!readValue(value, offset, lenStr, str, 2) || (value < 1) || (value > 12))
+                    return NULL;
+                tm->tm_mon = value-1;
+                break;
+            case 'd':
+                if (!readValue(value, offset, lenStr, str, 2) || (value < 1) || (value > 31))
+                    return NULL;
+                tm->tm_mday = value;
+                break;
+            case 'b':
+            case 'B':
+            case 'h':
+                if (!matchString(value, offset, lenStr, src, sizeof(monthNames)/sizeof(*monthNames), monthNames, 3))
+                    return NULL;
+                tm->tm_mon = value;
+                break;
+            case 'H':
+                if (!readValue(value, offset, lenStr, str, 2)|| (value > 24))
+                    return NULL;
+                tm->tm_hour = value;
+                break;
+            case 'M':
+                if (!readValue(value, offset, lenStr, str, 2)|| (value > 59))
+                    return NULL;
+                tm->tm_min = value;
+                break;
+            case 'S':
+                if (!readValue(value, offset, lenStr, str, 2)|| (value > 59))
+                    return NULL;
+                tm->tm_sec = value;
+                break;
+            default:
+                return NULL;
+            }
+        }
+        else
+        {
+            if (isspace(next))
+            {
+                while ((offset < lenStr) && isspace(src[offset]))
+                    offset++;
+            }
+            else
+            {
+                if ((offset >= lenStr) || (src[offset++] != next))
+                    return NULL;
+            }
+        }
+    }
+    return str+offset;
+}
+
+
+inline unsigned makeDate(const tm & tm)
+{
+    return (tm.tm_year + 1900) * 10000 + (tm.tm_mon + 1) * 100 + tm.tm_mday;
+}
+
+inline void extractDate(tm & tm, unsigned date)
+{
+    tm.tm_year = (date / 10000) - 1900;
+    tm.tm_mon = ((date / 100) % 100) - 1;
+    tm.tm_mday = (date % 100);
+}
+
+STRINGLIB_API unsigned STRINGLIB_CALL slStringToDate(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 makeDate(tm);
+    return 0;
+}
+
+
+STRINGLIB_API unsigned STRINGLIB_CALL slMatchDate(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 makeDate(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;
+    char * out = NULL;
+    if (date)
+    {
+        struct tm tm;
+        memset(&tm, 0, sizeof(tm));
+        extractDate(tm, date);
+        char buf[255];
+        strftime(buf, sizeof(buf), format, &tm);
+        len = strlen(buf);
+        out = static_cast<char *>(CTXMALLOC(parentCtx, len));
+        memcpy(out, buf, len);
+    }
+
+    __lenResult = len;
+    __result = out;
+}
+
 
 //--------------------------------------------------------------------------------------------------------------------
 //--------------------------------------------------------------------------------------------------------------------

+ 3 - 0
plugins/stringlib/stringlib.hpp

@@ -80,6 +80,9 @@ STRINGLIB_API unsigned STRINGLIB_CALL slStringWordCount(unsigned srcLen, const c
 STRINGLIB_API unsigned STRINGLIB_CALL slCountWords(size32_t lenSrc, const char * src, size32_t lenSeparator, const char * separator, bool allowBlankItems);
 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 slMatchDate(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);
 }
 
 #endif