Skip to content

Commit b7f4fcc

Browse files
Updated to add TimeZone support for XML output to address issue #99
1 parent a96f124 commit b7f4fcc

10 files changed

Lines changed: 249 additions & 3 deletions

File tree

libraries/MTConnect.NET-Common/Agents/MTConnectAgentBroker.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1240,7 +1240,7 @@ private IObservationOutput[] GetObservations(string deviceUuid, ref IObservation
12401240
return null;
12411241
}
12421242

1243-
private static IObservationOutput CreateObservation(IDataItem dataItem, ref BufferObservation bufferObservation)
1243+
private IObservationOutput CreateObservation(IDataItem dataItem, ref BufferObservation bufferObservation)
12441244
{
12451245
var observation = new ObservationOutput();
12461246
observation._dataItem = dataItem;
@@ -1253,7 +1253,10 @@ private static IObservationOutput CreateObservation(IDataItem dataItem, ref Buff
12531253
observation._name = dataItem.Name;
12541254
observation._compositionId = dataItem.CompositionId;
12551255
observation._sequence = bufferObservation.Sequence;
1256+
12561257
observation._timestamp = bufferObservation.Timestamp.ToDateTime();
1258+
observation._timeZoneTimestamp = MTConnectTimeZone.GetTimestamp(observation._timestamp, TimeZoneOutput);
1259+
12571260
observation._values = bufferObservation.Values;
12581261
return observation;
12591262
}

libraries/MTConnect.NET-Common/Configurations/AgentConfiguration.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ public class AgentConfiguration : IAgentConfiguration
5050
public uint AssetBufferSize { get; set; }
5151

5252

53+
/// <summary>
54+
/// Sets the TimeZone to use when timestamps are output from the Agent
55+
/// </summary>
56+
[JsonPropertyName("timezoneOutput")]
57+
public string TimeZoneOutput { get; set; }
58+
5359
/// <summary>
5460
/// Overwrite timestamps with the agent time.
5561
/// This will correct clock drift but will not give as accurate relative time since it will not take into consideration network latencies.

libraries/MTConnect.NET-Common/Configurations/IAgentConfiguration.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ public interface IAgentConfiguration
2727
uint AssetBufferSize { get; }
2828

2929

30+
/// <summary>
31+
/// Sets the TimeZone to use when timestamps are output from the Agent
32+
/// </summary>
33+
string TimeZoneOutput { get; }
34+
3035
/// <summary>
3136
/// Overwrite timestamps with the agent time.
3237
/// This will correct clock drift but will not give as accurate relative time since it will not take into consideration network latencies.
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Copyright (c) 2025 TrakHound Inc., All Rights Reserved.
2+
// TrakHound Inc. licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text.RegularExpressions;
8+
9+
namespace MTConnect
10+
{
11+
public class MTConnectTimeZone
12+
{
13+
private readonly static IEnumerable<MTConnectTimeZone> _timeZones = Init();
14+
private readonly static string _offsetParsePattern = "^(?:UTC)?\\s?([-+])([0-9]{1,2})(?::([0-9]{2}))?$";
15+
16+
17+
public string Abbreviation { get; set; }
18+
public string Name { get; set; }
19+
public string Offset { get; set; }
20+
21+
22+
public MTConnectTimeZone(string abbreviation, string name, string offset)
23+
{
24+
Abbreviation = abbreviation;
25+
Name = name;
26+
Offset = offset;
27+
}
28+
29+
public DateTimeOffset ToTimestamp(DateTime utcTimestamp)
30+
{
31+
return GetTimestamp(utcTimestamp, ToTimeZoneInfo());
32+
}
33+
34+
public TimeZoneInfo ToTimeZoneInfo()
35+
{
36+
try
37+
{
38+
var offsetTimeSpan = GetOffsetTimeSpan(Offset);
39+
if (offsetTimeSpan.HasValue)
40+
{
41+
var matchedTimeZones = TimeZoneInfo.GetSystemTimeZones().Where(o => o.BaseUtcOffset == offsetTimeSpan.Value);
42+
if (matchedTimeZones != null)
43+
{
44+
return matchedTimeZones.OrderBy(o => GetSimilarity(o.StandardName, Name)).FirstOrDefault();
45+
}
46+
}
47+
}
48+
catch { }
49+
50+
return null;
51+
}
52+
53+
public static DateTimeOffset GetTimestamp(DateTime utcTimestamp, TimeZoneInfo timeZoneInfo)
54+
{
55+
if (timeZoneInfo != null)
56+
{
57+
// Convert Timestamp to Time Zone using Offset
58+
var output = TimeZoneInfo.ConvertTime(utcTimestamp, timeZoneInfo);
59+
60+
// Convert to Unspecified (to not use Local based on PC settings)
61+
output = DateTime.SpecifyKind(output, DateTimeKind.Unspecified);
62+
63+
//return new DateTimeOffset(output, timeZoneInfo.BaseUtcOffset);
64+
return new DateTimeOffset(output, timeZoneInfo.GetUtcOffset(output));
65+
}
66+
67+
return new DateTimeOffset(utcTimestamp);
68+
}
69+
70+
71+
public static MTConnectTimeZone Get(string input)
72+
{
73+
if (!string.IsNullOrEmpty(input))
74+
{
75+
// Try Abbreviation
76+
var output = _timeZones.FirstOrDefault(o => o.Abbreviation.ToLower() == input.ToLower());
77+
78+
// Try Offset
79+
if (output == null)
80+
{
81+
output = _timeZones.FirstOrDefault(o => MatchOffset(input, o.Offset));
82+
}
83+
84+
return output;
85+
}
86+
87+
return null;
88+
}
89+
90+
private static bool MatchOffset(string input, string compare)
91+
{
92+
var offsetTimeSpan = GetOffsetTimeSpan(input);
93+
var compareTimeSpan = GetOffsetTimeSpan(compare);
94+
95+
if (offsetTimeSpan.HasValue && compareTimeSpan.HasValue)
96+
{
97+
return offsetTimeSpan == compareTimeSpan;
98+
}
99+
100+
return false;
101+
}
102+
103+
private static TimeSpan? GetOffsetTimeSpan(string input)
104+
{
105+
var match = Regex.Match(input, _offsetParsePattern);
106+
if (match.Success)
107+
{
108+
var offsetDirection = match.Groups[1].Value;
109+
var offsetHour = match.Groups[2].Value;
110+
var offsetMinute = match.Groups[3].Value;
111+
if (string.IsNullOrEmpty(offsetMinute)) offsetMinute = "00";
112+
var offsetInput = $"{offsetDirection}{offsetHour}:{offsetMinute}";
113+
return TimeSpan.Parse(offsetInput);
114+
}
115+
116+
return null;
117+
}
118+
119+
private static IEnumerable<MTConnectTimeZone> Init()
120+
{
121+
var timeZones = new List<MTConnectTimeZone>();
122+
timeZones.Add(new MTConnectTimeZone("Z", "Zulu Time", "UTC+0"));
123+
124+
// USA
125+
timeZones.Add(new MTConnectTimeZone("ET", "Eastern Time", "UTC-5:00"));
126+
timeZones.Add(new MTConnectTimeZone("EST", "Eastern Standard Time", "UTC-5:00"));
127+
timeZones.Add(new MTConnectTimeZone("CT", "Central Time", "UTC-6:00"));
128+
timeZones.Add(new MTConnectTimeZone("CST", "Central Standard Time", "UTC-6:00"));
129+
timeZones.Add(new MTConnectTimeZone("MT", "Mountain Time", "UTC-7:00"));
130+
timeZones.Add(new MTConnectTimeZone("MST", "Mountain Standard Time", "UTC-7:00"));
131+
timeZones.Add(new MTConnectTimeZone("PT", "Pacific Time", "UTC-8:00"));
132+
timeZones.Add(new MTConnectTimeZone("PST", "Pacific Standard Time", "UTC-8:00"));
133+
timeZones.Add(new MTConnectTimeZone("AKST", "Alaska Standard Time", "UTC-9:00"));
134+
timeZones.Add(new MTConnectTimeZone("HST", "Hawaii Standard Time", "UTC-10:00"));
135+
136+
// Europe
137+
timeZones.Add(new MTConnectTimeZone("WET", "Western European Time", "UTC+0:00"));
138+
timeZones.Add(new MTConnectTimeZone("CET", "Central European Time", "UTC+1:00"));
139+
timeZones.Add(new MTConnectTimeZone("EET", "Eastern European Time", "UTC+2:00"));
140+
141+
// Australia
142+
timeZones.Add(new MTConnectTimeZone("AWST", "Austrailian Western Standard Time", "UTC+8:00"));
143+
timeZones.Add(new MTConnectTimeZone("ACT", "Austrailian Central Time", "UTC+9:30"));
144+
timeZones.Add(new MTConnectTimeZone("ACST", "Austrailian Central Standard Time", "UTC+9:30"));
145+
timeZones.Add(new MTConnectTimeZone("AET", "Austrailian Eastern Time", "UTC+10:00"));
146+
timeZones.Add(new MTConnectTimeZone("AEST", "Austrailian Eastern Standard Time", "UTC+10:00"));
147+
148+
return timeZones;
149+
}
150+
151+
private static int GetSimilarity(string s, string t)
152+
{
153+
if (string.IsNullOrEmpty(s))
154+
{
155+
if (string.IsNullOrEmpty(t))
156+
return 0;
157+
return t.Length;
158+
}
159+
160+
if (string.IsNullOrEmpty(t))
161+
{
162+
return s.Length;
163+
}
164+
165+
int n = s.Length;
166+
int m = t.Length;
167+
int[,] d = new int[n + 1, m + 1];
168+
169+
// initialize the top and right of the table to 0, 1, 2, ...
170+
for (int i = 0; i <= n; d[i, 0] = i++) ;
171+
for (int j = 1; j <= m; d[0, j] = j++) ;
172+
173+
for (int i = 1; i <= n; i++)
174+
{
175+
for (int j = 1; j <= m; j++)
176+
{
177+
int cost = (t[j - 1] == s[i - 1]) ? 0 : 1;
178+
int min1 = d[i - 1, j] + 1;
179+
int min2 = d[i, j - 1] + 1;
180+
int min3 = d[i - 1, j - 1] + cost;
181+
d[i, j] = Math.Min(Math.Min(min1, min2), min3);
182+
}
183+
}
184+
return d[n, m];
185+
}
186+
}
187+
}

libraries/MTConnect.NET-Common/MTConnectVersions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace MTConnect
77
{
88
public static class MTConnectVersions
99
{
10-
public static Version Max => Version24;
10+
public static Version Max => Version25;
1111

1212
public static readonly Version Version10 = new Version(1, 0);
1313
public static readonly Version Version11 = new Version(1, 1);
@@ -23,5 +23,6 @@ public static class MTConnectVersions
2323
public static readonly Version Version22 = new Version(2, 2);
2424
public static readonly Version Version23 = new Version(2, 3);
2525
public static readonly Version Version24 = new Version(2, 4);
26+
public static readonly Version Version25 = new Version(2, 5);
2627
}
2728
}

libraries/MTConnect.NET-Common/Observations/Output/IObservationOutput.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public interface IObservationOutput
2929

3030
DateTime Timestamp { get; }
3131

32+
DateTimeOffset TimeZoneTimestamp { get; }
33+
3234
string CompositionId { get; }
3335

3436
DataItemRepresentation Representation { get; }

libraries/MTConnect.NET-Common/Observations/Output/ObservationOutput.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ public DateTime Timestamp
5050
set => _timestamp = value;
5151
}
5252

