DateTimeLong.cs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. namespace S7.Net.Types
  5. {
  6. /// <summary>
  7. /// Contains the methods to convert between <see cref="T:System.DateTime" /> and S7 representation of DateTimeLong (DTL) values.
  8. /// </summary>
  9. public static class DateTimeLong
  10. {
  11. public const int TypeLengthInBytes = 12;
  12. /// <summary>
  13. /// The minimum <see cref="T:System.DateTime" /> value supported by the specification.
  14. /// </summary>
  15. public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1970, 1, 1);
  16. /// <summary>
  17. /// The maximum <see cref="T:System.DateTime" /> value supported by the specification.
  18. /// </summary>
  19. public static readonly System.DateTime SpecMaximumDateTime = new System.DateTime(2262, 4, 11, 23, 47, 16, 854);
  20. /// <summary>
  21. /// Parses a <see cref="T:System.DateTime" /> value from bytes.
  22. /// </summary>
  23. /// <param name="bytes">Input bytes read from PLC.</param>
  24. /// <returns>A <see cref="T:System.DateTime" /> object representing the value read from PLC.</returns>
  25. /// <exception cref="ArgumentOutOfRangeException">
  26. /// Thrown when the length of
  27. /// <paramref name="bytes" /> is not 12 or any value in <paramref name="bytes" />
  28. /// is outside the valid range of values.
  29. /// </exception>
  30. public static System.DateTime FromByteArray(byte[] bytes)
  31. {
  32. return FromByteArrayImpl(bytes);
  33. }
  34. /// <summary>
  35. /// Parses an array of <see cref="T:System.DateTime" /> values from bytes.
  36. /// </summary>
  37. /// <param name="bytes">Input bytes read from PLC.</param>
  38. /// <returns>An array of <see cref="T:System.DateTime" /> objects representing the values read from PLC.</returns>
  39. /// <exception cref="ArgumentOutOfRangeException">
  40. /// Thrown when the length of
  41. /// <paramref name="bytes" /> is not a multiple of 12 or any value in
  42. /// <paramref name="bytes" /> is outside the valid range of values.
  43. /// </exception>
  44. public static System.DateTime[] ToArray(byte[] bytes)
  45. {
  46. if (bytes.Length % TypeLengthInBytes != 0)
  47. {
  48. throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length,
  49. $"Parsing an array of DateTimeLong requires a multiple of 12 bytes of input data, input data is '{bytes.Length}' long.");
  50. }
  51. var cnt = bytes.Length / TypeLengthInBytes;
  52. var result = new System.DateTime[cnt];
  53. for (var i = 0; i < cnt; i++)
  54. {
  55. var slice = new byte[TypeLengthInBytes];
  56. Array.Copy(bytes, i * TypeLengthInBytes, slice, 0, TypeLengthInBytes);
  57. result[i] = FromByteArrayImpl(slice);
  58. }
  59. return result;
  60. }
  61. private static System.DateTime FromByteArrayImpl(byte[] bytes)
  62. {
  63. if (bytes.Length != TypeLengthInBytes)
  64. {
  65. throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length,
  66. $"Parsing a DateTimeLong requires exactly 12 bytes of input data, input data is {bytes.Length} bytes long.");
  67. }
  68. var year = AssertRangeInclusive(Word.FromBytes(bytes[1], bytes[0]), 1970, 2262, "year");
  69. var month = AssertRangeInclusive(bytes[2], 1, 12, "month");
  70. var day = AssertRangeInclusive(bytes[3], 1, 31, "day of month");
  71. var dayOfWeek = AssertRangeInclusive(bytes[4], 1, 7, "day of week");
  72. var hour = AssertRangeInclusive(bytes[5], 0, 23, "hour");
  73. var minute = AssertRangeInclusive(bytes[6], 0, 59, "minute");
  74. var second = AssertRangeInclusive(bytes[7], 0, 59, "second");
  75. ;
  76. var nanoseconds = AssertRangeInclusive<uint>(DWord.FromBytes(bytes[11], bytes[10], bytes[9], bytes[8]), 0,
  77. 999999999, "nanoseconds");
  78. var time = new System.DateTime(year, month, day, hour, minute, second);
  79. return time.AddTicks(nanoseconds / 100);
  80. }
  81. /// <summary>
  82. /// Converts a <see cref="T:System.DateTime" /> value to a byte array.
  83. /// </summary>
  84. /// <param name="dateTime">The DateTime value to convert.</param>
  85. /// <returns>A byte array containing the S7 DateTimeLong representation of <paramref name="dateTime" />.</returns>
  86. /// <exception cref="ArgumentOutOfRangeException">
  87. /// Thrown when the value of
  88. /// <paramref name="dateTime" /> is before <see cref="P:SpecMinimumDateTime" />
  89. /// or after <see cref="P:SpecMaximumDateTime" />.
  90. /// </exception>
  91. public static byte[] ToByteArray(System.DateTime dateTime)
  92. {
  93. if (dateTime < SpecMinimumDateTime)
  94. {
  95. throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
  96. $"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 DateTimeLong representation.");
  97. }
  98. if (dateTime > SpecMaximumDateTime)
  99. {
  100. throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
  101. $"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 DateTimeLong representation.");
  102. }
  103. var stream = new MemoryStream(TypeLengthInBytes);
  104. // Convert Year
  105. stream.Write(Word.ToByteArray(Convert.ToUInt16(dateTime.Year)), 0, 2);
  106. // Convert Month
  107. stream.WriteByte(Convert.ToByte(dateTime.Month));
  108. // Convert Day
  109. stream.WriteByte(Convert.ToByte(dateTime.Day));
  110. // Convert WeekDay. NET DateTime starts with Sunday = 0, while S7DT has Sunday = 1.
  111. stream.WriteByte(Convert.ToByte(dateTime.DayOfWeek + 1));
  112. // Convert Hour
  113. stream.WriteByte(Convert.ToByte(dateTime.Hour));
  114. // Convert Minutes
  115. stream.WriteByte(Convert.ToByte(dateTime.Minute));
  116. // Convert Seconds
  117. stream.WriteByte(Convert.ToByte(dateTime.Second));
  118. // Convert Nanoseconds. Net DateTime has a representation of 1 Tick = 100ns.
  119. // Thus First take the ticks Mod 1 Second (1s = 10'000'000 ticks), and then Convert to nanoseconds.
  120. stream.Write(DWord.ToByteArray(Convert.ToUInt32(dateTime.Ticks % 10000000 * 100)), 0, 4);
  121. return stream.ToArray();
  122. }
  123. /// <summary>
  124. /// Converts an array of <see cref="T:System.DateTime" /> values to a byte array.
  125. /// </summary>
  126. /// <param name="dateTimes">The DateTime values to convert.</param>
  127. /// <returns>A byte array containing the S7 DateTimeLong representations of <paramref name="dateTimes" />.</returns>
  128. /// <exception cref="ArgumentOutOfRangeException">
  129. /// Thrown when any value of
  130. /// <paramref name="dateTimes" /> is before <see cref="P:SpecMinimumDateTime" />
  131. /// or after <see cref="P:SpecMaximumDateTime" />.
  132. /// </exception>
  133. public static byte[] ToByteArray(System.DateTime[] dateTimes)
  134. {
  135. var bytes = new List<byte>(dateTimes.Length * TypeLengthInBytes);
  136. foreach (var dateTime in dateTimes)
  137. {
  138. bytes.AddRange(ToByteArray(dateTime));
  139. }
  140. return bytes.ToArray();
  141. }
  142. private static T AssertRangeInclusive<T>(T input, T min, T max, string field) where T : IComparable<T>
  143. {
  144. if (input.CompareTo(min) < 0)
  145. {
  146. throw new ArgumentOutOfRangeException(nameof(input), input,
  147. $"Value '{input}' is lower than the minimum '{min}' allowed for {field}.");
  148. }
  149. if (input.CompareTo(max) > 0)
  150. {
  151. throw new ArgumentOutOfRangeException(nameof(input), input,
  152. $"Value '{input}' is higher than the maximum '{max}' allowed for {field}.");
  153. }
  154. return input;
  155. }
  156. }
  157. }