using System;
using System.Collections.Generic;
namespace S7.Net.Types
{
///
/// Contains the methods to convert between and S7 representation of datetime values.
///
public static class DateTime
{
///
/// The minimum value supported by the specification.
///
public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1990, 1, 1);
///
/// The maximum value supported by the specification.
///
public static readonly System.DateTime SpecMaximumDateTime = new System.DateTime(2089, 12, 31, 23, 59, 59, 999);
///
/// 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 8 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 8 or any value in
/// is outside the valid range of values.
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(bytes, i * 8, 8));
return result;
}
private static System.DateTime FromByteArrayImpl(IList 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);
}
///
/// Converts a value to a byte array.
///
/// The DateTime value to convert.
/// A byte array containing the S7 date time representation of .
/// Thrown when the value of
/// is before
/// or after .
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))
};
}
///
/// Converts an array of values to a byte array.
///
/// The DateTime values to convert.
/// A byte array containing the S7 date time representations of .
/// Thrown when any value of
/// is before
/// or after .
public static byte[] ToByteArray(System.DateTime[] dateTimes)
{
var bytes = new List(dateTimes.Length * 8);
foreach (var dateTime in dateTimes) bytes.AddRange(ToByteArray(dateTime));
return bytes.ToArray();
}
}
}