Skip to content
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,6 @@ FakesAssemblies/

# Jetbrains Rider
.idea/

# VSCode
.vscode/
14 changes: 0 additions & 14 deletions .vscode/launch.json

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ public IEnumerable<SpatialRecord> Map(IEnumerable<ISOSpatialRow> 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.");
}
}
}
}
Expand Down Expand Up @@ -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;
}
Expand Down
21 changes: 21 additions & 0 deletions ISOv4Plugin/Mappers/TaskDataMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}

/// <summary>
/// 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.
/// </summary>
/// <returns>The validated TimeSpan offset, or null if the offset is outside the acceptable ±14 hour range.</returns>
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;
Expand Down
14 changes: 10 additions & 4 deletions ISOv4Plugin/Mappers/TimeLogMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,11 +330,17 @@ protected IEnumerable<OperationData> 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.");
}
}
}
}
Expand Down
Loading