Browse Source

Fix SFTP file UTC time handling (#356)

* Improved SFTP file UTC time handling
Bill Menees 5 years ago
parent
commit
90a13a6c5a
17 changed files with 144 additions and 49 deletions
  1. 1 1
      src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs
  2. 2 0
      src/Renci.SshNet.Tests/Classes/Common/BigIntegerTest.cs
  3. 1 1
      src/Renci.SshNet.Tests/Classes/Messages/Connection/ChannelExtendedDataMessageTest.cs
  4. 2 1
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTestBase.cs
  5. 2 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessWrite.cs
  6. 3 3
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs
  7. 2 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs
  8. 2 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs
  9. 2 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs
  10. 2 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessReadWrite.cs
  11. 2 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessWrite.cs
  12. 2 2
      src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Write_SessionOpen_CountGreatherThanTwoTimesTheWriteBufferSize.cs
  13. 9 1
      src/Renci.SshNet.Tests/Classes/SftpClientTest.cs
  14. 2 2
      src/Renci.SshNet.Tests/Common/Extensions.cs
  15. 8 2
      src/Renci.SshNet.Tests/Common/SftpFileAttributesBuilder.cs
  16. 4 4
      src/Renci.SshNet/Sftp/SftpFile.cs
  17. 98 20
      src/Renci.SshNet/Sftp/SftpFileAttributes.cs

+ 1 - 1
src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs

@@ -181,4 +181,4 @@ namespace Renci.SshNet.Tests.Classes
             PublicKeyAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Exactly(2));
         }
     }
-}
+}

+ 2 - 0
src/Renci.SshNet.Tests/Classes/Common/BigIntegerTest.cs

@@ -1506,10 +1506,12 @@ namespace Renci.SshNet.Tests.Classes.Common
             Assert.AreEqual("0", a.ToString(), "#4");
 
             a = new BigInteger();
+#pragma warning disable CS1718 // Comparison made to same variable
             Assert.AreEqual(true, a == a, "#5");
 
             a = new BigInteger();
             Assert.AreEqual(false, a < a, "#6");
+#pragma warning restore CS1718 // Comparison made to same variable
 
             a = new BigInteger();
             Assert.AreEqual(true, a < 10L, "#7");

+ 1 - 1
src/Renci.SshNet.Tests/Classes/Messages/Connection/ChannelExtendedDataMessageTest.cs

@@ -30,7 +30,7 @@ namespace Renci.SshNet.Tests.Classes.Messages.Connection
         [Ignore] // placeholder
         public void ChannelExtendedDataMessageConstructorTest1()
         {
-            uint localChannelNumber = 0; // TODO: Initialize to an appropriate value
+            //uint localChannelNumber = 0; // TODO: Initialize to an appropriate value
             //ChannelExtendedDataMessage target = new ChannelExtendedDataMessage(localChannelNumber, null, null);
             Assert.Inconclusive("TODO: Implement code to verify target");
         }

+ 2 - 1
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTestBase.cs

@@ -38,7 +38,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
 
         protected static SftpFileAttributes CreateSftpFileAttributes(long size)
         {
-            return new SftpFileAttributes(default(DateTime), default(DateTime), size, default(int), default(int), default(uint), null);
+            var utcDefault = DateTime.SpecifyKind(default(DateTime), DateTimeKind.Utc);
+            return new SftpFileAttributes(utcDefault, utcDefault, size, default(int), default(int), default(uint), null);
         }
 
         protected static byte[] CreateByteArray(Random random, int length)

+ 2 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessWrite.cs

@@ -34,8 +34,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _readBufferSize = (uint) _random.Next(5, 1000);
             _writeBufferSize = (uint) _random.Next(5, 1000);
             _handle = GenerateRandom(_random.Next(1, 10), _random);
-            _fileAttributes = new SftpFileAttributesBuilder().WithLastAccessTime(DateTime.Now.AddSeconds(_random.Next()))
-                                                             .WithLastWriteTime(DateTime.Now.AddSeconds(_random.Next()))
+            _fileAttributes = new SftpFileAttributesBuilder().WithLastAccessTime(DateTime.UtcNow.AddSeconds(_random.Next()))
+                                                             .WithLastWriteTime(DateTime.UtcNow.AddSeconds(_random.Next()))
                                                              .WithSize(_random.Next())
                                                              .WithUserId(_random.Next())
                                                              .WithGroupId(_random.Next())

