You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ni...@apache.org on 2020/12/24 12:00:47 UTC

[ignite] branch master updated: IGNITE-12824 .NET: Add BinaryConfiguration.TimestampConverter (#8568)

This is an automated email from the ASF dual-hosted git repository.

nizhikov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 76eda8d  IGNITE-12824 .NET: Add BinaryConfiguration.TimestampConverter (#8568)
76eda8d is described below

commit 76eda8dba406754635599b0cea55bbffb4137efe
Author: Nikolay <ni...@apache.org>
AuthorDate: Thu Dec 24 15:00:16 2020 +0300

    IGNITE-12824 .NET: Add BinaryConfiguration.TimestampConverter (#8568)
    
    Co-authored-by: Pavel Tupitsyn <pt...@apache.org>
---
 .../ignite/platform/PlatformDeployServiceTask.java |  20 ++
 .../Binary/BinaryDateTimeTest.cs                   | 213 ++++++++++++++++++++-
 .../Services/IJavaService.cs                       |   3 +
 .../Services/JavaServiceDynamicProxy.cs            |   6 +
 .../Services/ServicesTest.cs                       |  49 +++++
 .../Apache.Ignite.Core/Apache.Ignite.Core.csproj   |   1 +
 .../Binary/BinaryConfiguration.cs                  |  12 +-
 .../Binary/ITimestampConverter.cs                  |  38 ++++
 .../IgniteClientConfigurationSection.xsd           |  12 ++
 .../IgniteConfigurationSection.xsd                 |  12 ++
 .../Apache.Ignite.Core/Impl/Binary/BinaryReader.cs |   8 +-
 .../Impl/Binary/BinarySystemHandlers.cs            |  25 ++-
 .../Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs  |  30 ++-
 .../Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs |   8 +-
 .../Apache.Ignite.Core/Impl/Binary/Marshaller.cs   |   8 +
 15 files changed, 412 insertions(+), 33 deletions(-)

diff --git a/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java b/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java
index cb3f8d7..688b3b1 100644
--- a/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java
+++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java
@@ -18,6 +18,8 @@
 package org.apache.ignite.platform;
 
 import java.sql.Timestamp;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collection;
@@ -531,6 +533,24 @@ public class PlatformDeployServiceTask extends ComputeTaskAdapter<String, Object
         }
 
         /** */
+        public void testLocalDateFromCache() {
+            IgniteCache<Integer, Timestamp> cache = ignite.cache("net-dates");
+
+            ZoneId msk = ZoneId.of("Europe/Moscow");
+
+            //This Date in Europe/Moscow have offset +4.
+            Timestamp ts1 = new Timestamp(ZonedDateTime.of(1982, 4, 1, 1, 0, 0, 0, msk).toInstant().toEpochMilli());
+            //This Date in Europe/Moscow have offset +3.
+            Timestamp ts2 = new Timestamp(ZonedDateTime.of(1982, 3, 31, 22, 0, 0, 0, msk).toInstant().toEpochMilli());
+
+            assertEquals(ts1, cache.get(5));
+            assertEquals(ts2, cache.get(6));
+
+            cache.put(7, ts1);
+            cache.put(8, ts2);
+        }
+
+        /** */
         public void sleep(long delayMs) {
             try {
                 U.sleep(delayMs);
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryDateTimeTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryDateTimeTest.cs
index e971eea..9eae893 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryDateTimeTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinaryDateTimeTest.cs
@@ -21,6 +21,7 @@ namespace Apache.Ignite.Core.Tests.Binary
     using System.Linq;
     using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Cache.Configuration;
+    using Apache.Ignite.Core.Impl.Binary;
     using NUnit.Framework;
 
     /// <summary>
@@ -28,6 +29,16 @@ namespace Apache.Ignite.Core.Tests.Binary
     /// </summary>
     public class BinaryDateTimeTest
     {
+        /** */
+        internal const String FromErrMsg = "FromJavaTicks Error!";
+
+        /** */
+        internal const String ToErrMsg = "ToJavaTicks Error!";
+
+        /** */
+        private const String NotUtcDate =
+            "DateTime is not UTC. Only UTC DateTime can be used for interop with other platforms.";
+
         /// <summary>
         /// Sets up the test fixture.
         /// </summary>
@@ -80,7 +91,7 @@ namespace Apache.Ignite.Core.Tests.Binary
                 .Select(x => x.Serializer)
                 .OfType<BinaryReflectiveSerializer>()
                 .Single();
-            
+
             Assert.IsTrue(ser.ForceTimestamp);
 
             AssertTimestampField<DateTimeObj2>((o, d) => o.Value = d, o => o.Value, "Value");
@@ -111,6 +122,152 @@ namespace Apache.Ignite.Core.Tests.Binary
         }
 
         /// <summary>
+        /// Tests custom timestamp converter that modifies the values by adding one year on write and read.
+        /// This test verifies that actual converted values are used by Ignite.
+        /// </summary>
+        [Test]
+        public void TestAddYearTimestampConverter()
+        {
+            var cfg =  new IgniteConfiguration(TestUtils.GetTestConfiguration())
+            {
+                AutoGenerateIgniteInstanceName = true,
+                BinaryConfiguration = new BinaryConfiguration
+                {
+                    ForceTimestamp = true, 
+                    TimestampConverter = new AddYearTimestampConverter()
+                }
+            };
+
+            var ignite = Ignition.Start(cfg);
+
+            var dt = DateTime.UtcNow;
+            var expected = dt.AddYears(2);
+
+            // Key & value.
+            var cache = ignite.GetOrCreateCache<DateTime, DateTime>(TestUtils.TestName);
+            cache[dt] = dt;
+
+            var resEntry = cache.Single();
+            
+            Assert.AreEqual(expected, resEntry.Key);
+            Assert.AreEqual(expected, resEntry.Value);
+            
+            // Key & value array.
+            
+            // Object field.
+            var cache2 = ignite.GetOrCreateCache<DateTimePropertyAttribute, DateTimePropertyAttribute>(
+                TestUtils.TestName);
+            
+            cache2.RemoveAll();
+            
+            var obj = new DateTimePropertyAttribute {Value = dt};
+            cache2[obj] = obj;
+
+            var resEntry2 = cache2.Single();
+            Assert.AreEqual(expected, resEntry2.Key.Value);
+            Assert.AreEqual(expected, resEntry2.Value.Value);
+            
+            // Object array field.
+            var cache3 = ignite.GetOrCreateCache<DateTimeArr, DateTimeArr>(TestUtils.TestName);
+            cache3.RemoveAll();
+            
+            var obj2 = new DateTimeArr {Value = new[]{dt}};
+            cache3[obj2] = obj2;
+            cache3[obj2] = obj2;
+
+            var resEntry3 = cache3.Single();
+            Assert.AreEqual(expected, resEntry3.Key.Value.Single());
+            Assert.AreEqual(expected, resEntry3.Value.Value.Single());
+        }
+
+        /// <summary>
+        /// Tests custom timestamp converter.
+        /// </summary>
+        [Test]
+        public void TestCustomTimestampConverter()
+        {
+            var cfg =  new IgniteConfiguration(TestUtils.GetTestConfiguration(name: "ignite-1"))
+            {
+                BinaryConfiguration = new BinaryConfiguration
+                {
+                    ForceTimestamp = true, 
+                    TimestampConverter = new TimestampConverter()
+                }
+            };
+
+            var ignite = Ignition.Start(cfg);
+            var binary = ignite.GetBinary();
+ 
+            // Check config.
+            Assert.NotNull(ignite.GetConfiguration().BinaryConfiguration.TimestampConverter);
+
+            AssertTimestampField<DateTimeObj2>((o, d) => o.Value = d, o => o.Value, "Value", ignite);
+
+            var dt1 = new DateTime(1997, 8, 29, 0, 0, 0, DateTimeKind.Utc);
+
+            var ex = Assert.Throws<BinaryObjectException>(() => binary.ToBinary<DateTime>(dt1));
+            Assert.AreEqual(ToErrMsg, ex.Message);
+
+            ex = Assert.Throws<BinaryObjectException>(() => binary.ToBinary<DateTime?[]>(new DateTime?[] {dt1}));
+            Assert.AreEqual(ToErrMsg, ex.Message);
+
+            var dt2 = new DateTime(1997, 8, 4, 0, 0, 0, DateTimeKind.Utc);
+
+            ex = Assert.Throws<BinaryObjectException>(() => binary.ToBinary<DateTime>(dt2));
+            Assert.AreEqual(FromErrMsg, ex.Message);
+
+            ex = Assert.Throws<BinaryObjectException>(() => binary.ToBinary<DateTime?[]>(new DateTime?[] {dt2}));
+            Assert.AreEqual(FromErrMsg, ex.Message);
+
+            var datesCache = ignite.CreateCache<DateTime, DateTime>("dates");
+
+            var check = new Action<DateTime, DateTime, String>((date1, date2, errMsg) =>
+            {
+                ex = Assert.Throws<BinaryObjectException>(() => datesCache.Put(date1, date2), "Timestamp fields should throw an error on non-UTC values");
+
+                Assert.AreEqual(errMsg, ex.Message);
+            });
+
+            check.Invoke(DateTime.Now, DateTime.UtcNow, NotUtcDate);
+            check.Invoke(DateTime.UtcNow, DateTime.Now, NotUtcDate);
+            check.Invoke(dt1, DateTime.UtcNow, ToErrMsg);
+            check.Invoke(DateTime.UtcNow, dt1, ToErrMsg);
+
+            var now = DateTime.UtcNow;
+
+            datesCache.Put(now, dt2);
+            ex = Assert.Throws<BinaryObjectException>(() => datesCache.Get(now), "Timestamp fields should throw an error on non-UTC values");
+            Assert.AreEqual(FromErrMsg, ex.Message);
+            
+            datesCache.Put(now, now);
+            Assert.AreEqual(now, datesCache.Get(now));
+
+            var datesObjCache = ignite.CreateCache<DateTimeObj, DateTimeObj>("datesObj");
+
+            check = (date1, date2, errMsg) =>
+            {
+                ex = Assert.Throws<BinaryObjectException>(() => datesObjCache.Put(new DateTimeObj {Value = date1}, new DateTimeObj {Value = date2}),
+                    "Timestamp fields should throw an error on non-UTC values");
+
+                Assert.AreEqual(errMsg, ex.Message);
+            };
+
+            check.Invoke(DateTime.Now, DateTime.UtcNow, NotUtcDate);
+            check.Invoke(DateTime.UtcNow, DateTime.Now, NotUtcDate);
+            check.Invoke(dt1, DateTime.UtcNow, ToErrMsg);
+            check.Invoke(DateTime.UtcNow, dt1, ToErrMsg);
+
+            var nowObj = new DateTimeObj {Value = now};
+
+            datesObjCache.Put(nowObj, new DateTimeObj {Value = dt2});
+            ex = Assert.Throws<BinaryObjectException>(() => datesObjCache.Get(nowObj), "Timestamp fields should throw an error on non-UTC values");
+            Assert.AreEqual(FromErrMsg, ex.Message);
+            
+            datesObjCache.Put(nowObj, nowObj);
+            Assert.AreEqual(nowObj.Value, datesObjCache.Get(nowObj).Value);
+        }
+
+        /// <summary>
         /// Asserts that specified field is serialized as DateTime object.
         /// </summary>
         private static void AssertDateTimeField<T>(Action<T, DateTime> setValue,
@@ -136,10 +293,10 @@ namespace Apache.Ignite.Core.Tests.Binary
         /// Asserts that specified field is serialized as Timestamp.
         /// </summary>
         private static void AssertTimestampField<T>(Action<T, DateTime> setValue,
-            Func<T, DateTime> getValue, string fieldName) where T : new()
+            Func<T, DateTime> getValue, string fieldName, IIgnite ignite = null) where T : new()
         {
             // Non-UTC DateTime throws.
-            var binary = Ignition.GetIgnite().GetBinary();
+            var binary = ignite != null ? ignite.GetBinary() : Ignition.GetIgnite().GetBinary();
 
             var obj = new T();
 
@@ -148,8 +305,7 @@ namespace Apache.Ignite.Core.Tests.Binary
             var ex = Assert.Throws<BinaryObjectException>(() => binary.ToBinary<IBinaryObject>(obj), 
                 "Timestamp fields should throw an error on non-UTC values");
 
-            Assert.AreEqual("DateTime is not UTC. Only UTC DateTime can be used for interop with other platforms.",
-                ex.Message);
+            Assert.AreEqual(NotUtcDate, ex.Message);
 
             // UTC DateTime works.
             setValue(obj, DateTime.UtcNow);
@@ -171,6 +327,11 @@ namespace Apache.Ignite.Core.Tests.Binary
             public DateTime Value { get; set; }
         }
 
+        private class DateTimeArr
+        {
+            public DateTime[] Value { get; set; }
+        }
+
         private class DateTimePropertyAttribute
         {
             [Timestamp]
@@ -200,5 +361,47 @@ namespace Apache.Ignite.Core.Tests.Binary
         {
             public DateTime Value;
         }
+        
+        private class TimestampConverter : ITimestampConverter
+        {
+            /** <inheritdoc /> */
+            public void ToJavaTicks(DateTime date, out long high, out int low)
+            {
+                if (date.Year == 1997 && date.Month == 8 && date.Day == 29)
+                    throw new BinaryObjectException(BinaryDateTimeTest.ToErrMsg);
+
+                BinaryUtils.ToJavaDate(date, out high, out low);
+            }
+
+            /** <inheritdoc /> */
+            public DateTime FromJavaTicks(long high, int low)
+            {
+                var date = new DateTime(BinaryUtils.JavaDateTicks + high * TimeSpan.TicksPerMillisecond + low / 100,
+                    DateTimeKind.Utc);
+
+                if (date.Year == 1997 && date.Month == 8 && date.Day == 4)
+                    throw new BinaryObjectException(BinaryDateTimeTest.FromErrMsg);
+
+                return date;
+            }
+        }
+        
+        private class AddYearTimestampConverter : ITimestampConverter
+        {
+            /** <inheritdoc /> */
+            public void ToJavaTicks(DateTime date, out long high, out int low)
+            {
+                BinaryUtils.ToJavaDate(date.AddYears(1), out high, out low);
+            }
+
+            /** <inheritdoc /> */
+            public DateTime FromJavaTicks(long high, int low)
+            {
+                var date = new DateTime(BinaryUtils.JavaDateTicks + high * TimeSpan.TicksPerMillisecond + low / 100,
+                    DateTimeKind.Utc);
+
+                return date.AddYears(1);
+            }
+        }
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs
index dfcaaff..07db779b 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs
@@ -187,6 +187,9 @@ namespace Apache.Ignite.Core.Tests.Services
         void testUTCDateFromCache();
 
         /** */
+        void testLocalDateFromCache();
+
+        /** */
         void sleep(long delayMs);
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs
index 26c0637..4156602 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs
@@ -350,6 +350,12 @@ namespace Apache.Ignite.Core.Tests.Services
         }
 
         /** <inheritDoc /> */
+        public void testLocalDateFromCache()
+        {
+            _svc.testLocalDateFromCache();
+        }
+
+        /** <inheritDoc /> */
         public void sleep(long delayMs)
         {
             _svc.sleep(delayMs);
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
index b2748ae..682f651 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
@@ -27,6 +27,7 @@ namespace Apache.Ignite.Core.Tests.Services
     using Apache.Ignite.Core.Cluster;
     using Apache.Ignite.Core.Common;
     using Apache.Ignite.Core.Impl;
+    using Apache.Ignite.Core.Impl.Binary;
     using Apache.Ignite.Core.Resource;
     using Apache.Ignite.Core.Services;
     using NUnit.Framework;
@@ -973,6 +974,28 @@ namespace Apache.Ignite.Core.Tests.Services
             Assert.AreEqual(dt1, cache.Get(3));
             Assert.AreEqual(dt2, cache.Get(4));
 
+#if NETCOREAPP
+            //This Date in Europe/Moscow have offset +4.
+            DateTime dt3 = new DateTime(1982, 4, 1, 1, 0, 0, 0, DateTimeKind.Local);
+            //This Date in Europe/Moscow have offset +3.
+            DateTime dt4 = new DateTime(1982, 3, 31, 22, 0, 0, 0, DateTimeKind.Local);
+
+            cache.Put(5, dt3);
+            cache.Put(6, dt4);
+
+            Assert.AreEqual(dt3.ToUniversalTime(), cache.Get(5).ToUniversalTime());
+            Assert.AreEqual(dt4.ToUniversalTime(), cache.Get(6).ToUniversalTime());
+
+            svc.testLocalDateFromCache();
+
+            Assert.AreEqual(dt3, cache.Get(7).ToLocalTime());
+            Assert.AreEqual(dt4, cache.Get(8).ToLocalTime());
+
+            var now = DateTime.Now;
+            cache.Put(9, now);
+            Assert.AreEqual(now.ToUniversalTime(), cache.Get(9).ToUniversalTime());
+#endif
+
             Services.Cancel(javaSvcName);
         }
 
@@ -1157,6 +1180,9 @@ namespace Apache.Ignite.Core.Tests.Services
                 {
                     NameMapper = BinaryBasicNameMapper.SimpleNameInstance,
                     ForceTimestamp = true
+#if NETCOREAPP
+                    , TimestampConverter = new TimestampConverter()
+#endif
                 }
             };
         }
@@ -1539,5 +1565,28 @@ namespace Apache.Ignite.Core.Tests.Services
             /** */
             public int Field { get; set; }
         }
+        
+#if NETCOREAPP
+        /// <summary>
+        /// Adds support of the local dates to the Ignite timestamp serialization.
+        /// </summary>
+        class TimestampConverter : ITimestampConverter
+        {
+            /** <inheritdoc /> */
+            public void ToJavaTicks(DateTime date, out long high, out int low)
+            {
+                if (date.Kind == DateTimeKind.Local)
+                    date = date.ToUniversalTime();
+
+                BinaryUtils.ToJavaDate(date, out high, out low);
+            }
+
+            /** <inheritdoc /> */
+            public DateTime FromJavaTicks(long high, int low)
+            {
+                return new DateTime(BinaryUtils.JavaDateTicks + high * TimeSpan.TicksPerMillisecond + low / 100, DateTimeKind.Utc);
+            }
+        }
+#endif
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
index c315f08..55488ce 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
@@ -52,6 +52,7 @@
   <ItemGroup>
     <Compile Include="Binary\BinaryBasicNameMapper.cs" />
     <Compile Include="Binary\TimestampAttribute.cs" />
+    <Compile Include="Binary\ITimestampConverter.cs" />
     <Compile Include="Cache\Affinity\IAffinityBackupFilter.cs" />
     <Compile Include="Cache\Affinity\Rendezvous\ClusterNodeAttributeAffinityBackupFilter.cs" />
     <Compile Include="Cache\Configuration\CacheKeyConfiguration.cs" />
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs
index 2a2bb95..b25a9f5 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs
@@ -79,6 +79,7 @@ namespace Apache.Ignite.Core.Binary
             NameMapper = cfg.NameMapper;
             KeepDeserialized = cfg.KeepDeserialized;
             ForceTimestamp = cfg.ForceTimestamp;
+            TimestampConverter = cfg.TimestampConverter;
 
             if (cfg.Serializer != null)
             {
@@ -137,6 +138,15 @@ namespace Apache.Ignite.Core.Binary
         public IBinarySerializer Serializer { get; set; }
 
         /// <summary>
+        /// Gets or sets a converter between <see cref="DateTime"/> and Java Timestamp.
+        /// Called from <see cref="IBinaryWriter.WriteTimestamp"/>, <see cref="IBinaryWriter.WriteTimestampArray"/>,
+        /// <see cref="IBinaryReader.ReadTimestamp"/>, <see cref="IBinaryReader.ReadTimestampArray"/>.
+        /// <para />
+        /// See also <see cref="ForceTimestamp"/>.
+        /// </summary>
+        public ITimestampConverter TimestampConverter { get; set; }
+
+        /// <summary>
         /// Default keep deserialized flag.
         /// </summary>
         [DefaultValue(DefaultKeepDeserialized)]
@@ -164,7 +174,7 @@ namespace Apache.Ignite.Core.Binary
         /// should be written as a Timestamp.
         /// <para />
         /// Timestamp format is required for values used in SQL and for interoperation with other platforms.
-        /// Only UTC values are supported in Timestamp format. Other values will cause an exception on write.
+        /// Only UTC values are supported in Timestamp format. Other values will cause an exception on write, unless <see cref="TimestampConverter"/> is provided.
         /// <para />
         /// Normally Ignite serializer uses <see cref="IBinaryWriter.WriteObject{T}"/> for DateTime fields,
         /// keys and values.
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/ITimestampConverter.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/ITimestampConverter.cs
new file mode 100644
index 0000000..c760479
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/ITimestampConverter.cs
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Core.Binary
+{
+    using System;
+
+    /// <summary>
+    /// Converts <see cref="DateTime"/> values to Java Timestamp and back.
+    /// </summary>
+    public interface ITimestampConverter
+    {
+        /// <summary>Converts date to Java ticks.</summary>
+        /// <param name="date">Date</param>
+        /// <param name="high">High part (milliseconds).</param>
+        /// <param name="low">Low part (nanoseconds)</param>
+        void ToJavaTicks(DateTime date, out long high, out int low);
+
+        /// <summary>Converts date from Java ticks.</summary>
+        /// <param name="high">High part (milliseconds).</param>
+        /// <param name="low">Low part (nanoseconds)</param>
+        DateTime FromJavaTicks(long high, int low);
+    }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd
index 42aa56b..b610318 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd
@@ -174,6 +174,18 @@
                                     </xs:attribute>
                                 </xs:complexType>
                             </xs:element>
+                            <xs:element name="timestampConverter" minOccurs="0">
+                                <xs:annotation>
+                                    <xs:documentation>Default date time converter.</xs:documentation>
+                                </xs:annotation>
+                                <xs:complexType>
+                                    <xs:attribute name="type" type="xs:string" use="required">
+                                        <xs:annotation>
+                                            <xs:documentation>Assembly-qualified type name.</xs:documentation>
+                                        </xs:annotation>
+                                    </xs:attribute>
+                                </xs:complexType>
+                            </xs:element>
                         </xs:all>
                         <xs:attribute name="keepDeserialized" type="xs:boolean">
                             <xs:annotation>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd
index 4bb0fef..a840d99 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd
@@ -263,6 +263,18 @@
                                     </xs:attribute>
                                 </xs:complexType>
                             </xs:element>
+                            <xs:element name="timestampConverter" minOccurs="0">
+                                <xs:annotation>
+                                    <xs:documentation>Default date time converter.</xs:documentation>
+                                </xs:annotation>
+                                <xs:complexType>
+                                    <xs:attribute name="type" type="xs:string" use="required">
+                                        <xs:annotation>
+                                            <xs:documentation>Assembly-qualified type name.</xs:documentation>
+                                        </xs:annotation>
+                                    </xs:attribute>
+                                </xs:complexType>
+                            </xs:element>
                         </xs:all>
                         <xs:attribute name="keepDeserialized" type="xs:boolean">
                             <xs:annotation>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs
index 9153046..c1a6fed 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs
@@ -315,25 +315,25 @@ namespace Apache.Ignite.Core.Impl.Binary
         /** <inheritdoc /> */
         public DateTime? ReadTimestamp(string fieldName)
         {
-            return ReadField(fieldName, BinaryUtils.ReadTimestamp, BinaryTypeId.Timestamp);
+            return ReadField(fieldName, stream => BinaryUtils.ReadTimestamp(stream, _marsh.TimestampConverter), BinaryTypeId.Timestamp);
         }
 
         /** <inheritdoc /> */
         public DateTime? ReadTimestamp()
         {
-            return Read(BinaryUtils.ReadTimestamp, BinaryTypeId.Timestamp);
+            return Read(stream => BinaryUtils.ReadTimestamp(stream, _marsh.TimestampConverter), BinaryTypeId.Timestamp);
         }
         
         /** <inheritdoc /> */
         public DateTime?[] ReadTimestampArray(string fieldName)
         {
-            return ReadField(fieldName, BinaryUtils.ReadTimestampArray, BinaryTypeId.ArrayTimestamp);
+            return ReadField(fieldName, stream => BinaryUtils.ReadTimestampArray(stream, _marsh.TimestampConverter), BinaryTypeId.ArrayTimestamp);
         }
         
         /** <inheritdoc /> */
         public DateTime?[] ReadTimestampArray()
         {
-            return Read(BinaryUtils.ReadTimestampArray, BinaryTypeId.ArrayTimestamp);
+            return Read(stream => BinaryUtils.ReadTimestampArray(stream, _marsh.TimestampConverter), BinaryTypeId.ArrayTimestamp);
         }
         
         /** <inheritdoc /> */
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs
index 3ffb1f9..8fdf371 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs
@@ -54,9 +54,6 @@ namespace Apache.Ignite.Core.Impl.Binary
             ReadHandlers[BinaryTypeId.Double] = new BinarySystemReader<double>(s => s.ReadDouble());
             ReadHandlers[BinaryTypeId.Decimal] = new BinarySystemReader<decimal?>(BinaryUtils.ReadDecimal);
 
-            // 2. Date.
-            ReadHandlers[BinaryTypeId.Timestamp] = new BinarySystemReader<DateTime?>(BinaryUtils.ReadTimestamp);
-
             // 3. String.
             ReadHandlers[BinaryTypeId.String] = new BinarySystemReader<string>(BinaryUtils.ReadString);
 
@@ -92,10 +89,6 @@ namespace Apache.Ignite.Core.Impl.Binary
             ReadHandlers[BinaryTypeId.ArrayDecimal] =
                 new BinarySystemReader<decimal?[]>(BinaryUtils.ReadDecimalArray);
 
-            // 6. Date array.
-            ReadHandlers[BinaryTypeId.ArrayTimestamp] =
-                new BinarySystemReader<DateTime?[]>(BinaryUtils.ReadTimestampArray);
-
             // 7. String array.
             ReadHandlers[BinaryTypeId.ArrayString] = new BinarySystemTypedArrayReader<string>();
 
@@ -258,6 +251,20 @@ namespace Apache.Ignite.Core.Impl.Binary
 
             if (handler == null)
             {
+                if (typeId == BinaryTypeId.Timestamp)
+                {
+                    // Date.
+                    res = TypeCaster<T>.Cast(BinaryUtils.ReadTimestamp(ctx.Stream, ctx.Marshaller.TimestampConverter));
+                    return true;
+                }
+
+                if (typeId == BinaryTypeId.ArrayTimestamp)
+                {
+                    // Date array.
+                    res = TypeCaster<T>.Cast(BinaryUtils.ReadTimestampArray(ctx.Stream, ctx.Marshaller.TimestampConverter));
+                    return true;
+                }
+
                 res = default(T);
                 return false;
             }
@@ -311,7 +318,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         {
             ctx.Stream.WriteByte(BinaryTypeId.Timestamp);
 
-            BinaryUtils.WriteTimestamp(obj, ctx.Stream);
+            BinaryUtils.WriteTimestamp(obj, ctx.Stream, ctx.Marshaller.TimestampConverter);
         }
 
         /// <summary>
@@ -455,7 +462,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         {
             ctx.Stream.WriteByte(BinaryTypeId.ArrayTimestamp);
 
-            BinaryUtils.WriteTimestampArray(obj, ctx.Stream);
+            BinaryUtils.WriteTimestampArray(obj, ctx.Stream, ctx.Marshaller.TimestampConverter);
         }
 
         /// <summary>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
index 5b30c7e..82e2284 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
@@ -69,7 +69,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         public const int ObjTypeId = -1;
 
         /** Ticks for Java epoch. */
-        private static readonly long JavaDateTicks = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).Ticks;
+        public static readonly long JavaDateTicks = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).Ticks;
 
         /** Binding flags for static search. */
         private const BindingFlags BindFlagsStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
@@ -388,13 +388,17 @@ namespace Apache.Ignite.Core.Impl.Binary
          * <summary>Write date.</summary>
          * <param name="val">Date.</param>
          * <param name="stream">Stream.</param>
+         * <param name="converter">Timestamp Converter.</param>
          */
-        public static void WriteTimestamp(DateTime val, IBinaryStream stream)
+        public static void WriteTimestamp(DateTime val, IBinaryStream stream, ITimestampConverter converter)
         {
             long high;
             int low;
 
-            ToJavaDate(val, out high, out low);
+            if (converter != null)
+                converter.ToJavaTicks(val, out high, out low);
+            else
+                ToJavaDate(val, out high, out low);
 
             stream.WriteLong(high);
             stream.WriteInt(low);
@@ -403,14 +407,18 @@ namespace Apache.Ignite.Core.Impl.Binary
         /**
          * <summary>Read date.</summary>
          * <param name="stream">Stream.</param>
+         * <param name="converter">Timestamp Converter.</param>
          * <returns>Date</returns>
          */
-        public static DateTime? ReadTimestamp(IBinaryStream stream)
+        public static DateTime? ReadTimestamp(IBinaryStream stream, ITimestampConverter converter)
         {
             long high = stream.ReadLong();
             int low = stream.ReadInt();
 
-            return new DateTime(JavaDateTicks + high * TimeSpan.TicksPerMillisecond + low / 100, DateTimeKind.Utc);
+            if (converter != null)
+                return converter.FromJavaTicks(high, low);
+            else
+                return new DateTime(JavaDateTicks + high * TimeSpan.TicksPerMillisecond + low / 100, DateTimeKind.Utc);
         }
 
         /// <summary>
@@ -438,7 +446,8 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// </summary>
         /// <param name="vals">Values.</param>
         /// <param name="stream">Stream.</param>
-        public static void WriteTimestampArray(DateTime?[] vals, IBinaryStream stream)
+        /// <param name="converter">Timestamp Converter.</param>
+        public static void WriteTimestampArray(DateTime?[] vals, IBinaryStream stream, ITimestampConverter converter)
         {
             stream.WriteInt(vals.Length);
 
@@ -448,7 +457,7 @@ namespace Apache.Ignite.Core.Impl.Binary
                 {
                     stream.WriteByte(BinaryTypeId.Timestamp);
 
-                    WriteTimestamp(val.Value, stream);
+                    WriteTimestamp(val.Value, stream, converter);
                 }
                 else
                     stream.WriteByte(HdrNull);
@@ -1165,15 +1174,16 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// Read timestamp array.
         /// </summary>
         /// <param name="stream">Stream.</param>
+        /// <param name="converter">Timestamp Converter.</param>
         /// <returns>Timestamp array.</returns>
-        public static DateTime?[] ReadTimestampArray(IBinaryStream stream)
+        public static DateTime?[] ReadTimestampArray(IBinaryStream stream, ITimestampConverter converter)
         {
             int len = stream.ReadInt();
 
             DateTime?[] vals = new DateTime?[len];
 
             for (int i = 0; i < len; i++)
-                vals[i] = stream.ReadByte() == HdrNull ? null : ReadTimestamp(stream);
+                vals[i] = stream.ReadByte() == HdrNull ? null : ReadTimestamp(stream, converter);
 
             return vals;
         }
@@ -1612,7 +1622,7 @@ namespace Apache.Ignite.Core.Impl.Binary
          * <param name="high">High part (milliseconds).</param>
          * <param name="low">Low part (nanoseconds)</param>
          */
-        private static void ToJavaDate(DateTime date, out long high, out int low)
+        public static void ToJavaDate(DateTime date, out long high, out int low)
         {
             if (date.Kind != DateTimeKind.Utc)
             {
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs
index 9025b4d..a39bcd7 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs
@@ -657,7 +657,7 @@ namespace Apache.Ignite.Core.Impl.Binary
             else
             {
                 _stream.WriteByte(BinaryTypeId.Timestamp);
-                BinaryUtils.WriteTimestamp(val.Value, _stream);
+                BinaryUtils.WriteTimestamp(val.Value, _stream, _marsh.TimestampConverter);
             }
         }
         
@@ -672,7 +672,7 @@ namespace Apache.Ignite.Core.Impl.Binary
             else
             {
                 _stream.WriteByte(BinaryTypeId.Timestamp);
-                BinaryUtils.WriteTimestamp(val.Value, _stream);
+                BinaryUtils.WriteTimestamp(val.Value, _stream, _marsh.TimestampConverter);
             }
         }
 
@@ -690,7 +690,7 @@ namespace Apache.Ignite.Core.Impl.Binary
             else
             {
                 _stream.WriteByte(BinaryTypeId.ArrayTimestamp);
-                BinaryUtils.WriteTimestampArray(val, _stream);
+                BinaryUtils.WriteTimestampArray(val, _stream, _marsh.TimestampConverter);
             }
         }
 
@@ -705,7 +705,7 @@ namespace Apache.Ignite.Core.Impl.Binary
             else
             {
                 _stream.WriteByte(BinaryTypeId.ArrayTimestamp);
-                BinaryUtils.WriteTimestampArray(val, _stream);
+                BinaryUtils.WriteTimestampArray(val, _stream, _marsh.TimestampConverter);
             }
         }
 
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
index cc9aeff..76b9cbc 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs
@@ -145,6 +145,14 @@ namespace Apache.Ignite.Core.Impl.Binary
         }
 
         /// <summary>
+        /// Gets date time converter.
+        /// </summary>
+        public ITimestampConverter TimestampConverter
+        {
+            get { return _cfg.TimestampConverter; }
+        }
+
+        /// <summary>
         /// Marshal object.
         /// </summary>
         /// <param name="val">Value.</param>