agmission/server/helpers/application_datetime.js

119 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
const moment = require('moment-timezone');
const tzLookup = require('tz-lookup');
function isFiniteNumber(value) {
return typeof value === 'number' && Number.isFinite(value);
}
function parseCompactDateTime(dateTimeString) {
const match = /^([0-9]{8})T([0-9]{6})$/.exec(dateTimeString || '');
if (!match) return null;
return {
datePart: match[1],
timePart: match[2]
};
}
function resolveTimezoneName(lat, lon) {
if (!isFiniteNumber(lat) || !isFiniteNumber(lon)) return 'UTC';
try {
return tzLookup(lat, lon) || 'UTC';
} catch (err) {
return 'UTC';
}
}
function getUtcOffsetMinutesFromLocation(lat, lon, dateLabel) {
const timezoneName = resolveTimezoneName(lat, lon);
if (timezoneName === 'UTC') return 0;
const referenceDate = dateLabel
? moment.utc(dateLabel, ['YYYYMMDD', 'YYYY-MM-DD'], true).add(12, 'hours')
: moment.utc();
if (!referenceDate.isValid()) return 0;
return moment.tz(referenceDate.toDate(), timezoneName).utcOffset();
}
function getDateLabelFromDateTime(dateTimeString) {
if (!dateTimeString) return null;
const compact = parseCompactDateTime(dateTimeString);
if (compact) return compact.datePart;
const isoMoment = moment.utc(dateTimeString, moment.ISO_8601, true);
if (isoMoment.isValid()) return isoMoment.format('YYYYMMDD');
return null;
}
function toUtcDateFromAppDateTime(dateTimeString, utcOffsetMinutes = 0) {
if (!dateTimeString) return null;
const compact = parseCompactDateTime(dateTimeString);
if (!compact) {
const isoMoment = moment.utc(dateTimeString, moment.ISO_8601, true);
if (isoMoment.isValid()) return isoMoment.toDate();
return null;
}
const hours = Number(compact.timePart.slice(0, 2));
const minutes = Number(compact.timePart.slice(2, 4));
const seconds = Number(compact.timePart.slice(4, 6));
const utcSecondsOfDay = (hours * 3600) + (minutes * 60) + seconds;
// AgNav hybrid format:
// datePart = LOCAL mission date assigned by the device (not derived from GPS)
// timePart = GPS UTC time-of-day (already UTC, no conversion needed)
//
// timePart is placed directly onto a UTC date. The only question is: which UTC
// calendar date? Compute how timePart translates to local time; if the result
// crosses midnight the UTC date must be shifted by ±1 day relative to datePart:
//
// localSecondsOfDay = utcSecondsOfDay + offsetSeconds
// < 0 → local rolled back to previous day → UTC date = datePart 1 day
// ≥ 86400 → local rolled forward to next day → UTC date = datePart + 1 day
// otherwise→ UTC and local calendar dates align → no shift
const offsetSeconds = utcOffsetMinutes * 60;
const localSecondsOfDay = utcSecondsOfDay + offsetSeconds;
let dateShift = 0;
if (localSecondsOfDay < 0) dateShift = -1; // local rolled back → UTC one day earlier
if (localSecondsOfDay >= 86400) dateShift = +1; // local rolled forward → UTC one day later
return moment.utc(compact.datePart, 'YYYYMMDD', true)
.add(dateShift, 'days')
.add(utcSecondsOfDay, 'seconds')
.toDate();
}
function buildApplicationDateFields({ startDateTime, endDateTime, latitude, longitude }) {
const dateLabel = getDateLabelFromDateTime(startDateTime || endDateTime);
const utcOffset = getUtcOffsetMinutesFromLocation(latitude, longitude, dateLabel);
let startDateTimeUTC = toUtcDateFromAppDateTime(startDateTime, utcOffset);
let endDateTimeUTC = toUtcDateFromAppDateTime(endDateTime, utcOffset);
// Sanity guard: startDateTimeUTC must never exceed endDateTimeUTC.
// With the correct dateShift logic above this should not occur for valid data.
// Kept as a safety net for genuinely corrupt input (e.g. file contains timestamps
// from two different days with the start record from a later day than the end record).
if (startDateTimeUTC && endDateTimeUTC && startDateTimeUTC > endDateTimeUTC) {
startDateTimeUTC = new Date(startDateTimeUTC.getTime() - 86400000);
}
return { utcOffset, startDateTimeUTC, endDateTimeUTC };
}
module.exports = {
resolveTimezoneName,
getUtcOffsetMinutesFromLocation,
getDateLabelFromDateTime,
toUtcDateFromAppDateTime,
buildApplicationDateFields
};