Skip to content

Commit 3fd4d65

Browse files
Add an UtcTicksType
Part of #1631
1 parent facb8c2 commit 3fd4d65

14 files changed

+239
-116
lines changed

doc/reference/modules/basic_mapping.xml

+15-1
Original file line numberDiff line numberDiff line change
@@ -3733,7 +3733,10 @@
37333733
<entry><literal>Ticks</literal></entry>
37343734
<entry><literal>System.DateTime</literal></entry>
37353735
<entry><literal>DbType.Int64</literal></entry>
3736-
<entry><literal>type="Ticks"</literal> must be specified.</entry>
3736+
<entry>
3737+
<literal>type="Ticks"</literal> must be specified. This is the recommended way to "timestamp" a
3738+
column, along with <literal>UtcTicks</literal>.
3739+
</entry>
37373740
</row>
37383741
<row>
37393742
<entry><literal>Time</literal></entry>
@@ -3812,6 +3815,17 @@
38123815
Available since NHibernate v5.0.
38133816
</entry>
38143817
</row>
3818+
<row>
3819+
<entry><literal>UtcTicks</literal></entry>
3820+
<entry><literal>System.DateTime</literal></entry>
3821+
<entry><literal>DbType.Int64</literal></entry>
3822+
<entry>
3823+
<literal>type="UtcTicks"</literal> must be specified. This is the recommended way to "timestamp" a
3824+
column, along with <literal>Ticks</literal>.
3825+
Ensures the <literal>DateTimeKind</literal> is set to <literal>DateTimeKind.Utc</literal>.
3826+
Throws if set with a date having another kind.
3827+
</entry>
3828+
</row>
38153829
<row>
38163830
<entry><literal>YesNo</literal></entry>
38173831
<entry><literal>System.Boolean</literal></entry>

src/NHibernate.Test/Async/TypesTest/AbstractDateTimeTypeFixture.cs

+53-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ protected override void DropSchema()
8888
[Test]
8989
public async Task NextAsync()
9090
{
91-
var current = DateTime.Parse("2004-01-01");
91+
var current = Now.Subtract(TimeSpan.FromTicks(DateAccuracyInTicks));
9292
var next = await (Type.NextAsync(current, null, CancellationToken.None));
9393

9494
Assert.That(next, Is.TypeOf<DateTime>(), "next should be DateTime");
@@ -101,6 +101,14 @@ public async Task SeedAsync()
101101
Assert.That(await (Type.SeedAsync(null, CancellationToken.None)), Is.TypeOf<DateTime>(), "seed should be DateTime");
102102
}
103103

104+
[Test]
105+
public async Task ComparerAsync()
106+
{
107+
var v1 = await (Type.SeedAsync(null, CancellationToken.None));
108+
var v2 = Now.Subtract(TimeSpan.FromTicks(DateAccuracyInTicks));
109+
Assert.That(() => Type.Comparator.Compare(v1, v2), Throws.Nothing);
110+
}
111+
104112
[Test]
105113
[TestCase(DateTimeKind.Unspecified)]
106114
[TestCase(DateTimeKind.Local)]
@@ -322,6 +330,37 @@ public virtual async Task QueryUseExpectedSqlTypeAsync()
322330
AssertSqlType(driver, 5, true);
323331
}
324332

