DateTime.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. using System;
  2. using System.Collections.Generic;
  3. namespace S7.Net.Types
  4. {
  5. /// <summary>
  6. /// Contains the methods to convert between <see cref="T:System.DateTime"/> and S7 representation of datetime values.
  7. /// </summary>
  8. public static class DateTime
  9. {
  10. /// <summary>
  11. /// The minimum <see cref="T:System.DateTime"/> value supported by the specification.
  12. /// </summary>
  13. public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1990, 1, 1);
  14. /// <summary>
  15. /// The maximum <see cref="T:System.DateTime"/> value supported by the specification.
  16. /// </summary>
  17. public static readonly System.DateTime SpecMaximumDateTime = new System.DateTime(2089, 12, 31, 23, 59, 59, 999);
  18. /// <summary>
  19. /// Parses a <see cref="T:System.DateTime"/> value from bytes.
  20. /// </summary>
  21. /// <param name="bytes">Input bytes read from PLC.</param>
  22. /// <returns>A <see cref="T:System.DateTime"/> object representing the value read from PLC.</returns>
  23. /// <exception cref="ArgumentOutOfRangeException">Thrown when the length of
  24. /// <paramref name="bytes"/> is not 8 or any value in <paramref name="bytes"/>
  25. /// is outside the valid range of values.</exception>
  26. public static System.DateTime FromByteArray(byte[] bytes)
  27. {
  28. return FromByteArrayImpl(bytes);
  29. }
  30. /// <summary>
  31. /// Parses an array of <see cref="T:System.DateTime"/> values from bytes.
  32. /// </summary>
  33. /// <param name="bytes">Input bytes read from PLC.</param>
  34. /// <returns>An array of <see cref="T:System.DateTime"/> objects representing the values read from PLC.</returns>
  35. /// <exception cref="ArgumentOutOfRangeException">Thrown when the length of
  36. /// <paramref name="bytes"/> is not a multiple of 8 or any value in
  37. /// <paramref name="bytes"/> is outside the valid range of values.</exception>
  38. public static System.DateTime[] ToArray(byte[] bytes)
  39. {
  40. if (bytes.Length % 8 != 0)
  41. throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length,
  42. $"Parsing an array of DateTime requires a multiple of 8 bytes of input data, input data is '{bytes.Length}' long.");
  43. var cnt = bytes.Length / 8;
  44. var result = new System.DateTime[bytes.Length / 8];
  45. for (var i = 0; i < cnt; i++)
  46. result[i] = FromByteArrayImpl(new ArraySegment<byte>(bytes, i * 8, 8));
  47. return result;
  48. }
  49. private static System.DateTime FromByteArrayImpl(IList<byte> bytes)
  50. {
  51. if (bytes.Count != 8)
  52. throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Count,
  53. $"Parsing a DateTime requires exactly 8 bytes of input data, input data is {bytes.Count} bytes long.");
  54. int DecodeBcd(byte input) => 10 * (input >> 4) + (input & 0b00001111);
  55. int ByteToYear(byte bcdYear)
  56. {
  57. var input = DecodeBcd(bcdYear);
  58. if (input < 90) return input + 2000;
  59. if (input < 100) return input + 1900;
  60. throw new ArgumentOutOfRangeException(nameof(bcdYear), bcdYear,
  61. $"Value '{input}' is higher than the maximum '99' of S7 date and time representation.");
  62. }
  63. int AssertRangeInclusive(int input, byte min, byte max, string field)
  64. {
  65. if (input < min)
  66. throw new ArgumentOutOfRangeException(nameof(input), input,
  67. $"Value '{input}' is lower than the minimum '{min}' allowed for {field}.");
  68. if (input > max)
  69. throw new ArgumentOutOfRangeException(nameof(input), input,
  70. $"Value '{input}' is higher than the maximum '{max}' allowed for {field}.");
  71. return input;
  72. }
  73. var year = ByteToYear(bytes[0]);
  74. var month = AssertRangeInclusive(DecodeBcd(bytes[1]), 1, 12, "month");
  75. var day = AssertRangeInclusive(DecodeBcd(bytes[2]), 1, 31, "day of month");
  76. var hour = AssertRangeInclusive(DecodeBcd(bytes[3]), 0, 23, "hour");
  77. var minute = AssertRangeInclusive(DecodeBcd(bytes[4]), 0, 59, "minute");
  78. var second = AssertRangeInclusive(DecodeBcd(bytes[5]), 0, 59, "second");
  79. var hsec = AssertRangeInclusive(DecodeBcd(bytes[6]), 0, 99, "first two millisecond digits");
  80. var msec = AssertRangeInclusive(bytes[7] >> 4, 0, 9, "third millisecond digit");
  81. var dayOfWeek = AssertRangeInclusive(bytes[7] & 0b00001111, 1, 7, "day of week");
  82. return new System.DateTime(year, month, day, hour, minute, second, hsec * 10 + msec);
  83. }
  84. /// <summary>
  85. /// Converts a <see cref="T:System.DateTime"/> value to a byte array.
  86. /// </summary>
  87. /// <param name="dateTime">The DateTime value to convert.</param>
  88. /// <returns>A byte array containing the S7 date time representation of <paramref name="dateTime"/>.</returns>
  89. /// <exception cref="ArgumentOutOfRangeException">Thrown when the value of
  90. /// <paramref name="dateTime"/> is before <see cref="P:SpecMinimumDateTime"/>
  91. /// or after <see cref="P:SpecMaximumDateTime"/>.</exception>
  92. public static byte[] ToByteArray(System.DateTime dateTime)
  93. {
  94. byte EncodeBcd(int value)
  95. {
  96. return (byte) ((value / 10 << 4) | value % 10);
  97. }
  98. if (dateTime < SpecMinimumDateTime)
  99. throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
  100. $"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 date time representation.");
  101. if (dateTime > SpecMaximumDateTime)
  102. throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
  103. $"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 date time representation.");
  104. byte MapYear(int year) => (byte) (year < 2000 ? year - 1900 : year - 2000);
  105. int DayOfWeekToInt(DayOfWeek dayOfWeek) => (int) dayOfWeek + 1;
  106. return new[]
  107. {
  108. EncodeBcd(MapYear(dateTime.Year)),
  109. EncodeBcd(dateTime.Month),
  110. EncodeBcd(dateTime.Day),
  111. EncodeBcd(dateTime.Hour),
  112. EncodeBcd(dateTime.Minute),
  113. EncodeBcd(dateTime.Second),
  114. EncodeBcd(dateTime.Millisecond / 10),
  115. (byte) (dateTime.Millisecond % 10 << 4 | DayOfWeekToInt(dateTime.DayOfWeek))
  116. };
  117. }
  118. /// <summary>
  119. /// Converts an array of <see cref="T:System.DateTime"/> values to a byte array.
  120. /// </summary>
  121. /// <param name="dateTimes">The DateTime values to convert.</param>
  122. /// <returns>A byte array containing the S7 date time representations of <paramref name="dateTimes"/>.</returns>
  123. /// <exception cref="ArgumentOutOfRangeException">Thrown when any value of
  124. /// <paramref name="dateTimes"/> is before <see cref="P:SpecMinimumDateTime"/>
  125. /// or after <see cref="P:SpecMaximumDateTime"/>.</exception>
  126. public static byte[] ToByteArray(System.DateTime[] dateTimes)
  127. {
  128. var bytes = new List<byte>(dateTimes.Length * 8);
  129. foreach (var dateTime in dateTimes) bytes.AddRange(ToByteArray(dateTime));
  130. return bytes.ToArray();
  131. }
  132. }
  133. }