diff --git a/.gitignore b/.gitignore index 5c4f875e..52a44daf 100644 --- a/.gitignore +++ b/.gitignore @@ -198,3 +198,6 @@ FakesAssemblies/ # Jetbrains Rider .idea/ + +# VSCode +.vscode/ diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index a6e4859c..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "version": "0.2.0", - "configurations": [ - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach", - "processId": "${command:pickProcess}" - } - ] -} diff --git a/ISOv4Plugin/Mappers/LoggedDataMappers/Import/SpatialRecordMapper.cs b/ISOv4Plugin/Mappers/LoggedDataMappers/Import/SpatialRecordMapper.cs index a77f8967..e745e917 100644 --- a/ISOv4Plugin/Mappers/LoggedDataMappers/Import/SpatialRecordMapper.cs +++ b/ISOv4Plugin/Mappers/LoggedDataMappers/Import/SpatialRecordMapper.cs @@ -49,7 +49,11 @@ public IEnumerable Map(IEnumerable isoSpatialRows, pan.AllocationStamp.Start.Value.Minute == firstSpatialRow.TimeStart.Minute && pan.AllocationStamp.Start.Value.Second == firstSpatialRow.TimeStart.Second) { - _effectiveTimeZoneOffset = firstSpatialRow.TimeStart - pan.AllocationStamp.Start.Value; + _effectiveTimeZoneOffset = TaskDataMapper.ValidateTimezoneOffset(firstSpatialRow.TimeStart, pan.AllocationStamp.Start.Value); + if (!_effectiveTimeZoneOffset.HasValue) + { + _taskDataMapper.AddError($"Unable to determine effective timezone offset from comparison of spatial record and product allocation timestamps. Monitor date/time setting may be invalid."); + } } } } @@ -202,22 +206,24 @@ private bool GovernsTimestamp(ISOProductAllocation p, SpatialRecord spatialRecor // Comparing DateTime values with different Kind values leads to inaccurate results. // Convert DateTimes to UTC if possible before comparing them - private DateTime? ToUtc(DateTime? nullableDateTime, TimeSpan? timezoneOffset) + private static DateTime? ToUtc(DateTime? nullableDateTime, TimeSpan? timezoneOffset) { return nullableDateTime.HasValue ? ToUtc(nullableDateTime.Value, timezoneOffset) : nullableDateTime; } - private DateTime ToUtc(DateTime dateTime, TimeSpan? timezoneOffset) + private static DateTime ToUtc(DateTime dateTime, TimeSpan? timezoneOffset) { if (dateTime.Kind == DateTimeKind.Utc) return dateTime; - if (_taskDataMapper.TimezoneOffset.HasValue) + if (timezoneOffset.HasValue) { // Convert from local time to UTC using the timezone offset. + // We're relying on the upstream guard ensuring the timezone offset is + // within 14 hours var localTime = new DateTimeOffset(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond, - _taskDataMapper.TimezoneOffset.Value); + timezoneOffset.Value); DateTime utc = localTime.UtcDateTime; return utc; } diff --git a/ISOv4Plugin/Mappers/TaskDataMapper.cs b/ISOv4Plugin/Mappers/TaskDataMapper.cs index 99517d7c..5116f37f 100644 --- a/ISOv4Plugin/Mappers/TaskDataMapper.cs +++ b/ISOv4Plugin/Mappers/TaskDataMapper.cs @@ -157,6 +157,27 @@ public void AddError(string error, string id = null, string source = null, strin Errors.Add(new Error() { Description = error, Id = id, Source = source, StackTrace = stackTrace }); } + /// + /// Validates and processes a timezone offset calculated from local and UTC times. + /// Calculates offset = localTime - utcTime, rounds to nearest minute, and validates it's within ±14 hours. + /// + /// The validated TimeSpan offset, or null if the offset is outside the acceptable ±14 hour range. + public static TimeSpan? ValidateTimezoneOffset(DateTime localTime, DateTime utcTime) + { + TimeSpan offset = localTime - utcTime; + // Round offset to nearest minute for use in timezone offset + offset = TimeSpan.FromMinutes(Math.Round(offset.TotalMinutes)); + // DateTimeOffset requires the offset to be within ±14 hours + if (Math.Abs(offset.TotalHours) <= 14) + { + return offset; + } + else + { + return null; + } + } + public ISO11783_TaskData Export(ApplicationDataModel.ADM.ApplicationDataModel adm) { AdaptDataModel = adm; diff --git a/ISOv4Plugin/Mappers/TimeLogMapper.cs b/ISOv4Plugin/Mappers/TimeLogMapper.cs index 4d39cda4..47430c8a 100644 --- a/ISOv4Plugin/Mappers/TimeLogMapper.cs +++ b/ISOv4Plugin/Mappers/TimeLogMapper.cs @@ -330,11 +330,17 @@ protected IEnumerable ImportTimeLog(ISOTask loggedTask, ISOTimeLo var firstRecord = isoRecords.FirstOrDefault(r => r.GpsUtcDateTime.HasValue && r.GpsUtcDate != ushort.MaxValue && r.GpsUtcDate != 0); if (firstRecord != null) { - //Local - UTC = Delta. This value will be rough based on the accuracy of the clock settings + // Local - UTC = Delta. This value will be rough based on the accuracy of the clock settings // but will expose the ability to derive the UTC times from the exported local times. - TimeSpan offset = firstRecord.TimeStart - firstRecord.GpsUtcDateTime.Value; - // Round offset to nearest minute for use in timezone offset - TaskDataMapper.TimezoneOffset = TimeSpan.FromMinutes(Math.Round(offset.TotalMinutes)); + TimeSpan? offset = TaskDataMapper.ValidateTimezoneOffset(firstRecord.TimeStart, firstRecord.GpsUtcDateTime.Value); + if (offset.HasValue) + { + TaskDataMapper.TimezoneOffset = offset.Value; + } + else + { + TaskDataMapper.AddError($"GPS time offset of {firstRecord.TimeStart - firstRecord.GpsUtcDateTime.Value} is outside the acceptable range. Monitor date/time setting is probably invalid. Product allocation logic may be impacted."); + } } } }