feat(s7): DateTime (S7 DATE_AND_TIME) scalar read+write via S7.Net.Types.DateTime

This commit is contained in:
Joseph Doherty
2026-06-17 05:54:08 -04:00
parent 1e5fec2f85
commit 5db08e9e85
2 changed files with 79 additions and 15 deletions
@@ -590,7 +590,7 @@ public sealed class S7Driver
internal static int ScalarByteWidth(S7TagDefinition tag) => tag.DataType switch
{
S7DataType.Int64 or S7DataType.UInt64 or S7DataType.Float64 => 8,
S7DataType.DateTime => 8,
S7DataType.DateTime => 8, // 8 = S7 DATE_AND_TIME/DT BCD (S7.Net.Types.DateTime; the 8-byte DT, not 12-byte DTL).
S7DataType.String => tag.StringLength + 2,
_ => throw new InvalidOperationException(
$"S7 ScalarByteWidth called for non-buffer type {tag.DataType} (tag '{tag.Name}')"),
@@ -623,8 +623,11 @@ public sealed class S7Driver
// padding past curLen — it validates curLen against the block, so no extra guard is needed.
S7DataType.String => global::S7.Net.Types.S7String.FromByteArray(block),
S7DataType.DateTime => throw new NotSupportedException(
"S7 DateTime scalar reads land in a follow-up PR"),
// S7 classic DATE_AND_TIME (DT): 8-byte BCD value (year/month/day/hour/min/sec + ms +
// day-of-week nibble). S7.Net's DateTime.FromByteArray reads the BCD fields and returns a
// System.DateTime; the DT range is 19902089 and round-trips to the millisecond. Boxed as
// System.DateTime explicitly (the cast pins the box type, consistent with the numeric arms).
S7DataType.DateTime => (object)global::S7.Net.Types.DateTime.FromByteArray(block),
_ => throw new System.IO.InvalidDataException(
$"S7 scalar Read type-mismatch: tag '{tag.Name}' declared {tag.DataType} but address " +
@@ -673,7 +676,11 @@ public sealed class S7Driver
return global::S7.Net.Types.S7String.ToByteArray(Convert.ToString(value) ?? "", tag.StringLength);
case S7DataType.DateTime:
throw new NotSupportedException("S7 DateTime scalar writes land in a follow-up PR");
// S7.Net's DateTime.ToByteArray builds the 8-byte DT BCD block. It validates the
// 19902089 DT range and throws ArgumentOutOfRangeException for an out-of-range year —
// we surface that (no silent clamp). Value coerced via Convert.ToDateTime (accepts a
// System.DateTime or a parseable timestamp string).
return global::S7.Net.Types.DateTime.ToByteArray(Convert.ToDateTime(value));
default:
throw new InvalidOperationException(