Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions ISOv4Plugin/ExtensionMethods/XmlExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;

namespace AgGateway.ADAPT.ISOv4Plugin.ExtensionMethods
{
public static class XmlExtensions
{
private static readonly Regex _timezoneOffsetRegex = new Regex(@"(\+|-)\d\d:\d\d|Z$", RegexOptions.Compiled);

public static XmlNodeList LoadActualNodes(this XmlNode xmlNode, string externalNodeTag, string baseFolder)
{
if (string.Equals(xmlNode.Name, externalNodeTag, StringComparison.OrdinalIgnoreCase))
Expand Down Expand Up @@ -112,14 +115,36 @@ public static uint GetXmlNodeValueAsUInt(this XmlNode xmlNode, string xPath)
public static DateTime? GetXmlNodeValueAsNullableDateTime(this XmlNode xmlNode, string xPath)
{
string value = GetXmlNodeValue(xmlNode, xPath);
DateTime outValue;
if (DateTime.TryParse(value, out outValue))
if (value == null)
{
return outValue;
return null;
}

// The value has timezone info, parse as DateTimeOffset and convert to UTC DateTime
// Otherwise, parse as local DateTime
if (_timezoneOffsetRegex.IsMatch(value))
{
DateTimeOffset dto;
if (DateTimeOffset.TryParse(value, out dto))
{
return dto.UtcDateTime;
}
else
{
return null;
}
}
else
{
return null;
DateTime outValue;
if (DateTime.TryParse(value, out outValue))
{
return outValue;
}
else
{
return null;
}
}
}

Expand Down
59 changes: 23 additions & 36 deletions ISOv4Plugin/Mappers/LoggedDataMappers/Import/SpatialRecordMapper.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using AgGateway.ADAPT.ApplicationDataModel.LoggedData;
using AgGateway.ADAPT.ApplicationDataModel.Representations;
using AgGateway.ADAPT.ISOv4Plugin.ExtensionMethods;
using AgGateway.ADAPT.ISOv4Plugin.ObjectModel;
using AgGateway.ADAPT.ISOv4Plugin.ISOModels;
using AgGateway.ADAPT.Representation.UnitSystem;
using AgGateway.ADAPT.ISOv4Plugin.Representation;

namespace AgGateway.ADAPT.ISOv4Plugin.Mappers
{
Expand All @@ -27,7 +24,7 @@ public class SpatialRecordMapper : ISpatialRecordMapper
private readonly IWorkingDataMapper _workingDataMapper;
private readonly ISectionMapper _sectionMapper;
private readonly TaskDataMapper _taskDataMapper;
private double? _effectiveTimeZoneOffset;
private TimeSpan? _effectiveTimeZoneOffset;

public SpatialRecordMapper(IRepresentationValueInterpolator representationValueInterpolator, ISectionMapper sectionMapper, IWorkingDataMapper workingDataMapper, TaskDataMapper taskDataMapper)
{
Expand All @@ -52,7 +49,7 @@ 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).TotalHours;
_effectiveTimeZoneOffset = firstSpatialRow.TimeStart - pan.AllocationStamp.Start.Value;
}
}
}
Expand Down Expand Up @@ -191,52 +188,42 @@ private void SetNumericMeterValue(ISOSpatialRow isoSpatialRow, NumericWorkingDat
/// </summary>
private bool GovernsTimestamp(ISOProductAllocation p, SpatialRecord spatialRecord)
{
DateTime? allocationStart = Offset(p.AllocationStamp.Start);
DateTime? allocationStop = p.AllocationStamp.Stop != null ? Offset(p.AllocationStamp.Stop) : null;
DateTime spatialRecordTimestampUtc = ToUtc(spatialRecord.Timestamp);
DateTime? allocationStartUtc = ToUtc(p.AllocationStamp.Start, _effectiveTimeZoneOffset);
DateTime? allocationStopUtc = p.AllocationStamp.Stop != null ?
ToUtc(p.AllocationStamp.Stop, _effectiveTimeZoneOffset) : null;
DateTime spatialRecordTimestampUtc = ToUtc(spatialRecord.Timestamp, _taskDataMapper.TimezoneOffset);

return
ToUtc(allocationStart) <= spatialRecordTimestampUtc &&
(p.AllocationStamp.Stop == null || ToUtc(allocationStop) >= spatialRecordTimestampUtc);
var returnVal =
allocationStartUtc <= spatialRecordTimestampUtc &&
(p.AllocationStamp.Stop == null || allocationStopUtc >= spatialRecordTimestampUtc);

return returnVal;
}

// 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)
private DateTime? ToUtc(DateTime? nullableDateTime, TimeSpan? timezoneOffset)
{
return nullableDateTime.HasValue ? ToUtc(nullableDateTime.Value) : nullableDateTime;
return nullableDateTime.HasValue ? ToUtc(nullableDateTime.Value, timezoneOffset) : nullableDateTime;
}

private DateTime ToUtc(DateTime dateTime)
private DateTime ToUtc(DateTime dateTime, TimeSpan? timezoneOffset)
{
if (dateTime.Kind == DateTimeKind.Utc)
return dateTime;

DateTime utc;
if (dateTime.Kind == DateTimeKind.Local)
{
utc = dateTime.ToUniversalTime();
}
else if (dateTime.Kind == DateTimeKind.Unspecified && _taskDataMapper.GPSToLocalDelta.HasValue)
{
utc = new DateTime(dateTime.AddHours(-_taskDataMapper.GPSToLocalDelta.Value).Ticks, DateTimeKind.Utc);
}
else
if (_taskDataMapper.TimezoneOffset.HasValue)
{
// Nothing left to try; return original value
utc = dateTime;
// Convert from local time to UTC using the timezone offset.
var localTime = new DateTimeOffset(dateTime.Year, dateTime.Month, dateTime.Day,
dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond,
_taskDataMapper.TimezoneOffset.Value);
DateTime utc = localTime.UtcDateTime;
return utc;
}

return utc;
}

private DateTime? Offset(DateTime? input)
{
if (_effectiveTimeZoneOffset.HasValue && input.HasValue)
{
return input.Value.AddHours(_effectiveTimeZoneOffset.Value);
}
return input;
// Return original value
return dateTime;
}
}
}
3 changes: 2 additions & 1 deletion ISOv4Plugin/Mappers/TaskDataMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ public TaskDataMapper(string dataPath, Properties properties, int? taskDataVersi
internal RepresentationMapper RepresentationMapper { get; private set; }
internal Dictionary<int, DdiDefinition> DDIs { get; private set; }
internal DeviceOperationTypes DeviceOperationTypes { get; private set; }
internal double? GPSToLocalDelta { get; set; }
internal double? GPSToLocalDelta => TimezoneOffset?.TotalHours;
internal TimeSpan? TimezoneOffset { get; set; }

CodedCommentListMapper _commentListMapper;
public CodedCommentListMapper CommentListMapper
Expand Down
7 changes: 5 additions & 2 deletions ISOv4Plugin/Mappers/TimeLogMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,11 @@ 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 but will expose the ability to derive the UTC times from the exported local times.
TaskDataMapper.GPSToLocalDelta = (firstRecord.TimeStart - firstRecord.GpsUtcDateTime.Value).TotalHours;
//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));
}
}
}
Expand Down
Loading