333+
/// <summary>
334+
/// Tests if the type FromStringValue implementation behaves as expected.
335+
/// </summary>
336+
/// <param name="timestampValue"></param>
337+
[Test]
338+
[TestCase("2011-01-27T15:50:59.6220000+02:00")]
339+
[TestCase("2011-01-27T14:50:59.6220000+01:00")]
340+
[TestCase("2011-01-27T13:50:59.6220000Z")]
341+
[Obsolete]
342+
public virtual void FromStringValue_ParseValidValues(string timestampValue)
343+
{
344+
var timestamp = DateTime.Parse(timestampValue);
345+
346+
Assert.That(
347+
timestamp.Kind,
348+
Is.EqualTo(DateTimeKind.Local),
349+
"Kind is NOT Local. dotnet framework parses datetime values with kind set to Local and " +
350+
"time correct to local timezone.");
351+
352+
var typeKind = GetTypeKind();
353+
if (typeKind == DateTimeKind.Utc)
354+
timestamp = timestamp.ToUniversalTime();
355+
356+
var value = (DateTime) Type.FromStringValue(timestampValue);
357+
358+
Assert.That(value, Is.EqualTo(timestamp), timestampValue);
359+
360+
if (typeKind != DateTimeKind.Unspecified)
361+
Assert.AreEqual(GetTypeKind(), value.Kind, "Unexpected FromStringValue kind");
362+
}
363+
325364
private void AssertSqlType(ClientDriverWithParamsStats driver, int expectedCount, bool exactType)
326365
{
327366
var typeSqlTypes = Type.SqlTypes(Sfi);
@@ -369,6 +408,19 @@ private void AssertSqlType(ClientDriverWithParamsStats driver, int expectedCount
369408
Assert.That(driver.GetCount(DbType.DateTime), Is.EqualTo(0), "Found unexpected DbType.DateTime usages.");
370409
Assert.That(driver.GetCount(DbType.Date), Is.EqualTo(expectedCount), "Unexpected DbType.Date usage count.");
371410
}
411+
else if (typeSqlTypes.Any(t => Equals(t, SqlTypeFactory.Int64)))
412+
{
413+
Assert.That(
414+
driver.GetCount(SqlTypeFactory.DateTime),
415+
Is.EqualTo(0),
416+
"Found unexpected SqlTypeFactory.DateTime usages.");
417+
Assert.That(
418+
driver.GetCount(SqlTypeFactory.Int64),
419+
Is.EqualTo(expectedCount),
420+
"Unexpected SqlTypeFactory.Int64 usage count.");
421+
Assert.That(driver.GetCount(DbType.DateTime), Is.EqualTo(0), "Found unexpected DbType.DateTime usages.");
422+
Assert.That(driver.GetCount(DbType.Int64), Is.EqualTo(expectedCount), "Unexpected DbType.Int64 usage count.");
423+
}
372424
else
373425
{
374426
Assert.Ignore("Test does not involve tested types");

src/NHibernate.Test/Async/TypesTest/TicksTypeFixture.cs

+11-26
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,24 @@
1515
namespace NHibernate.Test.TypesTest
1616
{
1717
using System.Threading.Tasks;
18-
using System.Threading;
19-
/// <summary>
20-
/// Summary description for TicksTypeFixture.
21-
/// </summary>
2218
[TestFixture]
23-
public class TicksTypeFixtureAsync
19+
public class TicksTypeFixtureAsync : AbstractDateTimeTypeFixtureAsync
2420
{
25-
[Test]
26-
public async Task NextAsync()
27-
{
28-
TicksType type = (TicksType) NHibernateUtil.Ticks;
29-
object current = new DateTime(2004, 1, 1, 1, 1, 1, 1);
30-
object next = await (type.NextAsync(current, null, CancellationToken.None));
31-
32-
Assert.IsTrue(next is DateTime, "Next should be DateTime");
33-
Assert.IsTrue((DateTime) next > (DateTime) current,
34-
"next should be greater than current (could be equal depending on how quickly this occurs)");
35-
}
21+
protected override string TypeName => "Ticks";
22+
protected override AbstractDateTimeType Type => NHibernateUtil.Ticks;
3623

3724
[Test]
38-
public async Task SeedAsync()
25+
[TestCase("0")]
26+
[Obsolete]
27+
[Ignore("Ticks parse integer representations to date instead of date representations")]
28+
public override void FromStringValue_ParseValidValues(string timestampValue)
3929
{
40-
TicksType type = (TicksType) NHibernateUtil.Ticks;
41-
Assert.IsTrue(await (type.SeedAsync(null, CancellationToken.None)) is DateTime, "seed should be DateTime");
4230
}
4331

44-
[Test]
45-
public async Task ComparerAsync()
32+
[Ignore("Test relevant for datetime, not for ticks.")]
33+
public override Task QueryUseExpectedSqlTypeAsync()
4634
{
47-
var type = (IVersionType)NHibernateUtil.Ticks;
48-
object v1 = await (type.SeedAsync(null, CancellationToken.None));
49-
var v2 = v1;
50-
Assert.DoesNotThrow(() => type.Comparator.Compare(v1, v2));
35+
return Task.CompletedTask;
5136
}
5237
}
53-
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using NHibernate.Type;
12+
using NUnit.Framework;
13+
14+
namespace NHibernate.Test.TypesTest
15+
{
16+
using System.Threading.Tasks;
17+
[TestFixture]
18+
public class UtcTicksTypeFixtureAsync : TicksTypeFixtureAsync
19+
{
20+
protected override string TypeName => "UtcTicks";
21+
protected override AbstractDateTimeType Type => NHibernateUtil.UtcTicks;
22+
}
23+
}

src/NHibernate.Test/TypesTest/AbstractDateTimeTypeFixture.cs

+23-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ protected override void DropSchema()
7676
[Test]
7777
public void Next()
7878
{
79-
var current = DateTime.Parse("2004-01-01");
79+
var current = Now.Subtract(TimeSpan.FromTicks(DateAccuracyInTicks));
8080
var next = Type.Next(current, null);
8181

8282
Assert.That(next, Is.TypeOf<DateTime>(), "next should be DateTime");
@@ -89,6 +89,14 @@ public void Seed()
8989
Assert.That(Type.Seed(null), Is.TypeOf<DateTime>(), "seed should be DateTime");
9090
}
9191

92+
[Test]
93+
public void Comparer()
94+
{
95+
var v1 = Type.Seed(null);
96+
var v2 = Now.Subtract(TimeSpan.FromTicks(DateAccuracyInTicks));
97+
Assert.That(() => Type.Comparator.Compare(v1, v2), Throws.Nothing);
98+
}
99+
92100
[Test]
93101
public void DeepCopyNotNull()
94102
{
@@ -369,7 +377,7 @@ public virtual void QueryUseExpectedSqlType()
369377
[TestCase("2011-01-27T14:50:59.6220000+01:00")]
370378
[TestCase("2011-01-27T13:50:59.6220000Z")]
371379
[Obsolete]
372-
public void FromStringValue_ParseValidValues(string timestampValue)
380+
public virtual void FromStringValue_ParseValidValues(string timestampValue)
373381
{
374382
var timestamp = DateTime.Parse(timestampValue);
375383

@@ -454,6 +462,19 @@ private void AssertSqlType(ClientDriverWithParamsStats driver, int expectedCount
454462
Assert.That(driver.GetCount(DbType.DateTime), Is.EqualTo(0), "Found unexpected DbType.DateTime usages.");
455463
Assert.That(driver.GetCount(DbType.Date), Is.EqualTo(expectedCount), "Unexpected DbType.Date usage count.");
456464
}
465+
else if (typeSqlTypes.Any(t => Equals(t, SqlTypeFactory.Int64)))
466+
{
467+
Assert.That(
468+
driver.GetCount(SqlTypeFactory.DateTime),
469+
Is.EqualTo(0),
470+
"Found unexpected SqlTypeFactory.DateTime usages.");
471+
Assert.That(
472+
driver.GetCount(SqlTypeFactory.Int64),
473+
Is.EqualTo(expectedCount),
474+
"Unexpected SqlTypeFactory.Int64 usage count.");
475+
Assert.That(driver.GetCount(DbType.DateTime), Is.EqualTo(0), "Found unexpected DbType.DateTime usages.");
476+
Assert.That(driver.GetCount(DbType.Int64), Is.EqualTo(expectedCount), "Unexpected DbType.Int64 usage count.");
477+
}
457478
else
458479
{
459480
Assert.Ignore("Test does not involve tested types");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="false">
4+
<class
5+
name="NHibernate.Test.TypesTest.DateTimeClass, NHibernate.Test"
6+
table="bc_datetime">
7+
<id name="Id" column="id">
8+
<generator class="assigned"/>
9+
</id>
10+
<version name="Revision" column="`Revision`" type="ticks"/>
11+
<property name="Value" column="`Value`" type="ticks"/>
12+
<property name="NullableValue" type="ticks"/>
13+
</class>
14+
</hibernate-mapping>

src/NHibernate.Test/TypesTest/TicksTypeFixture.cs

+10-25
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,23 @@
44

55
namespace NHibernate.Test.TypesTest
66
{
7-
/// <summary>
8-
/// Summary description for TicksTypeFixture.
9-
/// </summary>
107
[TestFixture]
11-
public class TicksTypeFixture
8+
public class TicksTypeFixture : AbstractDateTimeTypeFixture
129
{
13-
[Test]
14-
public void Next()
15-
{
16-
TicksType type = (TicksType) NHibernateUtil.Ticks;
17-
object current = new DateTime(2004, 1, 1, 1, 1, 1, 1);
18-
object next = type.Next(current, null);
19-
20-
Assert.IsTrue(next is DateTime, "Next should be DateTime");
21-
Assert.IsTrue((DateTime) next > (DateTime) current,
22-
"next should be greater than current (could be equal depending on how quickly this occurs)");
23-
}
10+
protected override string TypeName => "Ticks";
11+
protected override AbstractDateTimeType Type => NHibernateUtil.Ticks;
2412

2513
[Test]
26-
public void Seed()
14+
[TestCase("0")]
15+
[Obsolete]
16+
[Ignore("Ticks parse integer representations to date instead of date representations")]
17+
public override void FromStringValue_ParseValidValues(string timestampValue)
2718
{
28-
TicksType type = (TicksType) NHibernateUtil.Ticks;
29-
Assert.IsTrue(type.Seed(null) is DateTime, "seed should be DateTime");
3019
}
3120

32-
[Test]
33-
public void Comparer()
21+
[Ignore("Test relevant for datetime, not for ticks.")]
22+
public override void QueryUseExpectedSqlType()
3423
{
35-
var type = (IVersionType)NHibernateUtil.Ticks;
36-
object v1 = type.Seed(null);
37-
var v2 = v1;
38-
Assert.DoesNotThrow(() => type.Comparator.Compare(v1, v2));
3924
}
4025
}
41-
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="false">
4+
<class
5+
name="NHibernate.Test.TypesTest.DateTimeClass, NHibernate.Test"
6+
table="bc_datetime">
7+
<id name="Id" column="id">
8+
<generator class="assigned"/>
9+
</id>
10+
<version name="Revision" column="`Revision`" type="utcticks"/>
11+
<property name="Value" column="`Value`" type="utcticks"/>
12+
<property name="NullableValue" type="utcticks"/>
13+
</class>
14+
</hibernate-mapping>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using NHibernate.Type;
2+
using NUnit.Framework;
3+
4+
namespace NHibernate.Test.TypesTest
5+
{
6+
[TestFixture]
7+
public class UtcTicksTypeFixture : TicksTypeFixture
8+
{
9+
protected override string TypeName => "UtcTicks";
10+
protected override AbstractDateTimeType Type => NHibernateUtil.UtcTicks;
11+
}
12+
}

src/NHibernate/Async/Type/TicksType.cs

+2-13
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99

1010

1111
using System;
12-
using System.Collections;
13-
using System.Collections.Generic;
1412
using System.Data;
1513
using System.Data.Common;
1614
using NHibernate.Engine;
@@ -20,21 +18,12 @@ namespace NHibernate.Type
2018
{
2119
using System.Threading.Tasks;
2220
using System.Threading;
23-
public partial class TicksType : PrimitiveType, IVersionType, ILiteralType
21+
public partial class TicksType : AbstractDateTimeType
2422
{
2523

2624
#region IVersionType Members
2725

28-
public Task<object> NextAsync(object current, ISessionImplementor session, CancellationToken cancellationToken)
29-
{
30-
if (cancellationToken.IsCancellationRequested)
31-
{
32-
return Task.FromCanceled<object>(cancellationToken);
33-
}
34-
return SeedAsync(session, cancellationToken);
35-
}
36-
37-
public virtual Task<object> SeedAsync(ISessionImplementor session, CancellationToken cancellationToken)
26+
public override Task<object> SeedAsync(ISessionImplementor session, CancellationToken cancellationToken)
3827
{
3928
if (cancellationToken.IsCancellationRequested)
4029
{

src/NHibernate/NHibernateUtil.cs

+5
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@ public static IType GuessType(System.Type type)
217217
/// </summary>
218218
public static readonly TicksType Ticks = new TicksType();
219219

220+
/// <summary>
221+
/// NHibernate UTC Ticks type
222+
/// </summary>
223+
public static readonly UtcTicksType UtcTicks = new UtcTicksType();
224+
220225
/// <summary>
221226
/// NHibernate TimeAsTimeSpan type
222227
/// </summary>

0 commit comments

Comments
 (0)