Bladeren bron

HPCC-18148 Std.Date: Fix handling of of local time and DST

Signed-off-by: Dan S. Camper <dan.camper@lexisnexisrisk.com>
Dan S. Camper 7 jaren geleden
bovenliggende
commit
85618324c3
4 gewijzigde bestanden met toevoegingen van 74 en 29 verwijderingen
  1. 31 16
      ecllibrary/std/Date.ecl
  2. 14 3
      ecllibrary/teststd/Date/TestDate.ecl
  3. 27 8
      plugins/timelib/timelib.cpp
  4. 2 2
      plugins/timelib/timelib.hpp

+ 31 - 16
ecllibrary/std/Date.ecl

@@ -173,13 +173,16 @@ EXPORT Seconds_t SecondsFromParts(INTEGER2 year,
  * Gregorian calendar after the year 1600.
  *
  * @param seconds               The number of seconds since epoch.
+ * @param is_local_time         TRUE if seconds is expressed in local time
+ *                              rather than UTC, FALSE if seconds is expressed
+ *                              in UTC.  Optional, defaults to FALSE.
  * @return                      Module with exported attributes for year, month,
  *                              day, hour, minute, second, day_of_week, date
  *                              and time.
  */
 
-EXPORT SecondsToParts(Seconds_t seconds) := FUNCTION
-    parts := ROW(TimeLib.SecondsToParts(seconds));
+EXPORT SecondsToParts(Seconds_t seconds, BOOLEAN is_local_time = FALSE) := FUNCTION
+    parts := ROW(TimeLib.SecondsToParts(seconds, is_local_time));
 
     result := MODULE
         EXPORT INTEGER2 year := parts.year + 1900;
@@ -1065,7 +1068,8 @@ EXPORT BOOLEAN IsLocalDaylightSavingsInEffect() :=
  * Returns the offset (in seconds) of the time represented from UTC, with
  * positive values indicating locations east of the Prime Meridian.  Given a
  * UTC time in seconds since epoch, you can find the local time by adding the
- * result of this function to the seconds.
+ * result of this function to the seconds.  Note that daylight savings time is
+ * factored into the offset.
  *
  * @return              The number of seconds offset from UTC.
  */
@@ -1262,12 +1266,16 @@ END;
 /**
  * A transform to create a Date_rec from a Seconds_t value.
  *
- * @param seconds       The number seconds since epoch.
- * @return              A transform that creates a Date_rec containing the date.
+ * @param seconds               The number seconds since epoch.
+ * @param is_local_time         TRUE if seconds is expressed in local time
+ *                              rather than UTC, FALSE if seconds is expressed
+ *                              in UTC.  Optional, defaults to FALSE.
+ * @return                      A transform that creates a Date_rec containing
+ *                              the date.
  */
 
-EXPORT Date_rec CreateDateFromSeconds(Seconds_t seconds) := TRANSFORM
-    timeParts := SecondsToParts(seconds);
+EXPORT Date_rec CreateDateFromSeconds(Seconds_t seconds, BOOLEAN is_local_time = FALSE) := TRANSFORM
+    timeParts := SecondsToParts(seconds, is_local_time);
 
     SELF.year := timeParts.year;
     SELF.month := timeParts.month;
@@ -1294,12 +1302,16 @@ END;
 /**
  * A transform to create a Time_rec from a Seconds_t value.
  *
- * @param seconds       The number seconds since epoch.
- * @return              A transform that creates a Time_rec containing the time of day.
+ * @param seconds               The number seconds since epoch.
+ * @param is_local_time         TRUE if seconds is expressed in local time
+ *                              rather than UTC, FALSE if seconds is expressed
+ *                              in UTC.  Optional, defaults to FALSE.
+ * @return                      A transform that creates a Time_rec containing
+ *                              the time of day.
  */
 
-EXPORT Time_rec CreateTimeFromSeconds(Seconds_t seconds) := TRANSFORM
-    timeParts := SecondsToParts(seconds);
+EXPORT Time_rec CreateTimeFromSeconds(Seconds_t seconds, BOOLEAN is_local_time = FALSE) := TRANSFORM
+    timeParts := SecondsToParts(seconds, is_local_time);
 
     SELF.hour := timeParts.hour;
     SELF.minute := timeParts.minute;
@@ -1338,13 +1350,16 @@ END;
 /**
  * A transform to create a DateTime_rec from a Seconds_t value.
  *
- * @param seconds       The number seconds since epoch.
- * @return              A transform that creates a DateTime_rec containing
- *                      date and time components.
+ * @param seconds               The number seconds since epoch.
+ * @param is_local_time         TRUE if seconds is expressed in local time
+ *                              rather than UTC, FALSE if seconds is expressed
+ *                              in UTC.  Optional, defaults to FALSE.
+ * @return                      A transform that creates a DateTime_rec
+ *                              containing date and time components.
  */
 
-EXPORT DateTime_rec CreateDateTimeFromSeconds(Seconds_t seconds) := TRANSFORM
-    timeParts := SecondsToParts(seconds);
+EXPORT DateTime_rec CreateDateTimeFromSeconds(Seconds_t seconds, BOOLEAN is_local_time = FALSE) := TRANSFORM
+    timeParts := SecondsToParts(seconds, is_local_time);
 
     SELF.year := timeParts.year;
     SELF.month := timeParts.month;

+ 14 - 3
ecllibrary/teststd/Date/TestDate.ecl

@@ -7,9 +7,12 @@ 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
+  // Note:  Cannot perform static local time zone checks because success depends
+  // on the time zone of the cluster
+
+  SHARED vCreateDateTimeFromSeconds := ROW(Date.CreateDateTimeFromSeconds(917872496)); // Feb 1, 1999 @ 12:34:56 (UTC)
+  SHARED vCreateDateFromSeconds := ROW(Date.CreateDateFromSeconds(917872496)); // Feb 1, 1999 (UTC)
+  SHARED vCreateTimeFromSeconds := ROW(Date.CreateTimeFromSeconds(917872496)); // 12:34:56 (UTC)
   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
@@ -118,6 +121,14 @@ EXPORT TestDate := MODULE
     ASSERT(Date.SecondsFromParts(1999,2,1,12,34,56,FALSE) = 917872496);     // UTC
     ASSERT(Date.SecondsFromParts(1965,2,17,0,0,0,FALSE) = -153705600);      // UTC
 
+    // UTC vs. local round-trip testing
+    ASSERT(ROW(Std.Date.CreateDateTimeFromSeconds(Std.Date.SecondsFromParts(1999,2,1,12,34,56,FALSE),FALSE)).year = ROW(Std.Date.CreateDateTimeFromSeconds(Std.Date.SecondsFromParts(1999,2,1,12,34,56,TRUE),TRUE)).year);
+    ASSERT(ROW(Std.Date.CreateDateTimeFromSeconds(Std.Date.SecondsFromParts(1999,2,1,12,34,56,FALSE),FALSE)).month = ROW(Std.Date.CreateDateTimeFromSeconds(Std.Date.SecondsFromParts(1999,2,1,12,34,56,TRUE),TRUE)).month);
+    ASSERT(ROW(Std.Date.CreateDateTimeFromSeconds(Std.Date.SecondsFromParts(1999,2,1,12,34,56,FALSE),FALSE)).day = ROW(Std.Date.CreateDateTimeFromSeconds(Std.Date.SecondsFromParts(1999,2,1,12,34,56,TRUE),TRUE)).day);
+    ASSERT(ROW(Std.Date.CreateDateTimeFromSeconds(Std.Date.SecondsFromParts(1999,2,1,12,34,56,FALSE),FALSE)).hour = ROW(Std.Date.CreateDateTimeFromSeconds(Std.Date.SecondsFromParts(1999,2,1,12,34,56,TRUE),TRUE)).hour);
+    ASSERT(ROW(Std.Date.CreateDateTimeFromSeconds(Std.Date.SecondsFromParts(1999,2,1,12,34,56,FALSE),FALSE)).minute = ROW(Std.Date.CreateDateTimeFromSeconds(Std.Date.SecondsFromParts(1999,2,1,12,34,56,TRUE),TRUE)).minute);
+    ASSERT(ROW(Std.Date.CreateDateTimeFromSeconds(Std.Date.SecondsFromParts(1999,2,1,12,34,56,FALSE),FALSE)).second = ROW(Std.Date.CreateDateTimeFromSeconds(Std.Date.SecondsFromParts(1999,2,1,12,34,56,TRUE),TRUE)).second);
+
     ASSERT(Date.SecondsToParts(917872496).year = 1999);
     ASSERT(Date.SecondsToParts(917872496).month = 2);
     ASSERT(Date.SecondsToParts(917872496).day = 1);

+ 27 - 8
plugins/timelib/timelib.cpp

@@ -49,8 +49,8 @@ static const char * EclDefinition =
 "  UNSIGNED4 endDate; \n"
 "END;"
 "EXPORT TimeLib := SERVICE : fold\n"
-"  INTEGER8 SecondsFromParts(integer2 year, unsigned1 month, unsigned1 day, unsigned1 hour, unsigned1 minute, unsigned1 second, boolean is_local_time) : c,pure,entrypoint='tlSecondsFromParts'; \n"
-"  TRANSFORM(TMPartsRec) SecondsToParts(INTEGER8 seconds) : c,pure,entrypoint='tlSecondsToParts'; \n"
+"  INTEGER8 SecondsFromParts(INTEGER2 year, UNSIGNED1 month, UNSIGNED1 day, UNSIGNED1 hour, UNSIGNED1 minute, UNSIGNED1 second, BOOLEAN is_local_time) : c,pure,entrypoint='tlSecondsFromParts'; \n"
+"  TRANSFORM(TMPartsRec) SecondsToParts(INTEGER8 seconds, BOOLEAN is_local_time) : 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, CONST VARSTRING format) : c,pure,entrypoint='tlDateToString'; \n"
@@ -215,7 +215,7 @@ static __int64 tlLocalTimeZoneDiffIn100nsIntervals()
     GetLocalTime(&systemLocal);
 
     SystemTimeToFileTime(&systemUTC, &fileUTC);
-    SystemTimeToFileTime(&systemLocal, &fileLocal);
+    SystemTimeToFileTime(&systemLocal, &fileLocal); // DST is accounted for
 
     return tlFileTimeToInt64(fileLocal) - tlFileTimeToInt64(fileUTC);
 }
