aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Tango.Telemetry/Helpers/DateTimeUTCFixer.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Software/Visual_Studio/Tango.Telemetry/Helpers/DateTimeUTCFixer.cs')
-rw-r--r--Software/Visual_Studio/Tango.Telemetry/Helpers/DateTimeUTCFixer.cs209
1 files changed, 209 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Tango.Telemetry/Helpers/DateTimeUTCFixer.cs b/Software/Visual_Studio/Tango.Telemetry/Helpers/DateTimeUTCFixer.cs
new file mode 100644
index 000000000..3ce0c700f
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Telemetry/Helpers/DateTimeUTCFixer.cs
@@ -0,0 +1,209 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+namespace Tango.Telemetry.Helpers
+{
+ internal static class DateTimeUtcFixer
+ {
+ /// <summary>
+ /// Recursively scans the object graph starting at <paramref name="obj"/> and
+ /// sets Kind=Utc on any DateTime/DateTime? properties or collection elements that are Kind=Unspecified.
+ /// Uses DateTime.SpecifyKind(value, DateTimeKind.Utc) (no clock adjustment).
+ /// Notes:
+ /// - Only properties are updated (not fields).
+ /// - Collections: updates elements for IList/arrays and dictionary values; traverses other IEnumerable to reach nested objects.
+ /// - Read-only properties (no setter) are skipped.
+ /// - Root objects that are a DateTime value cannot be changed in place (struct); wrap them in a container if needed.
+ /// </summary>
+ public static void EnsureDateTimeUTC(object obj)
+ {
+ var visited = new HashSet<object>(ReferenceEqualityComparer.Instance);
+ EnsureUtcInternal(obj, visited);
+ }
+
+ private static void EnsureUtcInternal(object obj, HashSet<object> visited)
+ {
+ if (obj == null) return;
+
+ var type = obj.GetType();
+
+ // Avoid revisiting reference objects (handle cycles, EF proxies, etc.)
+ if (!type.IsValueType) // only track reference types
+ {
+ if (!visited.Add(obj)) return;
+ }
+
+ // Handle dictionaries: update values, recurse into objects
+ if (obj is IDictionary dict)
+ {
+ var keys = new List<object>();
+ foreach (var k in dict.Keys) keys.Add(k);
+
+ foreach (var key in keys)
+ {
+ var value = dict[key];
+ if (TrySpecifyUtcOnBoxedDateTime(ref value))
+ {
+ dict[key] = value;
+ }
+ else
+ {
+ EnsureUtcInternal(value, visited);
+ }
+ }
+ return;
+ }
+
+ // Handle lists/arrays: update elements, recurse into objects
+ if (obj is IList list)
+ {
+ for (int i = 0; i < list.Count; i++)
+ {
+ var element = list[i];
+ if (TrySpecifyUtcOnBoxedDateTime(ref element))
+ {
+ list[i] = element;
+ }
+ else
+ {
+ EnsureUtcInternal(element, visited);
+ }
+ }
+ return;
+ }
+
+ // Traverse other enumerables just to reach nested objects (cannot replace DateTime elements)
+ if (obj is IEnumerable enumerable && !(obj is string))
+ {
+ foreach (var element in enumerable)
+ {
+ EnsureUtcInternal(element, visited);
+ }
+ }
+
+ // Inspect properties
+ const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
+ foreach (var prop in type.GetProperties(flags))
+ {
+ // indexers not supported
+ if (prop.GetIndexParameters().Length != 0) continue;
+
+ // must be readable
+ if (!prop.CanRead) continue;
+
+ var pType = prop.PropertyType;
+
+ // DateTime
+ if (pType == typeof(DateTime))
+ {
+ var val = (DateTime)prop.GetValue(obj);
+ if (val.Kind == DateTimeKind.Unspecified)
+ {
+ var newVal = DateTime.SpecifyKind(val, DateTimeKind.Utc);
+ TrySetProperty(obj, prop, newVal);
+ }
+ continue;
+ }
+
+ // Nullable<DateTime>
+ var underlying = Nullable.GetUnderlyingType(pType);
+ if (underlying == typeof(DateTime))
+ {
+ var val = (DateTime?)prop.GetValue(obj);
+ if (val.HasValue && val.Value.Kind == DateTimeKind.Unspecified)
+ {
+ var newVal = (DateTime?)DateTime.SpecifyKind(val.Value, DateTimeKind.Utc);
+ TrySetProperty(obj, prop, newVal);
+ }
+ continue;
+ }
+
+ // Skip obvious simple types
+ if (IsSimple(pType)) continue;
+
+ // Recurse into complex objects
+ var child = prop.GetValue(obj);
+ EnsureUtcInternal(child, visited);
+ }
+ }
+
+ private static bool TrySpecifyUtcOnBoxedDateTime(ref object obj)
+ {
+ if (obj == null) return false;
+
+ var t = obj.GetType();
+
+ // Exact DateTime
+ if (t == typeof(DateTime))
+ {
+ var dt = (DateTime)obj;
+ if (dt.Kind == DateTimeKind.Unspecified)
+ {
+ obj = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
+ }
+ return true;
+ }
+
+ // Nullable<DateTime> boxed
+ var underlying = Nullable.GetUnderlyingType(t);
+ if (underlying == typeof(DateTime))
+ {
+ // boxed Nullable<DateTime> can be unboxed to DateTime? safely
+ var ndt = (DateTime?)obj;
+ if (ndt.HasValue && ndt.Value.Kind == DateTimeKind.Unspecified)
+ {
+ obj = (DateTime?)DateTime.SpecifyKind(ndt.Value, DateTimeKind.Utc);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool TrySetProperty(object target, PropertyInfo prop, object value)
+ {
+ try
+ {
+ // Prefer invoking the setter (even if non-public)
+ var setter = prop.GetSetMethod(nonPublic: true);
+ if (setter == null) return false;
+ setter.Invoke(target, new[] { value });
+ return true;
+ }
+ catch
+ {
+ // Fall back to PropertyInfo.SetValue (may still work in some runtimes)
+ try
+ {
+ prop.SetValue(target, value);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+ }
+
+ private static bool IsSimple(Type t)
+ {
+ if (t.IsPrimitive || t.IsEnum) return true;
+ if (t == typeof(string) || t == typeof(decimal) || t == typeof(Guid) ||
+ t == typeof(Uri) || t == typeof(TimeSpan) || t == typeof(DateTimeOffset))
+ return true;
+ // Value types other than DateTime/Nullable<DateTime> are considered simple
+ return t.IsValueType;
+ }
+
+ /// <summary>Reference equality comparer for the visited set.</summary>
+ private sealed class ReferenceEqualityComparer : IEqualityComparer<object>
+ {
+ public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer();
+ public new bool Equals(object x, object y) => ReferenceEquals(x, y);
+ public int GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj);
+ }
+ }
+}