mirror of
https://github.com/S7NetPlus/s7netplus.git
synced 2026-02-17 22:38:27 +08:00
Merge pull request #189 from mycroes/add-datetime-support
Add DateTime support
This commit is contained in:
@@ -79,6 +79,7 @@
|
||||
<Compile Include="S7NetTestsSync.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Helpers\TestLongStruct.cs" />
|
||||
<Compile Include="TypeTests\DateTimeTests.cs" />
|
||||
<Compile Include="TypeTests\StringExTests.cs" />
|
||||
<Compile Include="TypeTests\StringTests.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
176
S7.Net.UnitTest/TypeTests/DateTimeTests.cs
Normal file
176
S7.Net.UnitTest/TypeTests/DateTimeTests.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace S7.Net.UnitTest.TypeTests
|
||||
{
|
||||
public static class DateTimeTests
|
||||
{
|
||||
private static readonly DateTime SampleDateTime = new DateTime(1993, 12, 25, 8, 12, 34, 567);
|
||||
|
||||
private static readonly byte[] SampleByteArray = {0x93, 0x12, 0x25, 0x08, 0x12, 0x34, 0x56, 7 << 4 | 7};
|
||||
|
||||
private static readonly byte[] SpecMinByteArray =
|
||||
{
|
||||
0x90, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, (byte) (int) (Types.DateTime.SpecMinimumDateTime.DayOfWeek + 1)
|
||||
};
|
||||
|
||||
private static readonly byte[] SpecMaxByteArray =
|
||||
{
|
||||
0x89, 0x12, 0x31, 0x23, 0x59, 0x59, 0x99, (byte) (9 << 4 | (int) (Types.DateTime.SpecMaximumDateTime.DayOfWeek + 1))
|
||||
};
|
||||
|
||||
[TestClass]
|
||||
public class FromByteArray
|
||||
{
|
||||
[TestMethod]
|
||||
public void Sample()
|
||||
{
|
||||
AssertFromByteArrayEquals(SampleDateTime, SampleByteArray);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpecMinimum()
|
||||
{
|
||||
AssertFromByteArrayEquals(Types.DateTime.SpecMinimumDateTime, SpecMinByteArray);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpecMaximum()
|
||||
{
|
||||
AssertFromByteArrayEquals(Types.DateTime.SpecMaximumDateTime, SpecMaxByteArray);
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnLessThan8Bytes()
|
||||
{
|
||||
Types.DateTime.FromByteArray(new byte[7]);
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnMoreTHan8Bytes()
|
||||
{
|
||||
Types.DateTime.FromByteArray(new byte[9]);
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnInvalidYear()
|
||||
{
|
||||
Types.DateTime.FromByteArray(MutateSample(0, 0xa0));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnZeroMonth()
|
||||
{
|
||||
Types.DateTime.FromByteArray(MutateSample(1, 0x00));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnTooLargeMonth()
|
||||
{
|
||||
Types.DateTime.FromByteArray(MutateSample(1, 0x13));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnZeroDay()
|
||||
{
|
||||
Types.DateTime.FromByteArray(MutateSample(2, 0x00));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnTooLargeDay()
|
||||
{
|
||||
Types.DateTime.FromByteArray(MutateSample(2, 0x32));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnInvalidHour()
|
||||
{
|
||||
Types.DateTime.FromByteArray(MutateSample(3, 0x24));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnInvalidMinute()
|
||||
{
|
||||
Types.DateTime.FromByteArray(MutateSample(4, 0x60));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnInvalidSecond()
|
||||
{
|
||||
Types.DateTime.FromByteArray(MutateSample(5, 0x60));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnInvalidFirstTwoMillisecondDigits()
|
||||
{
|
||||
Types.DateTime.FromByteArray(MutateSample(6, 0xa0));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnInvalidThirdMillisecondDigit()
|
||||
{
|
||||
Types.DateTime.FromByteArray(MutateSample(7, 10 << 4));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnZeroDayOfWeek()
|
||||
{
|
||||
Types.DateTime.FromByteArray(MutateSample(7, 0));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnTooLargeDayOfWeek()
|
||||
{
|
||||
Types.DateTime.FromByteArray(MutateSample(7, 8));
|
||||
}
|
||||
|
||||
private static void AssertFromByteArrayEquals(DateTime expected, params byte[] bytes)
|
||||
{
|
||||
Assert.AreEqual(expected, Types.DateTime.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.DateTime.SpecMinimumDateTime, SpecMinByteArray);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SpecMaximum()
|
||||
{
|
||||
AssertToByteArrayEquals(Types.DateTime.SpecMaximumDateTime, SpecMaxByteArray);
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnTimeBeforeSpecMinimum()
|
||||
{
|
||||
Types.DateTime.ToByteArray(new DateTime(1970, 1, 1));
|
||||
}
|
||||
|
||||
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ThrowsOnTimeAfterSpecMaximum()
|
||||
{
|
||||
Types.DateTime.ToByteArray(new DateTime(2090, 1, 1));
|
||||
}
|
||||
|
||||
private static void AssertToByteArrayEquals(DateTime value, params byte[] expected)
|
||||
{
|
||||
CollectionAssert.AreEqual(expected, Types.DateTime.ToByteArray(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,6 +181,11 @@
|
||||
/// <summary>
|
||||
/// Counter variable type
|
||||
/// </summary>
|
||||
Counter
|
||||
Counter,
|
||||
|
||||
/// <summary>
|
||||
/// DateTIme variable type
|
||||
/// </summary>
|
||||
DateTime
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using DateTime = S7.Net.Types.DateTime;
|
||||
|
||||
namespace S7.Net
|
||||
{
|
||||
@@ -146,6 +147,15 @@ namespace S7.Net
|
||||
{
|
||||
return Bit.ToBitArray(bytes);
|
||||
}
|
||||
case VarType.DateTime:
|
||||
if (varCount == 1)
|
||||
{
|
||||
return DateTime.FromByteArray(bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
return DateTime.ToArray(bytes);
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -178,6 +188,8 @@ namespace S7.Net
|
||||
case VarType.DInt:
|
||||
case VarType.Real:
|
||||
return varCount * 4;
|
||||
case VarType.DateTime:
|
||||
return varCount * 8;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ namespace S7.Net.Protocol
|
||||
return Types.Double.ToByteArray((double)value);
|
||||
case "Single":
|
||||
return Types.Single.ToByteArray((float)value);
|
||||
case "DateTime":
|
||||
return Types.DateTime.ToByteArray((System.DateTime) value);
|
||||
case "Byte[]":
|
||||
return (byte[])value;
|
||||
case "Int16[]":
|
||||
@@ -60,6 +62,8 @@ namespace S7.Net.Protocol
|
||||
// if the consumer does not pay attention to string length.
|
||||
var stringVal = (string) value;
|
||||
return Types.String.ToByteArray(stringVal, stringVal.Length);
|
||||
case "DateTime[]":
|
||||
return Types.DateTime.ToByteArray((System.DateTime[]) value);
|
||||
default:
|
||||
throw new InvalidVariableTypeException();
|
||||
}
|
||||
|
||||
156
S7.Net/Types/DateTime.cs
Normal file
156
S7.Net/Types/DateTime.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace S7.Net.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the methods to convert between <see cref="T:System.DateTime"/> and S7 representation of datetime values.
|
||||
/// </summary>
|
||||
public static class DateTime
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum <see cref="T:System.DateTime"/> value supported by the specification.
|
||||
/// </summary>
|
||||
public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1990, 1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// The maximum <see cref="T:System.DateTime"/> value supported by the specification.
|
||||
/// </summary>
|
||||
public static readonly System.DateTime SpecMaximumDateTime = new System.DateTime(2089, 12, 31, 23, 59, 59, 999);
|
||||
|
||||
/// <summary>
|
||||
/// Parses a <see cref="T:System.DateTime"/> value from bytes.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Input bytes read from PLC.</param>
|
||||
/// <returns>A <see cref="T:System.DateTime"/> object representing the value read from PLC.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the length of
|
||||
/// <paramref name="bytes"/> is not 8 or any value in <paramref name="bytes"/>
|
||||
/// is outside the valid range of values.</exception>
|
||||
public static System.DateTime FromByteArray(byte[] bytes)
|
||||
{
|
||||
return FromByteArrayImpl(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses an array of <see cref="T:System.DateTime"/> values from bytes.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Input bytes read from PLC.</param>
|
||||
/// <returns>An array of <see cref="T:System.DateTime"/> objects representing the values read from PLC.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the length of
|
||||
/// <paramref name="bytes"/> is not a multiple of 8 or any value in
|
||||
/// <paramref name="bytes"/> is outside the valid range of values.</exception>
|
||||
public static System.DateTime[] ToArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length % 8 != 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length,
|
||||
$"Parsing an array of DateTime requires a multiple of 8 bytes of input data, input data is '{bytes.Length}' long.");
|
||||
|
||||
var cnt = bytes.Length / 8;
|
||||
var result = new System.DateTime[bytes.Length / 8];
|
||||
|
||||
for (var i = 0; i < cnt; i++)
|
||||
result[i] = FromByteArrayImpl(new ArraySegment<byte>(bytes, i * 8, 8));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static System.DateTime FromByteArrayImpl(IList<byte> bytes)
|
||||
{
|
||||
if (bytes.Count != 8)
|
||||
throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Count,
|
||||
$"Parsing a DateTime requires exactly 8 bytes of input data, input data is {bytes.Count} bytes long.");
|
||||
|
||||
int DecodeBcd(byte input) => 10 * (input >> 4) + (input & 0b00001111);
|
||||
|
||||
int ByteToYear(byte bcdYear)
|
||||
{
|
||||
var input = DecodeBcd(bcdYear);
|
||||
if (input < 90) return input + 2000;
|
||||
if (input < 100) return input + 1900;
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(bcdYear), bcdYear,
|
||||
$"Value '{input}' is higher than the maximum '99' of S7 date and time representation.");
|
||||
}
|
||||
|
||||
int AssertRangeInclusive(int input, byte min, byte max, string field)
|
||||
{
|
||||
if (input < min)
|
||||
throw new ArgumentOutOfRangeException(nameof(input), input,
|
||||
$"Value '{input}' is lower than the minimum '{min}' allowed for {field}.");
|
||||
if (input > max)
|
||||
throw new ArgumentOutOfRangeException(nameof(input), input,
|
||||
$"Value '{input}' is higher than the maximum '{max}' allowed for {field}.");
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
var year = ByteToYear(bytes[0]);
|
||||
var month = AssertRangeInclusive(DecodeBcd(bytes[1]), 1, 12, "month");
|
||||
var day = AssertRangeInclusive(DecodeBcd(bytes[2]), 1, 31, "day of month");
|
||||
var hour = AssertRangeInclusive(DecodeBcd(bytes[3]), 0, 23, "hour");
|
||||
var minute = AssertRangeInclusive(DecodeBcd(bytes[4]), 0, 59, "minute");
|
||||
var second = AssertRangeInclusive(DecodeBcd(bytes[5]), 0, 59, "second");
|
||||
var hsec = AssertRangeInclusive(DecodeBcd(bytes[6]), 0, 99, "first two millisecond digits");
|
||||
var msec = AssertRangeInclusive(bytes[7] >> 4, 0, 9, "third millisecond digit");
|
||||
var dayOfWeek = AssertRangeInclusive(bytes[7] & 0b00001111, 1, 7, "day of week");
|
||||
|
||||
return new System.DateTime(year, month, day, hour, minute, second, hsec * 10 + msec);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="T:System.DateTime"/> value to a byte array.
|
||||
/// </summary>
|
||||
/// <param name="dateTime">The DateTime value to convert.</param>
|
||||
/// <returns>A byte array containing the S7 date time representation of <paramref name="dateTime"/>.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the value of
|
||||
/// <paramref name="dateTime"/> is before <see cref="P:SpecMinimumDateTime"/>
|
||||
/// or after <see cref="P:SpecMaximumDateTime"/>.</exception>
|
||||
public static byte[] ToByteArray(System.DateTime dateTime)
|
||||
{
|
||||
byte EncodeBcd(int value)
|
||||
{
|
||||
return (byte) ((value / 10 << 4) | value % 10);
|
||||
}
|
||||
|
||||
if (dateTime < SpecMinimumDateTime)
|
||||
throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
|
||||
$"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 date time representation.");
|
||||
|
||||
if (dateTime > SpecMaximumDateTime)
|
||||
throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
|
||||
$"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 date time representation.");
|
||||
|
||||
byte MapYear(int year) => (byte) (year < 2000 ? year - 1900 : year - 2000);
|
||||
|
||||
int DayOfWeekToInt(DayOfWeek dayOfWeek) => (int) dayOfWeek + 1;
|
||||
|
||||
return new[]
|
||||
{
|
||||
EncodeBcd(MapYear(dateTime.Year)),
|
||||
EncodeBcd(dateTime.Month),
|
||||
EncodeBcd(dateTime.Day),
|
||||
EncodeBcd(dateTime.Hour),
|
||||
EncodeBcd(dateTime.Minute),
|
||||
EncodeBcd(dateTime.Second),
|
||||
EncodeBcd(dateTime.Millisecond / 10),
|
||||
(byte) (dateTime.Millisecond % 10 << 4 | DayOfWeekToInt(dateTime.DayOfWeek))
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of <see cref="T:System.DateTime"/> values to a byte array.
|
||||
/// </summary>
|
||||
/// <param name="dateTimes">The DateTime values to convert.</param>
|
||||
/// <returns>A byte array containing the S7 date time representations of <paramref name="dateTime"/>.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when any value of
|
||||
/// <paramref name="dateTimes"/> is before <see cref="P:SpecMinimumDateTime"/>
|
||||
/// or after <see cref="P:SpecMaximumDateTime"/>.</exception>
|
||||
public static byte[] ToByteArray(System.DateTime[] dateTimes)
|
||||
{
|
||||
var bytes = new List<byte>(dateTimes.Length * 8);
|
||||
foreach (var dateTime in dateTimes) bytes.AddRange(ToByteArray(dateTime));
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user