+ 3 - 3
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs

@@ -53,8 +53,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC")
                                                              .WithExtension("V", "VValue")
                                                              .WithGroupId(random.Next())
-                                                             .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next()))
-                                                             .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithLastAccessTime(DateTime.UtcNow.AddSeconds(random.Next()))
+                                                             .WithLastWriteTime(DateTime.UtcNow.AddSeconds(random.Next()))
                                                              .WithPermissions((uint) random.Next())
                                                              .WithSize(_length + 100)
                                                              .WithUserId(random.Next())
@@ -201,4 +201,4 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4));
         }
     }
-}
+}

+ 2 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs

@@ -50,8 +50,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC")
                                                              .WithExtension("V", "VValue")
                                                              .WithGroupId(random.Next())
-                                                             .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next()))
-                                                             .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithLastAccessTime(DateTime.UtcNow.AddSeconds(random.Next()))
+                                                             .WithLastWriteTime(DateTime.UtcNow.AddSeconds(random.Next()))
                                                              .WithPermissions((uint)random.Next())
                                                              .WithSize(_length + 100)
                                                              .WithUserId(random.Next())

+ 2 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs

@@ -54,8 +54,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC")
                                                              .WithExtension("V", "VValue")
                                                              .WithGroupId(random.Next())
-                                                             .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next()))
-                                                             .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithLastAccessTime(DateTime.UtcNow.AddSeconds(random.Next()))
+                                                             .WithLastWriteTime(DateTime.UtcNow.AddSeconds(random.Next()))
                                                              .WithPermissions((uint)random.Next())
                                                              .WithSize(_length + 100)
                                                              .WithUserId(random.Next())

+ 2 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs

@@ -54,8 +54,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC")
                                                              .WithExtension("V", "VValue")
                                                              .WithGroupId(random.Next())
-                                                             .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next()))
-                                                             .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next()))
+                                                             .WithLastAccessTime(DateTime.UtcNow.AddSeconds(random.Next()))
+                                                             .WithLastWriteTime(DateTime.UtcNow.AddSeconds(random.Next()))
                                                              .WithPermissions((uint) random.Next())
                                                              .WithSize(_length + 100)
                                                              .WithUserId(random.Next())

+ 2 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessReadWrite.cs

@@ -48,8 +48,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _writeBufferSize = (uint) random.Next(0, 1000);
             _length = random.Next();
 
-            _fileAttributesLastAccessTime = DateTime.Now.AddSeconds(random.Next());
-            _fileAttributesLastWriteTime = DateTime.Now.AddSeconds(random.Next());
+            _fileAttributesLastAccessTime = DateTime.UtcNow.AddSeconds(random.Next());
+            _fileAttributesLastWriteTime = DateTime.UtcNow.AddSeconds(random.Next());
             _fileAttributesSize = random.Next();
             _fileAttributesUserId = random.Next();
             _fileAttributesGroupId = random.Next();

+ 2 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessWrite.cs

@@ -48,8 +48,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
             _writeBufferSize = (uint) random.Next(0, 1000);
             _length = random.Next();
 
-            _fileAttributesLastAccessTime = DateTime.Now.AddSeconds(random.Next());
-            _fileAttributesLastWriteTime = DateTime.Now.AddSeconds(random.Next());
+            _fileAttributesLastAccessTime = DateTime.UtcNow.AddSeconds(random.Next());
+            _fileAttributesLastWriteTime = DateTime.UtcNow.AddSeconds(random.Next());
             _fileAttributesSize = random.Next();
             _fileAttributesUserId = random.Next();
             _fileAttributesGroupId = random.Next();

+ 2 - 2
src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Write_SessionOpen_CountGreatherThanTwoTimesTheWriteBufferSize.cs

