Description
We have built a file synchronization service where we rely on the timestamps of files that we save in a DB.
We use SFTPClient.LastWriteTimeUtc to persist the date and time.
After Sweden changed to summertime, we experienced that files where considered as changed because they had a new time when calling SFTPFile.LastWriteTimeUtc. Files that had persisted 2012-12-01T11:51Z
now got 2012-12-01T12:51Z
.
It might be a problem with the server, CoreFTP, but I found a potential issue in SSH.NET code as well.
A look in https://github.com/sshnet/SSH.NET/blob/develop/src/Renci.SshNet/Sftp/SftpFileAttributes.cs:
if ((flag & 0x00000008) == 0x00000008) // SSH_FILEXFER_ATTR_ACMODTIME
{
var time = stream.ReadUInt32();
accessTime = DateTime.FromFileTime((time + 11644473600) * 10000000);
time = stream.ReadUInt32();
modifyTime = DateTime.FromFileTime((time + 11644473600) * 10000000);
}
According to the RFC, the times here are unix timestamp, in UTC. These are decoded directly into DateTimes with DateTimeKind.Local in the code above. The accessTime
and modifyTime
should be in local time, as I understand code elsewhere
According to this article this is how that should be done properly:
public static DateTime UnixTimeStampToDateTime( double unixTimeStamp )
{
// Unix timestamp is seconds past epoch
System.DateTime dtDateTime = new DateTime(1970,1,1,0,0,0,0,System.DateTimeKind.Utc);
dtDateTime = dtDateTime.AddSeconds( unixTimeStamp ).ToLocalTime();
return dtDateTime;
}
Note that it creates a DateTime with DateTimeKind.Utc first and then converts that to local time.
This difference might be the cause of my problems.
The article also mentions DateTimeOffset.FromUnixTimeMilliSeconds
(source) which can be used together with DateTimeOffset.UtcdateTime
to parse these dates.
I also had a look in the ScpClient.NET.cs where I found this code:
var mtime = long.Parse(match.Result("${mtime}"));
var atime = long.Parse(match.Result("${atime}"));
var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
modifiedTime = zeroTime.AddSeconds(mtime);
accessedTime = zeroTime.AddSeconds(atime);
In this case, the times are represented as UTC and this should be correct, I think.