feat(s7): DateTime (S7 DATE_AND_TIME) scalar read+write via S7.Net.Types.DateTime
This commit is contained in:
@@ -12,8 +12,10 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Tests;
|
||||
/// network I/O half (<c>Plc.ReadBytesAsync</c>/<c>WriteBytesAsync</c>) has no in-process
|
||||
/// fake so only the codec is unit-proven (mirrors <see cref="S7ArrayReadTests"/>).
|
||||
/// String (S7 classic STRING) decode/encode is proven here via
|
||||
/// <see cref="S7.Net.Types.S7String"/>; DateTime decode is still a deferred stub (T4) and
|
||||
/// this file pins the NotSupportedException contract it lands against.
|
||||
/// <see cref="S7.Net.Types.S7String"/>; DateTime (S7 classic DATE_AND_TIME / DT, 8-byte BCD)
|
||||
/// decode/encode is proven via <see cref="S7.Net.Types.DateTime"/> (the 8-byte DT helper —
|
||||
/// NOT DTL). Timer/Counter decode/encode are still deferred stubs (T5) and this file pins the
|
||||
/// NotSupportedException contract they land against.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class S7ScalarBlockTests
|
||||
@@ -296,19 +298,74 @@ public sealed class S7ScalarBlockTests
|
||||
decoded.ShouldBeOfType<string>().ShouldBe("ABCDE");
|
||||
}
|
||||
|
||||
// ── Deferred-stub contract: DateTime throws NotSupportedException ──────────────────────
|
||||
// ── DecodeScalarBlock — DateTime (S7 classic DATE_AND_TIME / DT) ───────────────────────
|
||||
|
||||
/// <summary>Verifies DateTime decode is a deferred stub (NotSupportedException — T4).</summary>
|
||||
/// <summary>Verifies an 8-byte S7 DT block decodes to its <see cref="System.DateTime"/> value
|
||||
/// via <see cref="S7.Net.Types.DateTime"/> (the 8-byte BCD DT helper, not the 12-byte DTL).</summary>
|
||||
[Fact]
|
||||
public void DecodeScalarBlock_DateTime_throws_NotSupported()
|
||||
public void DecodeScalarBlock_DateTime_reads_dt_bcd_block()
|
||||
{
|
||||
var tag = Tag(S7DataType.DateTime);
|
||||
var block = new byte[S7Driver.ScalarByteWidth(tag)];
|
||||
Should.Throw<NotSupportedException>(() => S7Driver.DecodeScalarBlock(tag, Addr(), block));
|
||||
var expected = new System.DateTime(2026, 6, 17, 12, 34, 56);
|
||||
// Fixture built by S7.Net's own DT encoder so the block matches the on-the-wire DT layout.
|
||||
var block = global::S7.Net.Types.DateTime.ToByteArray(expected);
|
||||
block.Length.ShouldBe(8);
|
||||
|
||||
var result = S7Driver.DecodeScalarBlock(Tag(S7DataType.DateTime), Addr(), block);
|
||||
result.ShouldBeOfType<System.DateTime>().ShouldBe(expected);
|
||||
}
|
||||
|
||||
/// <summary>Verifies DateTime encode is a deferred stub (NotSupportedException — T4).</summary>
|
||||
// ── EncodeScalarBlock — DateTime ───────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Verifies a <see cref="System.DateTime"/> encodes to an 8-byte DT block.</summary>
|
||||
[Fact]
|
||||
public void EncodeScalarBlock_DateTime_throws_NotSupported()
|
||||
=> Should.Throw<NotSupportedException>(() => S7Driver.EncodeScalarBlock(Tag(S7DataType.DateTime), "x"));
|
||||
public void EncodeScalarBlock_DateTime_writes_eight_byte_block()
|
||||
{
|
||||
var value = new System.DateTime(2026, 6, 17, 12, 34, 56);
|
||||
var block = S7Driver.EncodeScalarBlock(Tag(S7DataType.DateTime), value);
|
||||
block.Length.ShouldBe(8);
|
||||
// Matches S7.Net's own DT encoder exactly (same on-the-wire bytes).
|
||||
block.ShouldBe(global::S7.Net.Types.DateTime.ToByteArray(value));
|
||||
}
|
||||
|
||||
/// <summary>Verifies a string/ISO timestamp coerces via <c>Convert.ToDateTime</c> before encoding.</summary>
|
||||
[Fact]
|
||||
public void EncodeScalarBlock_DateTime_coerces_string_value()
|
||||
{
|
||||
var tag = Tag(S7DataType.DateTime);
|
||||
var block = S7Driver.EncodeScalarBlock(tag, "2026-06-17T12:34:56");
|
||||
|
||||
var decoded = S7Driver.DecodeScalarBlock(tag, Addr(), block);
|
||||
decoded.ShouldBeOfType<System.DateTime>().ShouldBe(new System.DateTime(2026, 6, 17, 12, 34, 56));
|
||||
}
|
||||
|
||||
/// <summary>Verifies a year outside the S7 DT range (1990–2089) throws — S7.Net's DT encoder
|
||||
/// validates the range and raises <see cref="ArgumentOutOfRangeException"/>; we surface it.</summary>
|
||||
[Theory]
|
||||
[InlineData(1980)]
|
||||
[InlineData(2100)]
|
||||
public void EncodeScalarBlock_DateTime_out_of_range_year_throws(int year)
|
||||
{
|
||||
var tag = Tag(S7DataType.DateTime);
|
||||
var value = new System.DateTime(year, 1, 1, 0, 0, 0);
|
||||
Should.Throw<ArgumentOutOfRangeException>(() => S7Driver.EncodeScalarBlock(tag, value));
|
||||
}
|
||||
|
||||
// ── DateTime round-trip identity (encode → decode) ────────────────────────────────────
|
||||
|
||||
/// <summary>Verifies DateTime round-trips through encode→decode. S7 DT preserves full
|
||||
/// millisecond precision (the 8-byte BCD packs ms-tens/hundreds), so the identity holds to
|
||||
/// the millisecond — no precision loss to document below the second.</summary>
|
||||
[Theory]
|
||||
[InlineData(1990, 1, 1, 0, 0, 0, 0)] // DT minimum.
|
||||
[InlineData(2026, 6, 17, 12, 34, 56, 0)] // no sub-second.
|
||||
[InlineData(2026, 6, 17, 12, 34, 56, 789)] // milliseconds preserved.
|
||||
[InlineData(2089, 12, 31, 23, 59, 59, 999)] // DT maximum, ms boundary.
|
||||
public void DateTime_round_trips_to_the_millisecond(
|
||||
int y, int mo, int d, int h, int mi, int s, int ms)
|
||||
{
|
||||
var value = new System.DateTime(y, mo, d, h, mi, s, ms);
|
||||
var tag = Tag(S7DataType.DateTime);
|
||||
var decoded = S7Driver.DecodeScalarBlock(tag, Addr(), S7Driver.EncodeScalarBlock(tag, value));
|
||||
decoded.ShouldBeOfType<System.DateTime>().ShouldBe(value);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user