@@ -377,7 +377,7 @@ time_t tlMKTime(struct tm* timeInfoPtr, bool inLocalTimeZone)
 
     if (!inLocalTimeZone)
     {
-        // Adjust for time zone offset
+        // Adjust for time zone and DST offset
         the_time += (tlLocalTimeZoneDiffIn100nsIntervals() / _onesec_in100ns);
     }
     #else
@@ -396,9 +396,16 @@ time_t tlMKTime(struct tm* timeInfoPtr, bool inLocalTimeZone)
 
 //------------------------------------------------------------------------------
 
-void tlMakeTimeStructFromUTCSeconds(time_t seconds, struct tm* timeInfo)
+void tlMakeTimeStructFromSeconds(time_t seconds, struct tm* timeInfo, bool inLocalTimeZone)
 {
-    tlGMTime_r(&seconds, timeInfo);
+    if (inLocalTimeZone)
+    {
+        tlLocalTime_r(&seconds, timeInfo);
+    }
+    else
+    {
+        tlGMTime_r(&seconds, timeInfo);
+    }
 }
 
 void tlInsertDateIntoTimeStruct(struct tm* timeInfo, unsigned int date)
@@ -461,6 +468,7 @@ TIMELIB_API __int64 TIMELIB_CALL tlSecondsFromParts(int year, unsigned int month
     timeInfo.tm_mday = day;
     timeInfo.tm_mon = month - 1;
     timeInfo.tm_year = year - 1900;
+    timeInfo.tm_isdst = -1;
 
     the_time = tlMKTime(&timeInfo, is_local_time);
 
@@ -469,7 +477,7 @@ TIMELIB_API __int64 TIMELIB_CALL tlSecondsFromParts(int year, unsigned int month
 
 //------------------------------------------------------------------------------
 
-TIMELIB_API size32_t TIMELIB_CALL tlSecondsToParts(ARowBuilder& __self, __int64 seconds)
+TIMELIB_API size32_t TIMELIB_CALL tlSecondsToParts(ARowBuilder& __self, __int64 seconds, bool is_local_time)
 {
     struct tm       timeInfo;
 
@@ -484,7 +492,7 @@ TIMELIB_API size32_t TIMELIB_CALL tlSecondsToParts(ARowBuilder& __self, __int64
         __int32 wday;
     };
 
-    tlMakeTimeStructFromUTCSeconds(seconds, &timeInfo);
+    tlMakeTimeStructFromSeconds(seconds, &timeInfo, is_local_time);
 
     TMParts* result = reinterpret_cast<TMParts*>(__self.getSelf());
 
@@ -524,6 +532,7 @@ TIMELIB_API unsigned int TIMELIB_CALL tlGetDayOfYear(short year, unsigned short
     timeInfo.tm_mday = day;
     timeInfo.tm_mon = month - 1;
     timeInfo.tm_year = year - 1900;
+    timeInfo.tm_isdst = -1;
 
     tlMKTime(&timeInfo);
 
@@ -545,6 +554,7 @@ TIMELIB_API unsigned int TIMELIB_CALL tlGetDayOfWeek(short year, unsigned short
     timeInfo.tm_mday = day;
     timeInfo.tm_mon = month - 1;
     timeInfo.tm_year = year - 1900;
+    timeInfo.tm_isdst = -1;
 
     tlMKTime(&timeInfo);
 
@@ -569,6 +579,7 @@ TIMELIB_API void TIMELIB_CALL tlDateToString(size32_t &__lenResult, char* &__res
 
         memset(&timeInfo, 0, sizeof(timeInfo));
         tlInsertDateIntoTimeStruct(&timeInfo, date);
+        timeInfo.tm_isdst = -1;
         tlMKTime(&timeInfo);
 
 #if defined(__clang__) || defined(__GNUC__)
@@ -597,6 +608,7 @@ TIMELIB_API void TIMELIB_CALL tlTimeToString(size32_t &__lenResult, char* &__res
 
     memset(&timeInfo, 0, sizeof(timeInfo));
     tlInsertTimeIntoTimeStruct(&timeInfo, time);
+    timeInfo.tm_isdst = -1;
     tlMKTime(&timeInfo);
 
 #if defined(__clang__) || defined(__GNUC__)
@@ -660,6 +672,7 @@ TIMELIB_API unsigned int TIMELIB_CALL tlAdjustDate(unsigned int date, short year
     timeInfo.tm_year += year_delta;
     timeInfo.tm_mon += month_delta;
     timeInfo.tm_mday += day_delta;
+    timeInfo.tm_isdst = -1;
 
     tlMKTime(&timeInfo);
 
@@ -678,6 +691,7 @@ TIMELIB_API unsigned int TIMELIB_CALL tlAdjustDateBySeconds(unsigned int date, i
     memset(&timeInfo, 0, sizeof(timeInfo));
 
     tlInsertDateIntoTimeStruct(&timeInfo, date);
+    timeInfo.tm_isdst = -1;
     timeInfo.tm_sec = seconds_delta;
 
     tlMKTime(&timeInfo);
@@ -700,6 +714,7 @@ TIMELIB_API unsigned int TIMELIB_CALL tlAdjustTime(unsigned int time, short hour
 #endif
 
     tlInsertTimeIntoTimeStruct(&timeInfo, time);
+    timeInfo.tm_isdst = -1;
 
     timeInfo.tm_hour += hour_delta;
     timeInfo.tm_min += minute_delta;
@@ -725,6 +740,7 @@ TIMELIB_API unsigned int TIMELIB_CALL tlAdjustTimeBySeconds(unsigned int time, i
 #endif
 
     tlInsertTimeIntoTimeStruct(&timeInfo, time);
+    timeInfo.tm_isdst = -1;
     timeInfo.tm_sec += seconds_delta;
 
     tlMKTime(&timeInfo);
@@ -786,6 +802,7 @@ TIMELIB_API unsigned int TIMELIB_CALL tlAdjustCalendar(unsigned int date, short
 
     timeInfo.tm_year += year_delta;
     timeInfo.tm_mon += month_delta;
+    timeInfo.tm_isdst = -1;
 
     seconds = tlMKTime(&timeInfo);
 
@@ -941,6 +958,7 @@ TIMELIB_API unsigned int TIMELIB_CALL tlGetLastDayOfMonth(unsigned int date)
 
     memset(&timeInfo, 0, sizeof(timeInfo));
     tlInsertDateIntoTimeStruct(&timeInfo, date);
+    timeInfo.tm_isdst = -1;
 
     // Call mktime once to fix up any bogus data
     tlMKTime(&timeInfo);
@@ -971,6 +989,7 @@ TIMELIB_API size32_t TIMELIB_CALL tlDatesForWeek(ARowBuilder& __self, unsigned i
 
     memset(&timeInfo, 0, sizeof(timeInfo));
     tlInsertDateIntoTimeStruct(&timeInfo, date);
+    timeInfo.tm_isdst = -1;
 
     // Call mktime once to fix up any bogus data
     tlMKTime(&timeInfo);

+ 2 - 2
plugins/timelib/timelib.hpp

@@ -44,7 +44,7 @@ TIMELIB_API bool getECLPluginDefinition(ECLPluginDefinitionBlock *pb);
 TIMELIB_API void setPluginContext(IPluginContext * _ctx);
 #endif
 
-void tlMakeTimeStructFromUTCSeconds(time_t seconds, struct tm* timeInfo);
+void tlMakeTimeStructFromSeconds(time_t seconds, struct tm* timeInfo, bool inLocalTimeZone);
 void tlInsertDateIntoTimeStruct(struct tm* timeInfo, unsigned int date);
 unsigned int tlExtractDateFromTimeStruct(const struct tm* timeInfo);
 void tlInsertTimeIntoTimeStruct(struct tm* timeInfo, unsigned int time);
@@ -55,7 +55,7 @@ void tlGMTime_r(const time_t* clock, struct tm* timeInfoPtr);
 time_t tlMKTime(struct tm* timeInfoPtr, bool inLocalTimeZone = true);
 
 TIMELIB_API __int64 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 size32_t TIMELIB_CALL tlSecondsToParts(ARowBuilder & __self, __int64 seconds);
+TIMELIB_API size32_t TIMELIB_CALL tlSecondsToParts(ARowBuilder & __self, __int64 seconds, bool is_local_time);
 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);