diff --git a/tests/MTConnect.NET-Common-Tests/UnixTimeTests.cs b/tests/MTConnect.NET-Common-Tests/UnixTimeTests.cs
new file mode 100644
index 000000000..f14499c56
--- /dev/null
+++ b/tests/MTConnect.NET-Common-Tests/UnixTimeTests.cs
@@ -0,0 +1,285 @@
+// Copyright (c) 2026 TrakHound Inc., All Rights Reserved.
+// TrakHound Inc. licenses this file to you under the MIT license.
+
+using System;
+using NUnit.Framework;
+
+namespace MTConnect.Tests.Common
+{
+ ///
+ /// Pins the contract of ,
+ /// and their round-trip invariants.
+ /// Every mutation-visible constant, branch, and arithmetic step is
+ /// covered by at least one assertion so Stryker.NET kills the mutants
+ /// Stryker previously reported for this module.
+ /// See TrakHound/MTConnect.NET#242.
+ ///
+ [TestFixture]
+ public class UnixTimeTests
+ {
+ // --- Constants ---------------------------------------------------------
+
+ /// Pins the epoch instant to 1970-01-01T00:00:00Z; kills a constant-substitution mutant on the year/month/day/hour/minute/second/Kind arguments.
+ [Test]
+ public void EpochTime_IsUnixEpoch_1970_01_01_UtcMidnight()
+ {
+ var epoch = UnixTimeExtensions.EpochTime;
+ Assert.AreEqual(1970, epoch.Year);
+ Assert.AreEqual(1, epoch.Month);
+ Assert.AreEqual(1, epoch.Day);
+ Assert.AreEqual(0, epoch.Hour);
+ Assert.AreEqual(0, epoch.Minute);
+ Assert.AreEqual(0, epoch.Second);
+ Assert.AreEqual(0, epoch.Millisecond);
+ Assert.AreEqual(DateTimeKind.Utc, epoch.Kind);
+ }
+
+ /// Pins the constant against a fresh BCL-computed epoch tick count; kills a constant-value mutant on the literal 621355968000000000.
+ [Test]
+ public void EpochTicks_Constant_MatchesBclEpochTicks()
+ {
+ var bclEpochTicks = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
+ Assert.AreEqual(bclEpochTicks, UnixTimeExtensions.EpochTicks);
+ Assert.AreEqual(621355968000000000L, UnixTimeExtensions.EpochTicks);
+ Assert.AreEqual(UnixTimeExtensions.EpochTime.Ticks, UnixTimeExtensions.EpochTicks);
+ }
+
+ // --- ToUnixTime --------------------------------------------------------
+
+ /// Pins the epoch → 0 mapping; kills off-by-one arithmetic mutants on the epoch subtraction.
+ [Test]
+ public void ToUnixTime_AtEpoch_ReturnsZero()
+ {
+ Assert.AreEqual(0L, UnixTimeExtensions.EpochTime.ToUnixTime());
+ }
+
+ /// Pins the one-tick-after-epoch → 1 mapping; kills constant-substitution mutants on the arithmetic.
+ [Test]
+ public void ToUnixTime_OneTickAfterEpoch_ReturnsOne()
+ {
+ var oneAfter = new DateTime(UnixTimeExtensions.EpochTicks + 1, DateTimeKind.Utc);
+ Assert.AreEqual(1L, oneAfter.ToUnixTime());
+ }
+
+ /// Pins the one-tick-before-epoch → -1 mapping; kills a sign/subtract-swap mutant.
+ [Test]
+ public void ToUnixTime_OneTickBeforeEpoch_ReturnsNegativeOne()
+ {
+ var oneBefore = new DateTime(UnixTimeExtensions.EpochTicks - 1, DateTimeKind.Utc);
+ Assert.AreEqual(-1L, oneBefore.ToUnixTime());
+ }
+
+ /// Pins the arithmetic on a known-post-epoch Utc value; kills every constant-off arithmetic mutant on the epoch subtraction.
+ [Test]
+ public void ToUnixTime_UtcKind_ReturnsUtcTicksMinusEpochTicks()
+ {
+ var utc = new DateTime(2026, 1, 1, 12, 30, 45, DateTimeKind.Utc);
+ var expected = utc.Ticks - UnixTimeExtensions.EpochTicks;
+ Assert.AreEqual(expected, utc.ToUnixTime());
+ }
+
+ /// Pins the Local-branch conversion: a Local DateTime is converted to Utc before subtraction; assertion holds in every timezone.
+ [Test]
+ public void ToUnixTime_LocalKind_ConvertsToUtcBeforeSubtracting()
+ {
+ var local = new DateTime(2026, 1, 1, 12, 30, 45, DateTimeKind.Local);
+ var expected = TimeZoneInfo.ConvertTimeToUtc(local, TimeZoneInfo.Local).Ticks - UnixTimeExtensions.EpochTicks;
+ Assert.AreEqual(expected, local.ToUnixTime());
+ }
+
+ /// Pins the Unspecified-Kind behaviour of : an Unspecified value is treated as UTC (no conversion), so its ticks are subtracted from epoch verbatim.
+ [Test]
+ public void ToUnixTime_UnspecifiedKind_TreatedAsUtc()
+ {
+ var unspec = new DateTime(2026, 1, 1, 12, 30, 45, DateTimeKind.Unspecified);
+ var expected = unspec.Ticks - UnixTimeExtensions.EpochTicks;
+ Assert.AreEqual(expected, unspec.ToUnixTime());
+ }
+
+ // --- ToUnixUtcTime -----------------------------------------------------
+
+ /// Pins the Utc no-op path: a Utc DateTime is returned as its tick offset from epoch unchanged.
+ [Test]
+ public void ToUnixUtcTime_UtcKind_ReturnsUtcTicksMinusEpochTicks()
+ {
+ var utc = new DateTime(2026, 6, 15, 10, 20, 30, DateTimeKind.Utc);
+ var expected = utc.Ticks - UnixTimeExtensions.EpochTicks;
+ Assert.AreEqual(expected, utc.ToUnixUtcTime());
+ }
+
+ /// Pins the Local-branch conversion of ; identical semantics to ToUnixTime for Local inputs.
+ [Test]
+ public void ToUnixUtcTime_LocalKind_ConvertsToUtcBeforeSubtracting()
+ {
+ var local = new DateTime(2026, 6, 15, 10, 20, 30, DateTimeKind.Local);
+ var expected = TimeZoneInfo.ConvertTimeToUtc(local, TimeZoneInfo.Local).Ticks - UnixTimeExtensions.EpochTicks;
+ Assert.AreEqual(expected, local.ToUnixUtcTime());
+ }
+
+ /// Pins the default Unspecified path: with no unspecifiedAssume argument, the value is treated as Utc and returned unchanged.
+ [Test]
+ public void ToUnixUtcTime_UnspecifiedKind_DefaultsToUtcAssumption()
+ {
+ var unspec = new DateTime(2026, 6, 15, 10, 20, 30, DateTimeKind.Unspecified);
+ var expected = unspec.Ticks - UnixTimeExtensions.EpochTicks;
+ Assert.AreEqual(expected, unspec.ToUnixUtcTime());
+ }
+
+ /// Pins the explicit-Utc unspecifiedAssume path: identical to the default.
+ [Test]
+ public void ToUnixUtcTime_UnspecifiedKind_ExplicitUtcAssumption_ReturnsTicksMinusEpoch()
+ {
+ var unspec = new DateTime(2026, 6, 15, 10, 20, 30, DateTimeKind.Unspecified);
+ var expected = unspec.Ticks - UnixTimeExtensions.EpochTicks;
+ Assert.AreEqual(expected, unspec.ToUnixUtcTime(DateTimeKind.Utc));
+ }
+
+ /// Pins the Local unspecifiedAssume path: an Unspecified value is stamped Local, then converted to Utc via the machine timezone. Assertion holds in every timezone.
+ [Test]
+ public void ToUnixUtcTime_UnspecifiedKind_LocalAssumption_ConvertsAsLocal()
+ {
+ var unspec = new DateTime(2026, 6, 15, 10, 20, 30, DateTimeKind.Unspecified);
+ var asLocal = DateTime.SpecifyKind(unspec, DateTimeKind.Local);
+ var expected = TimeZoneInfo.ConvertTimeToUtc(asLocal, TimeZoneInfo.Local).Ticks - UnixTimeExtensions.EpochTicks;
+ Assert.AreEqual(expected, unspec.ToUnixUtcTime(DateTimeKind.Local));
+ }
+
+ /// Pins the Unspecified/unspecifiedAssume=Unspecified degenerate path: the SpecifyKind result is Unspecified so the inner Local check is false and the ticks pass through verbatim (identical to the Utc default).
+ [Test]
+ public void ToUnixUtcTime_UnspecifiedKind_UnspecifiedAssumption_PassesTicksThrough()
+ {
+ var unspec = new DateTime(2026, 6, 15, 10, 20, 30, DateTimeKind.Unspecified);
+ var expected = unspec.Ticks - UnixTimeExtensions.EpochTicks;
+ Assert.AreEqual(expected, unspec.ToUnixUtcTime(DateTimeKind.Unspecified));
+ }
+
+ /// Pins the ToUnixUTCTime alias: identical result to for every Kind + assumption combination.
+ [Test]
+ public void ToUnixUTCTime_Alias_MatchesToUnixUtcTime()
+ {
+ var utc = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+ var local = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Local);
+ var unspec = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Unspecified);
+
+ Assert.AreEqual(utc.ToUnixUtcTime(), utc.ToUnixUTCTime());
+ Assert.AreEqual(local.ToUnixUtcTime(), local.ToUnixUTCTime());
+ Assert.AreEqual(unspec.ToUnixUtcTime(), unspec.ToUnixUTCTime());
+ Assert.AreEqual(unspec.ToUnixUtcTime(DateTimeKind.Local), unspec.ToUnixUTCTime(DateTimeKind.Local));
+ Assert.AreEqual(unspec.ToUnixUtcTime(DateTimeKind.Utc), unspec.ToUnixUTCTime(DateTimeKind.Utc));
+ }
+
+ // --- FromUnixTime / ToDateTime / ToLocalDateTime ------------------------
+
+ /// Pins the FromUnixTime(0) → epoch mapping; kills a constant-substitution mutant on the AddTicks argument.
+ [Test]
+ public void FromUnixTime_Zero_ReturnsEpochUtc()
+ {
+ var d = UnixTimeExtensions.FromUnixTime(0L);
+ Assert.AreEqual(UnixTimeExtensions.EpochTime, d);
+ Assert.AreEqual(DateTimeKind.Utc, d.Kind);
+ }
+
+ /// Pins the FromUnixTime arithmetic on a known-positive tick count.
+ [Test]
+ public void FromUnixTime_PositiveTicks_AddsToEpoch()
+ {
+ var ticks = 12345678901234L;
+ var d = UnixTimeExtensions.FromUnixTime(ticks);
+ Assert.AreEqual(UnixTimeExtensions.EpochTime.AddTicks(ticks), d);
+ Assert.AreEqual(UnixTimeExtensions.EpochTicks + ticks, d.Ticks);
+ Assert.AreEqual(DateTimeKind.Utc, d.Kind);
+ }
+
+ /// Pins the FromUnixTime arithmetic on a negative tick count (pre-epoch instant).
+ [Test]
+ public void FromUnixTime_NegativeTicks_SubtractsFromEpoch()
+ {
+ var d = UnixTimeExtensions.FromUnixTime(-1L);
+ Assert.AreEqual(new DateTime(UnixTimeExtensions.EpochTicks - 1, DateTimeKind.Utc), d);
+ Assert.AreEqual(DateTimeKind.Utc, d.Kind);
+ }
+
+ /// Pins the ToDateTime alias: identical result to .
+ [Test]
+ public void ToDateTime_LongExtension_MatchesFromUnixTime()
+ {
+ long[] samples = { 0L, 1L, -1L, 621355968000000000L, -621355968000000000L, 12345678901234L };
+ foreach (var ticks in samples)
+ {
+ Assert.AreEqual(UnixTimeExtensions.FromUnixTime(ticks), ticks.ToDateTime());
+ }
+ }
+
+ /// Pins the ToLocalDateTime conversion: the returned instant equals FromUnixTime(ticks).ToLocalTime() and has Kind=Local.
+ [Test]
+ public void ToLocalDateTime_LongExtension_ReturnsLocalKindAndCorrectInstant()
+ {
+ long[] samples = { 0L, 12345678901234L };
+ foreach (var ticks in samples)
+ {
+ var actual = ticks.ToLocalDateTime();
+ var expected = UnixTimeExtensions.FromUnixTime(ticks).ToLocalTime();
+ Assert.AreEqual(expected, actual);
+ Assert.AreEqual(DateTimeKind.Local, actual.Kind);
+ }
+ }
+
+ // --- Round-trip invariants --------------------------------------------
+
+ /// Pins ToUnixTime ∘ FromUnixTime = identity on Utc DateTimes across a spread of instants including epoch, pre-epoch, and MTConnect's contemporary window.
+ [Test]
+ public void RoundTrip_UtcDateTime_ToUnixTime_ThenFromUnixTime_IsIdentity()
+ {
+ DateTime[] samples =
+ {
+ UnixTimeExtensions.EpochTime,
+ new DateTime(1969, 12, 31, 23, 59, 59, DateTimeKind.Utc),
+ new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc),
+ new DateTime(2026, 6, 15, 12, 30, 45, DateTimeKind.Utc),
+ new DateTime(2099, 12, 31, 23, 59, 59, DateTimeKind.Utc)
+ };
+ foreach (var d in samples)
+ {
+ var round = UnixTimeExtensions.FromUnixTime(d.ToUnixTime());
+ Assert.AreEqual(d, round);
+ Assert.AreEqual(d.Kind, round.Kind);
+ }
+ }
+
+ /// Pins ToUnixTime ∘ FromUnixTime = identity on Unspecified DateTimes (treated as Utc): round-trip preserves the tick value, though Kind normalises to Utc on the return.
+ [Test]
+ public void RoundTrip_UnspecifiedDateTime_ToUnixTime_ThenFromUnixTime_PreservesTicks()
+ {
+ var unspec = new DateTime(2026, 6, 15, 12, 30, 45, DateTimeKind.Unspecified);
+ var round = UnixTimeExtensions.FromUnixTime(unspec.ToUnixTime());
+ Assert.AreEqual(unspec.Ticks, round.Ticks);
+ Assert.AreEqual(DateTimeKind.Utc, round.Kind);
+ }
+
+ // --- UnixDateTime.Now --------------------------------------------------
+
+ /// Pins to a bounded window around the harness-observed BCL UtcNow; kills constant-return mutants (Now → 0, Now → 1, etc.) and off-by-epoch mutants.
+ [Test]
+ public void Now_ReturnsCurrentUtcInstantWithinTwoSeconds()
+ {
+ var before = DateTime.UtcNow.ToUnixTime();
+ var now = UnixDateTime.Now;
+ var after = DateTime.UtcNow.ToUnixTime();
+
+ // Two-second window in each direction absorbs GC / scheduler stalls without ever
+ // spanning the epoch offset a mutation could introduce (epoch offset ~ 6e17 ticks).
+ var twoSecondsInTicks = TimeSpan.FromSeconds(2).Ticks;
+ Assert.That(now, Is.GreaterThanOrEqualTo(before - twoSecondsInTicks));
+ Assert.That(now, Is.LessThanOrEqualTo(after + twoSecondsInTicks));
+ }
+
+ /// Pins that is monotone non-decreasing between two consecutive reads on the same thread.
+ [Test]
+ public void Now_TwoConsecutiveReads_AreMonotoneNonDecreasing()
+ {
+ var first = UnixDateTime.Now;
+ var second = UnixDateTime.Now;
+ Assert.That(second, Is.GreaterThanOrEqualTo(first));
+ }
+ }
+}