Bladeren bron

Merge pull request #60 from bmx-ng/task/datetime-conversion-fixes

Fixed some time conversions. Resolves #58.
Brucey 2 jaren geleden
bovenliggende
commit
0f10857ab3
3 gewijzigde bestanden met toevoegingen van 219 en 12 verwijderingen
  1. 33 2
      stdc.mod/stdc.bmx
  2. 82 10
      stdc.mod/stdc.c
  3. 104 0
      stdc.mod/tests/test.bmx

+ 33 - 2
stdc.mod/stdc.bmx

@@ -501,14 +501,30 @@ Struct SDateTime
 	End Rem
 	Field offset:Int
 	Rem
-	bbdoc: #True if the date time is observing daylight savings time, #False otherwise.
+	bbdoc: 1 if the date time is observing daylight savings time, 0 if not and -1 if it is not known.
     about: Daylight Saving Time (DST) is the practice of setting the clock ahead by one hour from standard time
 	during the warmer months, and then back again in the fall, in order to extend evening daylight and reduce the
 	need for artificial lighting. This can affect local time calculations, and so it's important to track whether a
 	given datetime object is observing DST. Note that not all regions observe DST, and the start and end dates
 	for DST can vary from one region to another.
 	End Rem
-	Field dst:Int
+	Field dst:Int = -1
+
+	Rem
+	bbdoc: Creates a new #SDateTime instance from the given date and time information.
+	End Rem
+	Method New(year:Int, month:Int, day:Int, hour:Int, minute:Int, second:Int, millisecond:Int = 0, utc:Int = True, offset:Int = 0, dst:Int = -1)
+		Self.year = year
+		Self.month = month
+		Self.day = day
+		Self.hour = hour
+		Self.minute = minute
+		Self.second = second
+		Self.millisecond = millisecond
+		Self.utc = utc
+		Self.offset = offset
+		Self.dst = dst
+	End Method
 
 	Rem
 	bbdoc: Returns a string representation of the date time in ISO 8601 format.
@@ -554,6 +570,20 @@ Struct SDateTime
 	Method ToEpochSecs:Long()
 		Return bmx_datetime_to_epoch(Self)
 	End Method
+
+	Rem
+	bbdoc: Converts the current date time to the equivalent in UTC.
+	returns: The equivalent date time in UTC, or the original date time if the conversion failed.
+	End Rem
+	Method ToUtc:SDateTime()
+		Local dt:SDateTime
+		Local res:Int = bmx_datetime_convert_to_utc(Self, dt)
+		If res = 0 Then
+			Return dt
+		Else
+			Return Self
+		End If
+	End Method
 End Struct
 
 Rem
@@ -609,6 +639,7 @@ Extern "c"
 	Function bmx_datetime_from_epoch:SDateTime(epochSecs:Long, fracNanoSecs:Long)
 	Function bmx_current_datetime_format:String(format:String)
 	Function bmx_datetime_to_epoch:Long(dt:SDateTime Var)
+	Function bmx_datetime_convert_to_utc:Int(dt:SDateTime Var, dtUtc:SDateTime Var)
 End Extern
 
 Startup

+ 82 - 10
stdc.mod/stdc.c

@@ -1030,9 +1030,8 @@ SDateTime bmx_datetime_from_epoch(BBLONG epochTimeSecs, BBLONG fracNanoseconds)
     return dt;
 }
 