@@ -119,8 +119,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp
         [TestMethod]
         public void LengthShouldFlushBufferAndReturnSizeOfFile()
         {
-            var lengthFileAttributes = new SftpFileAttributes(DateTime.Now,
-                                                              DateTime.Now,
+            var lengthFileAttributes = new SftpFileAttributes(DateTime.UtcNow,
+                                                              DateTime.UtcNow,
                                                               _random.Next(),
                                                               _random.Next(),
                                                               _random.Next(),

+ 9 - 1
src/Renci.SshNet.Tests/Classes/SftpClientTest.cs

@@ -1076,7 +1076,9 @@ namespace Renci.SshNet.Tests.Classes
             SftpClient target = new SftpClient(connectionInfo); // TODO: Initialize to an appropriate value
             string path = string.Empty; // TODO: Initialize to an appropriate value
             DateTime lastAccessTime = new DateTime(); // TODO: Initialize to an appropriate value
+#pragma warning disable CS0618 // Type or member is obsolete
             target.SetLastAccessTime(path, lastAccessTime);
+#pragma warning restore CS0618 // Type or member is obsolete
             Assert.Inconclusive("A method that does not return a value cannot be verified.");
         }
 
@@ -1091,7 +1093,9 @@ namespace Renci.SshNet.Tests.Classes
             SftpClient target = new SftpClient(connectionInfo); // TODO: Initialize to an appropriate value
             string path = string.Empty; // TODO: Initialize to an appropriate value
             DateTime lastAccessTimeUtc = new DateTime(); // TODO: Initialize to an appropriate value
+#pragma warning disable CS0618 // Type or member is obsolete
             target.SetLastAccessTimeUtc(path, lastAccessTimeUtc);
+#pragma warning restore CS0618 // Type or member is obsolete
             Assert.Inconclusive("A method that does not return a value cannot be verified.");
         }
 
@@ -1106,7 +1110,9 @@ namespace Renci.SshNet.Tests.Classes
             SftpClient target = new SftpClient(connectionInfo); // TODO: Initialize to an appropriate value
             string path = string.Empty; // TODO: Initialize to an appropriate value
             DateTime lastWriteTime = new DateTime(); // TODO: Initialize to an appropriate value
+#pragma warning disable CS0618 // Type or member is obsolete
             target.SetLastWriteTime(path, lastWriteTime);
+#pragma warning restore CS0618 // Type or member is obsolete
             Assert.Inconclusive("A method that does not return a value cannot be verified.");
         }
 
@@ -1121,7 +1127,9 @@ namespace Renci.SshNet.Tests.Classes
             SftpClient target = new SftpClient(connectionInfo); // TODO: Initialize to an appropriate value
             string path = string.Empty; // TODO: Initialize to an appropriate value
             DateTime lastWriteTimeUtc = new DateTime(); // TODO: Initialize to an appropriate value
+#pragma warning disable CS0618 // Type or member is obsolete
             target.SetLastWriteTimeUtc(path, lastWriteTimeUtc);
+#pragma warning restore CS0618 // Type or member is obsolete
             Assert.Inconclusive("A method that does not return a value cannot be verified.");
         }
 
@@ -1403,4 +1411,4 @@ namespace Renci.SshNet.Tests.Classes
             public SftpDownloadAsyncResult DownloadResult { get; set; }
         }
     }
-}
+}

+ 2 - 2
src/Renci.SshNet.Tests/Common/Extensions.cs

@@ -49,8 +49,8 @@ namespace Renci.SshNet.Tests.Common
                 clonedExtensions = null;
             }
 
