diff --git a/S7.Net.UnitTest/S7.Net.UnitTest.csproj b/S7.Net.UnitTest/S7.Net.UnitTest.csproj
index 0a14d88..62d54d0 100644
--- a/S7.Net.UnitTest/S7.Net.UnitTest.csproj
+++ b/S7.Net.UnitTest/S7.Net.UnitTest.csproj
@@ -81,6 +81,7 @@
+
diff --git a/S7.Net.UnitTest/S7NetTestsSync.cs b/S7.Net.UnitTest/S7NetTestsSync.cs
index c096643..3b7589b 100644
--- a/S7.Net.UnitTest/S7NetTestsSync.cs
+++ b/S7.Net.UnitTest/S7NetTestsSync.cs
@@ -997,6 +997,20 @@ namespace S7.Net.UnitTest
Assert.AreEqual(test_value, test_value2, "Compare Write/Read"); //No delta, datatype matches
}
+ [TestMethod]
+ public void T33_ReadWriteDateTimeLong()
+ {
+ var test_value = System.DateTime.Now;
+ var db = 1;
+ var offset = 0;
+
+ plc.WriteBytes(DataType.DataBlock, db, offset, Types.DateTimeLong.ToByteArray(test_value));
+ var test_value2 = plc.Read(DataType.DataBlock, db, offset, VarType.DateTimeLong, 1);
+ Assert.IsInstanceOfType(test_value2, typeof(System.DateTime));
+
+ Assert.AreEqual(test_value, test_value2, "Compare DateTimeLong Write/Read");
+ }
+
#endregion
#region Private methods
diff --git a/S7.Net.UnitTest/TypeTests/DateTimeLongTests.cs b/S7.Net.UnitTest/TypeTests/DateTimeLongTests.cs
new file mode 100644
index 0000000..2ef8fb2
--- /dev/null
+++ b/S7.Net.UnitTest/TypeTests/DateTimeLongTests.cs
@@ -0,0 +1,171 @@
+using System;
+using System.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace S7.Net.UnitTest.TypeTests
+{
+ public static class DateTimeLongTests
+ {
+ private static readonly DateTime SampleDateTime = new DateTime(1993, 12, 25, 8, 12, 34, 567);
+
+ private static readonly byte[] SampleByteArray = {0x07, 0xC9, 0x0C, 0x19, 0x07, 0x08, 0x0C, 0x22, 0x21, 0xCB, 0xBB, 0xC0 };
+
+ private static readonly byte[] SpecMinByteArray =
+ {
+ 0x07, 0xB2, 0x01, 0x01, (byte) (int) (Types.DateTimeLong.SpecMinimumDateTime.DayOfWeek + 1), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private static readonly byte[] SpecMaxByteArray =
+ {
+ 0x08, 0xD6, 0x04, 0x0B, (byte) (int) (Types.DateTimeLong.SpecMaximumDateTime.DayOfWeek + 1), 0x17, 0x2F, 0x10, 0x32, 0xE7, 0x01, 0x80
+ };
+
+ [TestClass]
+ public class FromByteArray
+ {
+ [TestMethod]
+ public void Sample()
+ {
+ AssertFromByteArrayEquals(SampleDateTime, SampleByteArray);
+ }
+
+ [TestMethod]
+ public void SpecMinimum()
+ {
+ AssertFromByteArrayEquals(Types.DateTimeLong.SpecMinimumDateTime, SpecMinByteArray);
+ }
+
+ [TestMethod]
+ public void SpecMaximum()
+ {
+ AssertFromByteArrayEquals(Types.DateTimeLong.SpecMaximumDateTime, SpecMaxByteArray);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void ThrowsOnLessThan12Bytes()
+ {
+ Types.DateTimeLong.FromByteArray(new byte[11]);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void ThrowsOnMoreTHan12Bytes()
+ {
+ Types.DateTimeLong.FromByteArray(new byte[13]);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void ThrowsOnInvalidYear()
+ {
+ Types.DateTimeLong.FromByteArray(MutateSample(0, 0xa0));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void ThrowsOnZeroMonth()
+ {
+ Types.DateTimeLong.FromByteArray(MutateSample(2, 0x00));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void ThrowsOnTooLargeMonth()
+ {
+ Types.DateTimeLong.FromByteArray(MutateSample(2, 0x13));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void ThrowsOnZeroDay()
+ {
+ Types.DateTimeLong.FromByteArray(MutateSample(3, 0x00));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void ThrowsOnTooLargeDay()
+ {
+ Types.DateTimeLong.FromByteArray(MutateSample(3, 0x32));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void ThrowsOnInvalidHour()
+ {
+ Types.DateTimeLong.FromByteArray(MutateSample(5, 0x24));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void ThrowsOnInvalidMinute()
+ {
+ Types.DateTimeLong.FromByteArray(MutateSample(6, 0x60));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void ThrowsOnInvalidSecond()
+ {
+ Types.DateTimeLong.FromByteArray(MutateSample(7, 0x60));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void ThrowsOnInvalidNanosecondsFirstDigit()
+ {
+ Types.DateTimeLong.FromByteArray(MutateSample(8, 0x3B));
+ }
+
+
+ [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void ThrowsOnZeroDayOfWeek()
+ {
+ Types.DateTimeLong.FromByteArray(MutateSample(4, 0));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void ThrowsOnTooLargeDayOfWeek()
+ {
+ Types.DateTimeLong.FromByteArray(MutateSample(4, 8));
+ }
+
+ private static void AssertFromByteArrayEquals(DateTime expected, params byte[] bytes)
+ {
+ Assert.AreEqual(expected, Types.DateTimeLong.FromByteArray(bytes));
+ }
+
+ private static byte[] MutateSample(int index, byte value) =>
+ SampleByteArray.Select((b, i) => i == index ? value : b).ToArray();
+ }
+
+ [TestClass]
+ public class ToByteArray
+ {
+ [TestMethod]
+ public void Sample()
+ {
+ AssertToByteArrayEquals(SampleDateTime, SampleByteArray);
+ }
+
+ [TestMethod]
+ public void SpecMinimum()
+ {
+ AssertToByteArrayEquals(Types.DateTimeLong.SpecMinimumDateTime, SpecMinByteArray);
+ }
+
+ [TestMethod]
+ public void SpecMaximum()
+ {
+ AssertToByteArrayEquals(Types.DateTimeLong.SpecMaximumDateTime, SpecMaxByteArray);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void ThrowsOnTimeBeforeSpecMinimum()
+ {
+ Types.DateTimeLong.ToByteArray(new DateTime(1950, 1, 1));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void ThrowsOnTimeAfterSpecMaximum()
+ {
+ Types.DateTimeLong.ToByteArray(new DateTime(2790, 1, 1));
+ }
+
+ private static void AssertToByteArrayEquals(DateTime value, params byte[] expected)
+ {
+ CollectionAssert.AreEqual(expected, Types.DateTimeLong.ToByteArray(value));
+ }
+ }
+ }
+}
diff --git a/S7.Net/Enums.cs b/S7.Net/Enums.cs
index f4b4778..6082d28 100644
--- a/S7.Net/Enums.cs
+++ b/S7.Net/Enums.cs
@@ -186,6 +186,11 @@
///
/// DateTIme variable type
///
- DateTime
+ DateTime,
+
+ ///
+ /// DateTimeLong variable type
+ ///
+ DateTimeLong
}
}
diff --git a/S7.Net/PLCHelpers.cs b/S7.Net/PLCHelpers.cs
index 2ea37cd..602c6a6 100644
--- a/S7.Net/PLCHelpers.cs
+++ b/S7.Net/PLCHelpers.cs
@@ -156,6 +156,15 @@ namespace S7.Net
{
return DateTime.ToArray(bytes);
}
+ case VarType.DateTimeLong:
+ if (varCount == 1)
+ {
+ return DateTimeLong.FromByteArray(bytes);
+ }
+ else
+ {
+ return DateTimeLong.ToArray(bytes);
+ }
default:
return null;
}
@@ -190,6 +199,8 @@ namespace S7.Net
return varCount * 4;
case VarType.DateTime:
return varCount * 8;
+ case VarType.DateTimeLong:
+ return varCount * 12;
default:
return 0;
}
diff --git a/S7.Net/Protocol/Serialization.cs b/S7.Net/Protocol/Serialization.cs
index 613c9f4..66142c2 100644
--- a/S7.Net/Protocol/Serialization.cs
+++ b/S7.Net/Protocol/Serialization.cs
@@ -64,6 +64,8 @@ namespace S7.Net.Protocol
return Types.String.ToByteArray(stringVal, stringVal.Length);
case "DateTime[]":
return Types.DateTime.ToByteArray((System.DateTime[]) value);
+ case "DateTimeLong[]":
+ return Types.DateTimeLong.ToByteArray((System.DateTime[])value);
default:
throw new InvalidVariableTypeException();
}
diff --git a/S7.Net/Types/DateTimeLong.cs b/S7.Net/Types/DateTimeLong.cs
new file mode 100644
index 0000000..6479f6f
--- /dev/null
+++ b/S7.Net/Types/DateTimeLong.cs
@@ -0,0 +1,185 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace S7.Net.Types
+{
+ ///
+ /// Contains the methods to convert between and S7 representation of DateTimeLong (DTL) values.
+ ///
+ public static class DateTimeLong
+ {
+ public const int TypeLengthInBytes = 12;
+ ///
+ /// The minimum value supported by the specification.
+ ///
+ public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1970, 1, 1);
+
+ ///
+ /// The maximum value supported by the specification.
+ ///
+ public static readonly System.DateTime SpecMaximumDateTime = new System.DateTime(2262, 4, 11, 23, 47, 16, 854);
+
+ ///
+ /// Parses a value from bytes.
+ ///
+ /// Input bytes read from PLC.
+ /// A object representing the value read from PLC.
+ ///
+ /// Thrown when the length of
+ /// is not 12 or any value in
+ /// is outside the valid range of values.
+ ///
+ public static System.DateTime FromByteArray(byte[] bytes)
+ {
+ return FromByteArrayImpl(bytes);
+ }
+
+ ///
+ /// Parses an array of values from bytes.
+ ///
+ /// Input bytes read from PLC.
+ /// An array of objects representing the values read from PLC.
+ ///
+ /// Thrown when the length of
+ /// is not a multiple of 12 or any value in
+ /// is outside the valid range of values.
+ ///
+ public static System.DateTime[] ToArray(byte[] bytes)
+ {
+ if (bytes.Length % TypeLengthInBytes != 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length,
+ $"Parsing an array of DateTimeLong requires a multiple of 12 bytes of input data, input data is '{bytes.Length}' long.");
+ }
+
+ var cnt = bytes.Length / TypeLengthInBytes;
+ var result = new System.DateTime[cnt];
+
+ for (var i = 0; i < cnt; i++)
+ {
+ var slice = new byte[TypeLengthInBytes];
+ Array.Copy(bytes, i * TypeLengthInBytes, slice, 0, TypeLengthInBytes);
+ result[i] = FromByteArrayImpl(slice);
+ }
+
+ return result;
+ }
+
+ private static System.DateTime FromByteArrayImpl(byte[] bytes)
+ {
+ if (bytes.Length != TypeLengthInBytes)
+ {
+ throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length,
+ $"Parsing a DateTimeLong requires exactly 12 bytes of input data, input data is {bytes.Length} bytes long.");
+ }
+
+
+ var year = AssertRangeInclusive(Word.FromBytes(bytes[1], bytes[0]), 1970, 2262, "year");
+ var month = AssertRangeInclusive(bytes[2], 1, 12, "month");
+ var day = AssertRangeInclusive(bytes[3], 1, 31, "day of month");
+ var dayOfWeek = AssertRangeInclusive(bytes[4], 1, 7, "day of week");
+ var hour = AssertRangeInclusive(bytes[5], 0, 23, "hour");
+ var minute = AssertRangeInclusive(bytes[6], 0, 59, "minute");
+ var second = AssertRangeInclusive(bytes[7], 0, 59, "second");
+ ;
+
+ var nanoseconds = AssertRangeInclusive(DWord.FromBytes(bytes[11], bytes[10], bytes[9], bytes[8]), 0,
+ 999999999, "nanoseconds");
+
+ var time = new System.DateTime(year, month, day, hour, minute, second);
+ return time.AddTicks(nanoseconds / 100);
+ }
+
+ ///
+ /// Converts a value to a byte array.
+ ///
+ /// The DateTime value to convert.
+ /// A byte array containing the S7 DateTimeLong representation of .
+ ///
+ /// Thrown when the value of
+ /// is before
+ /// or after .
+ ///
+ public static byte[] ToByteArray(System.DateTime dateTime)
+ {
+ if (dateTime < SpecMinimumDateTime)
+ {
+ throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
+ $"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 DateTimeLong representation.");
+ }
+
+ if (dateTime > SpecMaximumDateTime)
+ {
+ throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
+ $"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 DateTimeLong representation.");
+ }
+
+ var stream = new MemoryStream(TypeLengthInBytes);
+ // Convert Year
+ stream.Write(Word.ToByteArray(Convert.ToUInt16(dateTime.Year)), 0, 2);
+
+ // Convert Month
+ stream.WriteByte(Convert.ToByte(dateTime.Month));
+
+ // Convert Day
+ stream.WriteByte(Convert.ToByte(dateTime.Day));
+
+ // Convert WeekDay. NET DateTime starts with Sunday = 0, while S7DT has Sunday = 1.
+ stream.WriteByte(Convert.ToByte(dateTime.DayOfWeek + 1));
+
+ // Convert Hour
+ stream.WriteByte(Convert.ToByte(dateTime.Hour));
+
+ // Convert Minutes
+ stream.WriteByte(Convert.ToByte(dateTime.Minute));
+
+ // Convert Seconds
+ stream.WriteByte(Convert.ToByte(dateTime.Second));
+
+ // Convert Nanoseconds. Net DateTime has a representation of 1 Tick = 100ns.
+ // Thus First take the ticks Mod 1 Second (1s = 10'000'000 ticks), and then Convert to nanoseconds.
+ stream.Write(DWord.ToByteArray(Convert.ToUInt32(dateTime.Ticks % 10000000 * 100)), 0, 4);
+
+ return stream.ToArray();
+ }
+
+ ///
+ /// Converts an array of values to a byte array.
+ ///
+ /// The DateTime values to convert.
+ /// A byte array containing the S7 DateTimeLong representations of .
+ ///
+ /// Thrown when any value of
+ /// is before
+ /// or after .
+ ///
+ public static byte[] ToByteArray(System.DateTime[] dateTimes)
+ {
+ var bytes = new List(dateTimes.Length * TypeLengthInBytes);
+ foreach (var dateTime in dateTimes)
+ {
+ bytes.AddRange(ToByteArray(dateTime));
+ }
+
+ return bytes.ToArray();
+ }
+
+ private static T AssertRangeInclusive(T input, T min, T max, string field) where T : IComparable
+ {
+ if (input.CompareTo(min) < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(input), input,
+ $"Value '{input}' is lower than the minimum '{min}' allowed for {field}.");
+ }
+
+ if (input.CompareTo(max) > 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(input), input,
+ $"Value '{input}' is higher than the maximum '{max}' allowed for {field}.");
+ }
+
+ return input;
+ }
+ }
+}
\ No newline at end of file