Search code examples
c#c++datetimedate-conversionmktime

Converting c++ tm struct and mktime to C#


I'm converting some c++ to c# code. I cannot reproduce equivalent values when converting from the tm struct and mktime() to c#.

Example: Both versions use the same values:

int year = 2022;
int month = 9;
int day = 5;
int hour = 0;
int minute = 27;
int second = 20;

In C++:

struct tm sampleTime;
unsigned long longTime;
sampleTime.tm_year = year;
sampleTime.tm_mon  = month - 1;
sampleTime.tm_mday = day;
sampleTime.tm_hour = hour;
sampleTime.tm_min  = minute;
sampleTime.tm_sec  = second;
sampleTime.tm_wday = 0;
sampleTime.tm_yday = 0;
sampleTime.tm_isdst= 0;

longTime = mktime(&thistime); 

The value of longTime is 1662366439.

In C#:

DateTime sampleTime = new DateTime(year, month, day, hour, minute, second);
DateTime unixEpoch = new DateTime(1970, 1, 1);
ulong longTime = (ulong)(sampleTime - unixEpoch).TotalSeconds;

The value of longTime is 1662337639.

So they are very close but off, and I need them to be equivalent. What am I missing here?


Solution

  • mktime() takes in a tm expressed in local time, whereas the Unix epoch is expressed in UTC time instead.

    The C# DateTime() constructors you are calling don't know whether the specified date/time values are in local time or UTC time, so the Kind property of the resulting DateTimes is Unspecified, which can cause calculation errors. So, you need to use other DateTime constructors that make each Kind explicit so adjustments can be made as needed during calculations, eg:

    DateTime sampleTime = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Local);
    DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
    

    That being said, note that the C++ code is setting sampleTime.tm_isdst = 0 to indicate that the specified date/time IS NOT in DST time, even if the real date/time actually IS in DST time.

    I'm not sure how to handle that on the C# side. You will probably have to lookup whether the specified sampleTime is actually in DST (ie, via TimeZoneInfo.Local.IsDaylightSavingTime()) and if so then adjust it accordingly to move it out of DST before then performing further calculations on it, eg:

    DateTime sampleTime = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Local);
    DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
    
    TimeZoneInfo tz = TimeZoneInfo.Local;
    if (tz.SupportsDaylightSavingTime && if tz.IsDaylightSavingTime(sampleTime))
    {
        // figure out the difference between Standard/Daylight times
        // for this timezone at the specified date/time and adjust 
        // sampleTime accordingly ...
    }