53+
internal DateTimeOffset _timeZoneTimestamp;
54+
public DateTimeOffset TimeZoneTimestamp
55+
{
56+
get => _timeZoneTimestamp;
57+
set => _timeZoneTimestamp = value;
58+
}
59+
5360
/// <summary>
5461
/// The name of the DataItem.
5562
/// The name MUST match the name of the data item defined in the Device Information Model that this DataItem represents.

libraries/MTConnect.NET-JSON/JsonFunctions.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2024 TrakHound Inc., All Rights Reserved.
22
// TrakHound Inc. licenses this file to you under the MIT license.
33

4+
using System;
45
using System.IO;
56
using System.Text.Json;
67
using System.Text.Json.Serialization;
@@ -44,6 +45,23 @@ public static JsonSerializerOptions IndentOptions
4445
}
4546

4647

48+
public static string GetTimestamp(DateTime timestamp)
49+
{
50+
return timestamp.ToString("o");
51+
}
52+
53+
public static string GetTimestamp(DateTimeOffset timestamp)
54+
{
55+
if (timestamp.Offset != TimeSpan.Zero)
56+
{
57+
return timestamp.ToString("o");
58+
}
59+
else
60+
{
61+
return timestamp.UtcDateTime.ToString("o");
62+
}
63+
}
64+
4765
public static string Convert(object obj, JsonConverter converter = null, bool indented = false)
4866
{
4967
if (obj != null)

libraries/MTConnect.NET-XML/Streams/XmlObservationContainer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ private static void WriteXml(
195195
writer.WriteAttributeString("sequence", observation.Sequence.ToString());
196196

197197
// Timestamp
198-
writer.WriteAttributeString("timestamp", observation.Timestamp.ToString("o"));
198+
writer.WriteAttributeString("timestamp", XmlFunctions.GetTimestamp(observation.TimeZoneTimestamp));
199199

200200

201201
// Add Values

libraries/MTConnect.NET-XML/XmlFunctions.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,23 @@ public static class XmlFunctions
4242
public static XmlWriterSettings XmlWriterSettingsIndent => _xmlWriterSettingsIndent;
4343

4444

45+
public static string GetTimestamp(DateTime timestamp)
46+
{
47+
return timestamp.ToString("o");
48+
}
49+
50+
public static string GetTimestamp(DateTimeOffset timestamp)
51+
{
52+
if (timestamp.Offset != TimeSpan.Zero)
53+
{
54+
return timestamp.ToString("o");
55+
}
56+
else
57+
{
58+
return timestamp.UtcDateTime.ToString("o");
59+
}
60+
}
61+
4562
public static byte[] SanitizeBytes(byte[] inputBytes)
4663
{
4764
if (inputBytes != null)

0 commit comments

Comments
 (0)