-            return new SftpFileAttributes(value.LastAccessTime,
-                                          value.LastWriteTime,
+            return new SftpFileAttributes(value.LastAccessTimeUtc,
+                                          value.LastWriteTimeUtc,
                                           value.Size,
                                           value.UserId,
                                           value.GroupId,

+ 8 - 2
src/Renci.SshNet.Tests/Common/SftpFileAttributesBuilder.cs

@@ -64,9 +64,15 @@ namespace Renci.SshNet.Tests.Common
         public SftpFileAttributes Build()
         {
             if (_lastAccessTime == null)
-                _lastAccessTime = DateTime.MinValue;
+                _lastAccessTime = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
+            else if (_lastAccessTime.Value.Kind != DateTimeKind.Utc)
+                _lastAccessTime = _lastAccessTime.Value.ToUniversalTime();
+
             if (_lastWriteTime == null)
-                _lastWriteTime = DateTime.MinValue;
+                _lastWriteTime = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
+            else if (_lastWriteTime.Value.Kind != DateTimeKind.Utc)
+                _lastWriteTime = _lastWriteTime.Value.ToUniversalTime();
+
             if (_size == null)
                 _size = 0;
             if (_userId == null)

+ 4 - 4
src/Renci.SshNet/Sftp/SftpFile.cs

@@ -99,11 +99,11 @@ namespace Renci.SshNet.Sftp
         {
             get
             {
-                return Attributes.LastAccessTime.ToUniversalTime();
+                return Attributes.LastAccessTimeUtc;
             }
             set
             {
-                Attributes.LastAccessTime = value.ToLocalTime();
+                Attributes.LastAccessTimeUtc = value;
             }
         }
 
@@ -117,11 +117,11 @@ namespace Renci.SshNet.Sftp
         {
             get
             {
-                return Attributes.LastWriteTime.ToUniversalTime();
+                return Attributes.LastWriteTimeUtc;
             }
             set
             {
-                Attributes.LastWriteTime = value.ToLocalTime();
+                Attributes.LastWriteTimeUtc = value;
             }
         }
 

+ 98 - 20
src/Renci.SshNet/Sftp/SftpFileAttributes.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Globalization;
 using Renci.SshNet.Common;
+using System.Diagnostics;
 
 namespace Renci.SshNet.Sftp
 {
@@ -11,7 +12,7 @@ namespace Renci.SshNet.Sftp
     /// </summary>
     public class SftpFileAttributes
     {
-        #region Bitmask constats
+        #region Bitmask constants
 
         private const uint S_IFMT = 0xF000; //  bitmask for the file type bitfields
 
@@ -60,8 +61,8 @@ namespace Renci.SshNet.Sftp
         private bool _isGroupIDBitSet;
         private bool _isStickyBitSet;
 
-        private readonly DateTime _originalLastAccessTime;
-        private readonly DateTime _originalLastWriteTime;
+        private readonly DateTime _originalLastAccessTimeUtc;
+        private readonly DateTime _originalLastWriteTimeUtc;
         private readonly long _originalSize;
         private readonly int _originalUserId;
         private readonly int _originalGroupId;
@@ -70,12 +71,12 @@ namespace Renci.SshNet.Sftp
 
         internal bool IsLastAccessTimeChanged
         {
-            get { return _originalLastAccessTime != LastAccessTime; }
+            get { return _originalLastAccessTimeUtc != LastAccessTimeUtc; }
         }
 
         internal bool IsLastWriteTimeChanged
         {
-            get { return _originalLastWriteTime != LastWriteTime; }
+            get { return _originalLastWriteTimeUtc != LastWriteTimeUtc; }
         }
 
         internal bool IsSizeChanged
@@ -104,20 +105,58 @@ namespace Renci.SshNet.Sftp
         }
 
         /// <summary>
-        /// Gets or sets the time the current file or directory was last accessed.
+        /// Gets or sets the local time the current file or directory was last accessed.
         /// </summary>
         /// <value>
-        /// The time that the current file or directory was last accessed.
+        /// The local time that the current file or directory was last accessed.
         /// </value>
-        public DateTime LastAccessTime { get; set; }
+        public DateTime LastAccessTime
+        {
+            get
+            {
+                return ToLocalTime(this.LastAccessTimeUtc);
+            }
+
+            set
+            {
+                this.LastAccessTimeUtc = ToUniversalTime(value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the local time when the current file or directory was last written to.
+        /// </summary>
+        /// <value>
+        /// The local time the current file was last written.
+        /// </value>
+        public DateTime LastWriteTime
+        {
+            get
+            {
+                return ToLocalTime(this.LastWriteTimeUtc);
+            }
+
+            set
+            {
+                this.LastWriteTimeUtc = ToUniversalTime(value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the UTC time the current file or directory was last accessed.
+        /// </summary>
+        /// <value>
+        /// The UTC time that the current file or directory was last accessed.
+        /// </value>
+        public DateTime LastAccessTimeUtc { get; set; }
 
         /// <summary>
-        /// Gets or sets the time when the current file or directory was last written to.
+        /// Gets or sets the UTC time when the current file or directory was last written to.
         /// </summary>
         /// <value>
-        /// The time the current file was last written.
+        /// The UTC time the current file was last written.
         /// </value>
-        public DateTime LastWriteTime { get; set; }
+        public DateTime LastWriteTimeUtc { get; set; }
 
         /// <summary>
         /// Gets or sets the size, in bytes, of the current file.
@@ -395,10 +434,10 @@ namespace Renci.SshNet.Sftp
         {
         }
 
-        internal SftpFileAttributes(DateTime lastAccessTime, DateTime lastWriteTime, long size, int userId, int groupId, uint permissions, IDictionary<string, string> extensions)
+        internal SftpFileAttributes(DateTime lastAccessTimeUtc, DateTime lastWriteTimeUtc, long size, int userId, int groupId, uint permissions, IDictionary<string, string> extensions)
         {
-            LastAccessTime = _originalLastAccessTime = lastAccessTime;
-            LastWriteTime = _originalLastWriteTime = lastWriteTime;
+            LastAccessTimeUtc = _originalLastAccessTimeUtc = lastAccessTimeUtc;
+            LastWriteTimeUtc = _originalLastWriteTimeUtc = lastWriteTimeUtc;
             Size = _originalSize = size;
             UserId = _originalUserId = userId;
             GroupId = _originalGroupId = groupId;
@@ -491,9 +530,9 @@ namespace Renci.SshNet.Sftp
 
             if (IsLastAccessTimeChanged || IsLastWriteTimeChanged)
             {
-                var time = (uint)(LastAccessTime.ToFileTime() / 10000000 - 11644473600);
+                var time = (uint)(LastAccessTimeUtc.ToFileTimeUtc() / 10000000 - 11644473600);
                 stream.Write(time);
-                time = (uint)(LastWriteTime.ToFileTime() / 10000000 - 11644473600);
+                time = (uint)(LastWriteTimeUtc.ToFileTimeUtc() / 10000000 - 11644473600);
                 stream.Write(time);
             }
 
@@ -521,8 +560,8 @@ namespace Renci.SshNet.Sftp
             var userId = -1;
             var groupId = -1;
             uint permissions = 0;
-            var accessTime = DateTime.MinValue;
-            var modifyTime = DateTime.MinValue;
+            DateTime accessTime;
+            DateTime modifyTime;
             IDictionary<string, string> extensions = null;
 
             if ((flag & 0x00000001) == 0x00000001)   //  SSH_FILEXFER_ATTR_SIZE
@@ -544,10 +583,17 @@ namespace Renci.SshNet.Sftp
 
             if ((flag & 0x00000008) == 0x00000008)   //  SSH_FILEXFER_ATTR_ACMODTIME
             {
+                // The incoming times are "Unix times", so they're already in UTC.  We need to preserve that
+                // to avoid losing information in a local time conversion during the "fall back" hour in DST.
                 var time = stream.ReadUInt32();
-                accessTime = DateTime.FromFileTime((time + 11644473600) * 10000000);
+                accessTime = DateTime.FromFileTimeUtc((time + 11644473600) * 10000000);
                 time = stream.ReadUInt32();
-                modifyTime = DateTime.FromFileTime((time + 11644473600) * 10000000);
+                modifyTime = DateTime.FromFileTimeUtc((time + 11644473600) * 10000000);
+            }
+            else
+            {
+                accessTime = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
+                modifyTime = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
             }
 
             if ((flag & 0x80000000) == 0x80000000)   //  SSH_FILEXFER_ATTR_EXTENDED
@@ -572,5 +618,37 @@ namespace Renci.SshNet.Sftp
                 return FromBytes(stream);
             }
         }
+
+        private static DateTime ToLocalTime(DateTime value)
+        {
+            DateTime result;
+
+            if (value == DateTime.MinValue)
+            {
+                result = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Local);
+            }
+            else
+            {
+                result = value.ToLocalTime();
+            }
+
+            return result;
+        }
+
+        private static DateTime ToUniversalTime(DateTime value)
+        {
+            DateTime result;
+
+            if (value == DateTime.MinValue)
+            {
+                result = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
+            }
+            else
+            {
+                result = value.ToUniversalTime();
+            }
+
+            return result;
+        }
     }
 }