-BBLONG bmx_datetime_to_epoch(SDateTime * dt) {
+time_t bmx_datetime_to_time_t(SDateTime * dt) {
     struct tm t;
-    time_t ts;
 
     t.tm_year = dt->year - 1900;
     t.tm_mon = dt->month - 1;
@@ -1040,17 +1039,89 @@ BBLONG bmx_datetime_to_epoch(SDateTime * dt) {
     t.tm_hour = dt->hour;
     t.tm_min = dt->minute;
     t.tm_sec = dt->second;
-    t.tm_isdst = dt->dst;
+    t.tm_isdst = -1; // timegm and _mkgmtime do not use this field
+
+    if (!dt->utc) {
+        // Convert the offset to seconds
+        int offsetSeconds = dt->offset * 60;
+        if (dt->dst == 1) {
+            offsetSeconds += 3600;
+        }
+
+        // Convert struct tm to time_t as if it was UTC
+        time_t ts;
+    #if defined(_WIN32) || defined(_WIN64)
+        ts = _mkgmtime(&t);
+    #else
+        ts = timegm(&t);
+    #endif
+        if (ts == -1) {
+            return -1;
+        }
+
+        // Apply the offset
+        ts -= offsetSeconds;
+
+        return (BBLONG)ts;
+    } else {
+        // Convert struct tm to time_t as UTC
+        time_t ts;
+    #if defined(_WIN32) || defined(_WIN64)
+        ts = _mkgmtime(&t);
+    #else
+        ts = timegm(&t);
+    #endif
+
+        if (ts == -1) {
+            return -1;
+        }
+
+        return ts;
+    }
+}
+
+BBLONG bmx_datetime_to_epoch(SDateTime * dt) {
+	return (BBLONG)bmx_datetime_to_time_t(dt);
+}
 
-    // Convert struct tm to time_t (seconds since the Epoch)
-    ts = mktime(&t);
-    if (ts == -1) {
+int bmx_datetime_convert_to_utc(const SDateTime* dt, SDateTime* dt_utc) {
+   if (!dt || !dt_utc)
+        return -1; // Return error if either pointer is NULL
+
+	if (dt->utc == 1) {
+		*dt_utc = *dt;
+        return 0;
+    }
+
+	time_t ts = bmx_datetime_to_time_t(dt);
+
+	if (ts == -1) {
         return -1;
     }
-    
-    return (BBLONG)ts;
+
+    struct tm utc;
+
+#if defined(_WIN32) || defined(_WIN64)
+    gmtime_s(&timeinfo, &epochTimeSecs);
+#else
+    gmtime_r(&ts, &utc);
+#endif
+
+    dt_utc->year = utc.tm_year + 1900;
+    dt_utc->month = utc.tm_mon + 1;
+    dt_utc->day = utc.tm_mday;
+    dt_utc->hour = utc.tm_hour;
+    dt_utc->minute = utc.tm_min;
+    dt_utc->second = utc.tm_sec;
+    dt_utc->millisecond = dt->millisecond;
+    dt_utc->utc = 1;
+    dt_utc->offset = 0;
+    dt_utc->dst = 0;
+
+    return 0;
 }
 
+
 BBString * bmx_current_datetime_format(BBString * format) {
 	struct tm tm;
 	time_t rawtime;
@@ -1086,9 +1157,10 @@ BBString * bmx_datetime_iso8601(const SDateTime *dt, int showMillis) {
                      dt->year, dt->month, dt->day, dt->hour, dt->minute, dt->second);
         }
     } else {
+		int offset = dt->dst == 1 ? dt->offset + 60 : dt->offset;
         int offset_sign = dt->offset >= 0 ? 1 : -1;
-        int offset_hours = dt->offset / 60 * offset_sign;
-        int offset_minutes = dt->offset % 60 * offset_sign;
+        int offset_hours = offset / 60;
+        int offset_minutes = offset % 60 * offset_sign;
 
         if (showMillis) {
             snprintf(buf, 32, "%04d-%02d-%02dT%02d:%02d:%02d.%03d%+03d:%02d",

+ 104 - 0
stdc.mod/tests/test.bmx

@@ -0,0 +1,104 @@
+SuperStrict
+
+Framework brl.standardio
+Import BRL.MaxUnit
+Import Pub.Stdc
+
+New TTestSuite.run()
+
+Type TSDateTimeTest Extends TTest
+
+	Method testConversions() { test }
+
+		Local epoch:Long = 0
+		Local expected:SDateTime = New SDateTime( 1970, 1, 1, 0, 0, 0, 0, True, 0, 0 )
+
+		Local dt:SDateTIme = SDateTime.FromEpoch(epoch)
+
+		AssertSDateTimeEquals( expected, dt )
+		AssertEquals(epoch, dt.ToEpochSecs())
+		AssertEquals("1970-01-01T00:00:00Z", dt.ToString())
+
+		epoch = 86400
+		expected = New SDateTime( 1970, 1, 2, 0, 0, 0, 0, True, 0, 0 )
+
+		dt = SDateTime.FromEpoch(epoch)
+
+		AssertSDateTimeEquals( expected, dt )
+		AssertEquals(epoch, dt.ToEpochSecs())
+		AssertEquals("1970-01-02T00:00:00Z", dt.ToString())
+
+		epoch = 31536000
+		expected = New SDateTime( 1971, 1, 1, 0, 0, 0, 0, True, 0, 0 )
+
+		dt = SDateTime.FromEpoch(epoch)
+
+		AssertSDateTimeEquals( expected, dt )
+		AssertEquals(epoch, dt.ToEpochSecs())
+		AssertEquals("1971-01-01T00:00:00Z", dt.ToString())
+
+		epoch = 1603580400
+		expected = New SDateTime( 2020, 10, 24, 23, 0, 0, 0, True, 0, 0 )
+
+		dt = SDateTime.FromEpoch(epoch)
+
+		AssertSDateTimeEquals( expected, dt )
+		AssertEquals(epoch, dt.ToEpochSecs())
+		AssertEquals("2020-10-24T23:00:00Z", dt.ToString())
+
+		epoch = 1603580400
+		expected = New SDateTime( 2020, 10, 25, 0, 0, 0, 0, False, 0, 1 )
+
+		dt = SDateTime.FromEpoch(epoch)
+
+		AssertSDateTimeEquals( expected.ToUtc(), dt )
+		AssertEquals(epoch, dt.ToEpochSecs())
+		AssertEquals("2020-10-24T23:00:00Z", dt.ToString())
+		AssertEquals("2020-10-25T00:00:00+01:00", expected.ToString() )
+
+		epoch = 1583650800
+		expected = New SDateTime( 2020, 3, 8, 2, 0, 0, 0, False, -300, 0 )
+
+		dt = SDateTime.FromEpoch(epoch)
+
+		AssertSDateTimeEquals( expected.ToUtc(), dt )
+		AssertEquals(epoch, dt.ToEpochSecs())
+		AssertEquals("2020-03-08T07:00:00Z", dt.ToString())
+		AssertEquals("2020-03-08T02:00:00-05:00", expected.ToString())
+
+		epoch = 1583650800
+		expected = New SDateTime( 2020, 3, 8, 2, 0, 0, 0, False, -300, 0 )
+
+		dt = SDateTime.FromEpoch(epoch)
+
+		AssertSDateTimeEquals( expected.ToUtc(), dt )
+		AssertEquals(epoch, dt.ToEpochSecs())
+		AssertEquals("2020-03-08T07:00:00Z", dt.ToString())
+		AssertEquals("2020-03-08T02:00:00-05:00", expected.ToString())
+
+		expected = New SDateTime( 2020, 3, 8, 3, 0, 0, 0, False, -300, 1 )
+
+		dt = SDateTime.FromEpoch(epoch)
+
+		AssertSDateTimeEquals( expected.ToUtc(), dt )
+		AssertEquals(epoch, dt.ToEpochSecs())
+		AssertEquals("2020-03-08T07:00:00Z", dt.ToString())
+		AssertEquals("2020-03-08T03:00:00-04:00", expected.ToString())
+
+	End Method
+
+	Method AssertSDateTimeEquals( expected:SDateTime, actual:SDateTime )
+
+		AssertEquals( expected.year, actual.year )
+		AssertEquals( expected.month, actual.month )
+		AssertEquals( expected.day, actual.day )
+		AssertEquals( expected.hour, actual.hour )
+		AssertEquals( expected.minute, actual.minute )
+		AssertEquals( expected.second, actual.second )
+		AssertEquals( expected.milliSecond, actual.milliSecond )
+		AssertEquals( expected.utc, actual.utc )
+		AssertEquals( expected.offset, actual.offset )
+		AssertEquals( expected.dst, actual.dst )
+
+	End Method
+End Type