05ed039c73
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
84 lines
3.5 KiB
C#
84 lines
3.5 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests;
|
|
|
|
/// <summary>#306 — TIME / DATE / DT / TOD decode and encode round trips on known values.</summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class TwinCATTemporalCodecTests
|
|
{
|
|
private static readonly DateTime Dt = new(2026, 10, 8, 12, 0, 0, DateTimeKind.Utc);
|
|
|
|
[Fact]
|
|
public void Dt_decodes_seconds_since_1970_to_utc()
|
|
{
|
|
var raw = (uint)(Dt - DateTime.UnixEpoch).TotalSeconds;
|
|
var v = (DateTime)TwinCATTemporalCodec.Decode(TwinCATDataType.DateTime, raw);
|
|
v.ShouldBe(Dt);
|
|
v.Kind.ShouldBe(DateTimeKind.Utc);
|
|
TwinCATTemporalCodec.Encode(TwinCATDataType.DateTime, v).ShouldBe(raw);
|
|
}
|
|
|
|
[Fact]
|
|
public void Date_round_trips_and_truncates_to_day()
|
|
{
|
|
var day = new DateTime(2026, 10, 8, 0, 0, 0, DateTimeKind.Utc);
|
|
var raw = TwinCATTemporalCodec.Encode(TwinCATDataType.Date, day);
|
|
((DateTime)TwinCATTemporalCodec.Decode(TwinCATDataType.Date, raw)).ShouldBe(day);
|
|
TwinCATTemporalCodec.Encode(TwinCATDataType.Date, day.AddHours(13)).ShouldBe(raw);
|
|
}
|
|
|
|
[Fact]
|
|
public void Tod_round_trips_milliseconds()
|
|
{
|
|
const uint ms = (12 * 3600 + 30 * 60 + 15) * 1000 + 250; // TOD#12:30:15.250
|
|
TwinCATTemporalCodec.Decode(TwinCATDataType.TimeOfDay, ms).ShouldBe(45_015_250d);
|
|
TwinCATTemporalCodec.Encode(TwinCATDataType.TimeOfDay, 45_015_250d).ShouldBe(ms);
|
|
}
|
|
|
|
[Fact]
|
|
public void Time_round_trips_milliseconds_and_accepts_timespan()
|
|
{
|
|
const uint ms = ((1 * 60 + 2) * 60 + 3) * 1000 + 4; // T#1h2m3s4ms
|
|
TwinCATTemporalCodec.Decode(TwinCATDataType.Time, ms).ShouldBe(3_723_004d);
|
|
TwinCATTemporalCodec.Encode(TwinCATDataType.Time, 3_723_004d).ShouldBe(ms);
|
|
TwinCATTemporalCodec.Encode(TwinCATDataType.Time, new TimeSpan(0, 1, 2, 3, 4)).ShouldBe(ms);
|
|
TwinCATTemporalCodec.Encode(TwinCATDataType.Time, 5u).ShouldBe(5u); // raw integer still writes
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(-1d)]
|
|
[InlineData(double.NaN)]
|
|
[InlineData(5_000_000_000d)]
|
|
public void Time_encode_refuses_out_of_range(double value)
|
|
=> Should.Throw<OverflowException>(() => TwinCATTemporalCodec.Encode(TwinCATDataType.Time, value));
|
|
|
|
[Theory]
|
|
[InlineData(86_400_000d)]
|
|
[InlineData(-1d)]
|
|
public void Tod_encode_refuses_a_full_day_or_more(double value)
|
|
=> Should.Throw<OverflowException>(() => TwinCATTemporalCodec.Encode(TwinCATDataType.TimeOfDay, value));
|
|
|
|
[Fact]
|
|
public void Dt_before_1970_is_refused()
|
|
=> Should.Throw<OverflowException>(() =>
|
|
TwinCATTemporalCodec.Encode(TwinCATDataType.DateTime, new DateTime(1960, 1, 1, 0, 0, 0, DateTimeKind.Utc)));
|
|
|
|
[Fact]
|
|
public void Wrong_shape_is_a_type_mismatch()
|
|
{
|
|
Should.Throw<InvalidCastException>(() => TwinCATTemporalCodec.Encode(TwinCATDataType.Time, Dt));
|
|
Should.Throw<InvalidCastException>(() => TwinCATTemporalCodec.Encode(TwinCATDataType.DateTime, TimeSpan.FromSeconds(1)));
|
|
}
|
|
|
|
[Fact]
|
|
public void Client_write_conversion_routes_temporal_types_through_the_codec()
|
|
{
|
|
AdsTwinCATClient.ConvertForWrite(TwinCATDataType.Time, 1000d).ShouldBe(1000u);
|
|
AdsTwinCATClient.ConvertForWrite(TwinCATDataType.DateTime, Dt)
|
|
.ShouldBe((uint)(Dt - DateTime.UnixEpoch).TotalSeconds);
|
|
// Authored UDInt against a TIME symbol keeps the raw counter.
|
|
AdsTwinCATClient.ConvertForWrite(TwinCATDataType.UDInt, 1000u).ShouldBe(1000u);
|
|
}
|
|
}
|