diff --git a/DateTimeNano.Tests/DateTimeNanoTests.cs b/DateTimeNano.Tests/DateTimeNanoTests.cs
index 678e9a0..2f4f73d 100644
--- a/DateTimeNano.Tests/DateTimeNanoTests.cs
+++ b/DateTimeNano.Tests/DateTimeNanoTests.cs
@@ -448,6 +448,61 @@ public void Now_ShouldReturnRecentUtcTime()
Assert.That(now.ToDateTimeUtc(), Is.LessThanOrEqualTo(after));
}
+ // ── DateTimeOffset interop ────────────────────────────────────────────────
+
+ [Test]
+ public void Constructor_DateTimeOffset_UtcOffset_ShouldStoreCorrectNanoseconds()
+ {
+ var dto = new DateTimeOffset(2025, 2, 10, 20, 27, 12, 123, TimeSpan.Zero);
+ var nano = new Seerstone.DateTimeNano(dto);
+ Assert.That(nano.NanosecondsSinceEpoch, Is.EqualTo(1_739_219_232_123_000_000UL));
+ }
+
+ [Test]
+ public void Constructor_DateTimeOffset_NonUtcOffset_ShouldConvertToUtc()
+ {
+ // +05:30 (IST) — same instant as 15:00 UTC
+ var dto = new DateTimeOffset(2025, 2, 10, 20, 30, 0, TimeSpan.FromHours(5.5));
+ var expected = new Seerstone.DateTimeNano(dto.UtcDateTime);
+ var actual = new Seerstone.DateTimeNano(dto);
+ Assert.That(actual.NanosecondsSinceEpoch, Is.EqualTo(expected.NanosecondsSinceEpoch));
+ }
+
+ [Test]
+ public void ToDateTimeOffsetUtc_ShouldReturnUtcOffset()
+ {
+ var nano = new Seerstone.DateTimeNano(1_739_219_232_123_000_000UL);
+ var dto = nano.ToDateTimeOffsetUtc();
+ Assert.That(dto.Offset, Is.EqualTo(TimeSpan.Zero));
+ Assert.That(dto.UtcDateTime, Is.EqualTo(new DateTime(2025, 2, 10, 20, 27, 12, 123, DateTimeKind.Utc)));
+ }
+
+ [Test]
+ public void ImplicitOperator_DateTimeNanoToDateTimeOffset_ShouldBeUtc()
+ {
+ var nano = new Seerstone.DateTimeNano(1_739_219_232_123_000_000UL);
+ DateTimeOffset dto = nano;
+ Assert.That(dto.Offset, Is.EqualTo(TimeSpan.Zero));
+ Assert.That(dto.UtcDateTime, Is.EqualTo(nano.ToDateTimeUtc()));
+ }
+
+ [Test]
+ public void ImplicitOperator_DateTimeOffsetToDateTimeNano_RoundTrips()
+ {
+ var original = new DateTimeOffset(2025, 2, 10, 20, 27, 12, 123, TimeSpan.Zero);
+ Seerstone.DateTimeNano nano = original;
+ DateTimeOffset roundTripped = nano;
+ Assert.That(roundTripped, Is.EqualTo(original));
+ }
+
+ [Test]
+ public void ImplicitOperator_NonUtcDateTimeOffset_ShouldNormalisedToUtc()
+ {
+ var dto = new DateTimeOffset(2025, 2, 10, 21, 27, 12, 123, TimeSpan.FromHours(1));
+ Seerstone.DateTimeNano nano = dto;
+ Assert.That(nano.ToDateTimeUtc(), Is.EqualTo(dto.UtcDateTime));
+ }
+
// ── Parts constructor ─────────────────────────────────────────────────────
[Test]
diff --git a/DateTimeNano/DateTimeNano.cs b/DateTimeNano/DateTimeNano.cs
index 9782d2f..14411f1 100644
--- a/DateTimeNano/DateTimeNano.cs
+++ b/DateTimeNano/DateTimeNano.cs
@@ -442,5 +442,28 @@ public override string ToString()
/// Implicitly converts a to a .
public static implicit operator DateTimeNano(DateTime d) => new DateTimeNano(d);
+
+ ///
+ /// Create a from a .
+ /// The offset is converted to UTC before the nanosecond value is computed,
+ /// so the stored timestamp always represents the UTC instant.
+ ///
+ /// The source .
+ public DateTimeNano(DateTimeOffset datetimeOffset)
+ : this(datetimeOffset.UtcDateTime) { }
+
+ ///
+ /// Returns the UTC date and time as a with a zero UTC offset.
+ /// Sub-microsecond nanoseconds are not representable in
+ /// and are truncated; access them via .
+ ///
+ /// A in UTC.
+ public DateTimeOffset ToDateTimeOffsetUtc() => new DateTimeOffset(DateTime, TimeSpan.Zero);
+
+ /// Implicitly converts a to a UTC .
+ public static implicit operator DateTimeOffset(DateTimeNano d) => d.ToDateTimeOffsetUtc();
+
+ /// Implicitly converts a to a .
+ public static implicit operator DateTimeNano(DateTimeOffset d) => new DateTimeNano(d);
}
}