Repository navigation
Expand file tree
/
Copy pathIec61850TimestampPresentation.cs
More file actions
97 lines (82 loc) · 3.69 KB
/
Copy pathIec61850TimestampPresentation.cs
File metadata and controls
97 lines (82 loc) · 3.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
using System.Globalization;
namespace ArIED61850Tester;
/// <summary>
/// Customer-facing IEC 61850 timestamp presentation helpers.
/// IEC 61850 timestamps retain their original full-resolution value in the model/evidence;
/// this class only rounds when a millisecond-resolution string is requested, matching
/// the nearest-millisecond presentation expected when comparing relay and SAS event logs.
/// </summary>
public static class Iec61850TimestampPresentation
{
private const long TicksPerMillisecond = TimeSpan.TicksPerMillisecond;
private const long HalfMillisecondTicks = TicksPerMillisecond / 2;
/// <summary>
/// Rounds to the nearest millisecond. An exact half-millisecond is rounded forward
/// so a value such as 31.2005000 is presented as 31.201 rather than truncated to 31.200.
/// The original UTC offset is preserved.
/// </summary>
public static DateTimeOffset RoundToNearestMillisecond(DateTimeOffset value)
{
var delta = MillisecondDelta(value.Ticks);
if (delta > 0 && value.UtcTicks > DateTime.MaxValue.Ticks - delta)
delta = -(value.Ticks % TicksPerMillisecond);
return value.AddTicks(delta);
}
public static DateTime RoundToNearestMillisecond(DateTime value)
{
var delta = MillisecondDelta(value.Ticks);
return value.AddTicks(delta);
}
public static string FormatMilliseconds(DateTimeOffset value, string format)
=> RoundToNearestMillisecond(value).ToString(format, CultureInfo.InvariantCulture);
public static string FormatMilliseconds(DateTimeOffset? value, string format, string missing = "—")
=> value.HasValue ? FormatMilliseconds(value.Value, format) : missing;
public static string FormatMilliseconds(DateTime value, string format)
=> RoundToNearestMillisecond(value).ToString(format, CultureInfo.InvariantCulture);
/// <summary>
/// Formats a full-resolution timestamp string for a compact live-grid display while
/// leaving the source string untouched for evidence, search and full-precision tooltips.
/// Non-timestamp values are returned unchanged rather than guessed.
/// </summary>
public static string FormatMilliseconds(
string? value,
string format = "yyyy-MM-dd HH:mm:ss.fff",
string missing = "-")
{
var text = (value ?? string.Empty).Trim();
if (text.Length == 0 || text == "-")
return text.Length == 0 ? missing : text;
// Relay timestamps in the live workspace normally have no explicit offset.
// Parse those as DateTime first so presentation never invents or shifts a zone.
if (DateTime.TryParse(
text,
CultureInfo.InvariantCulture,
DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.RoundtripKind,
out var dateTime))
{
return FormatMilliseconds(dateTime, format);
}
// Preserve explicit-offset timestamps when they are supplied by another surface.
if (DateTimeOffset.TryParse(
text,
CultureInfo.InvariantCulture,
DateTimeStyles.AllowWhiteSpaces,
out var dateTimeOffset))
{
return FormatMilliseconds(dateTimeOffset, format);
}
return text;
}
private static long MillisecondDelta(long ticks)
{
var remainder = ticks % TicksPerMillisecond;
if (remainder == 0)
return 0;
var delta = remainder >= HalfMillisecondTicks
? TicksPerMillisecond - remainder
: -remainder;
if (delta > 0 && ticks > DateTime.MaxValue.Ticks - delta)
return -remainder;
return delta;
}
}