1
votes

I'm in the process of porting a C++ program to C#. The program needs to be able to read a file's "modified" timestamp and store it in a List. This is what I have so far:

C# code:

ret = new List<Byte>(); //list to store the timestamp

var file = new FileInfo(filename);

//Get revision date/time
DateTime revTime_lastWriteTime_LT = file.LastWriteTime;

//Copy date/time into the List (binary buffer)
ret.Add(Convert.ToByte(revTime_lastWriteTime_LT.Month));
ret.Add(Convert.ToByte(revTime_lastWriteTime_LT.Day));
ret.Add(Convert.ToByte(revTime_lastWriteTime_LT.Year % 100)); // 2-digit year
ret.Add(Convert.ToByte(revTime_lastWriteTime_LT.Hour));
ret.Add(Convert.ToByte(revTime_lastWriteTime_LT.Minute));
ret.Add(Convert.ToByte(revTime_lastWriteTime_LT.Second));

The problem occurs when I read in the Hours value. If the file was modified during daylight savings time (like a summer month), the hour value in the C++ program gets subtracted by one. I can't seem to replicate this in my program. In the MSDN article for DateTime it says under "DateTime values": "local time is optionally affected by daylight saving time, which adds or subtracts an hour from the length of a day". In my C# code I made sure to change my DateTime object to local time using, ToLocalTime(), but apparently I haven't instituted the option that the article is talking about. How do I make sure that my DateTime object in local time subtracts a value when reading in a file that was modified during daylight savings time?

C++ code just in case:

static WIN32_FILE_ATTRIBUTE_DATA get_file_data(const std::string & filename)
{
  WIN32_FILE_ATTRIBUTE_DATA ret;
  if (!GetFileAttributesEx(filename.c_str(), GetFileExInfoStandard, &ret))
    RaiseLastWin32Error();
  return ret;
}
//---------------------------------------------------------------------------
static SYSTEMTIME get_file_time(const std::string & filename)
{
  const WIN32_FILE_ATTRIBUTE_DATA data(get_file_data(filename));
  FILETIME local;
  if (!FileTimeToLocalFileTime(&data.ftLastWriteTime, &local))
    RaiseLastWin32Error();
  SYSTEMTIME ret;
  if (!FileTimeToSystemTime(&local, &ret))
    RaiseLastWin32Error();
  return ret;
}

void parse_asm()
{
    // Get revision date/time and size
    const SYSTEMTIME rev = get_file_time(filename);
    // Copy date/time into the binary buffer
    ret.push_back(rev.wMonth);
    ret.push_back(rev.wDay);
    ret.push_back(rev.wYear % 100); // 2-digit year
    ret.push_back(rev.wHour);
    ret.push_back(rev.wMinute);
    ret.push_back(rev.wSecond);
}

Update for clarity:

In Windows time settings I am in (UTC-05:00) Eastern Time (US & Canada). The file was last modified on Tues Sept 03, 2013 at 12:13:52 PM. The C++ app shows the hour value as 11 and the C# app shows the hour value as 12 using the code above. I need the C# app to show the same hour value as the C++ app.

2
Ignore the binary buffer bit - what happens if you just print out revTime_lastWriteTime_LT? How does that compare with what's displayed in the file system? Note that on my box, LastWriteTime already returns a DateTime with a kind of Local, so calling ToLocalTime shouldn't make any difference. Is it possible that the problem is actually with the C++ code (which you haven't shown)? - Jon Skeet
DateTime has IsDaylightSavingTime() method - Nadia Chibrikova
Converting historical timestamps to local time is fraught with trouble, you need a reliable database that knows when DST was in effect in previous years. You can typically count on .NET getting that right,. C++, no, not very likely. If you can't change it to use UTC instead, like it should, then keeping that C++ code happy is your cross to bear, use TimeZoneInfo. - Hans Passant
@JonSkeet I didn't think that calling ToLocalTime made a difference, but I was just trying it after reading the MSDN article. I am sure that the problem isn't with the C++ code. I'm basically in a situation where the C++ file is always right, haha. I tried to print out revTime_lastWriteTime_LT.Hour.ToString() and it didn't subtract an hour. - Eric after dark
@NadiaChibrikova Isn't that just a boolean property? - Eric after dark

2 Answers

2
votes

The bug is actually not with .NET, but with your C++ code. You're using FileTimeToLocalFileTime, which has a well known bug, as described in KB932955:

Note The FileTimeToLocalFileTime() function and the LocalFileTimeToFileTime() function perform the conversion between UTC time and local time by using only the current time zone information and the DST information. This conversion occurs regardless of the timestamp that is being converted.

So in the example you gave, Sept 03, 2013 at 12:13:52 PM in the US Eastern Time zone should indeed be in daylight saving time. But because it is right now (February 2015) not daylight saving time, you currently get 11 for the hour in your C++ program. If you run the exact same C++ code after next month's transition (March 8th 2015), you will then get 12 for the hour.

The fix for the C++ code is described in the remarks section of the MSDN entry for the FileTimeToLocalFileTime function:

To account for daylight saving time when converting a file time to a local time, use the following sequence of functions in place of using FileTimeToLocalFileTime:

  1. FileTimeToSystemTime
  2. SystemTimeToTzSpecificLocalTime
  3. SystemTimeToFileTime

Now that you understand the bug - if you actually wanted to keep that behavior in C# (which I do not recommend), then you would do the following:

TimeSpan currentOffset = TimeZoneInfo.Local.GetUtcOffset(DateTime.UtcNow);
DateTime revTime_LastWriteTime_Lt = file.LastWriteTimeUtc.Add(currentOffset);

The better thing to do would just to leave your current code as is (using file.LastWriteTime), and call the bug fixed.

1
votes

Sorry, you need to use Add not AddHours, Add accepts TimeSpan. So you're looking for:

 file.LastWriteTimeUtc.Add(TimeZoneInfo.Local.BaseUtcOffset);