import { FieldType, Segment } from "@hamradio/packet"; import { ITimestamp } from "./frame.types"; export class Timestamp implements ITimestamp { day?: number; month?: number; hours: number; minutes: number; seconds?: number; format: "DHM" | "HMS" | "MDHM"; zulu?: boolean; constructor( hours: number, minutes: number, format: "DHM" | "HMS" | "MDHM", options: { day?: number; month?: number; seconds?: number; zulu?: boolean; } = {} ) { this.hours = hours; this.minutes = minutes; this.format = format; this.day = options.day; this.month = options.month; this.seconds = options.seconds; this.zulu = options.zulu; } /** * Convert APRS timestamp to JavaScript Date object * Note: APRS timestamps don't include year, so we use current year * For DHM format, we find the most recent occurrence of that day * For HMS format, we use current date * For MDHM format, we use the specified month/day in current year */ toDate(): Date { const now = new Date(); if (this.format === "DHM") { // Day-Hour-Minute format (UTC) // Find the most recent occurrence of this day const currentYear = this.zulu ? now.getUTCFullYear() : now.getFullYear(); const currentMonth = this.zulu ? now.getUTCMonth() : now.getMonth(); let date: Date; if (this.zulu) { date = new Date(Date.UTC(currentYear, currentMonth, this.day!, this.hours, this.minutes, 0, 0)); } else { date = new Date(currentYear, currentMonth, this.day!, this.hours, this.minutes, 0, 0); } // If the date is in the future, it's from last month if (date > now) { if (this.zulu) { date = new Date(Date.UTC(currentYear, currentMonth - 1, this.day!, this.hours, this.minutes, 0, 0)); } else { date = new Date(currentYear, currentMonth - 1, this.day!, this.hours, this.minutes, 0, 0); } } return date; } else if (this.format === "HMS") { // Hour-Minute-Second format (UTC) // Use current date if (this.zulu) { const date = new Date(); date.setUTCHours(this.hours, this.minutes, this.seconds || 0, 0); // If time is in the future, it's from yesterday if (date > now) { date.setUTCDate(date.getUTCDate() - 1); } return date; } else { const date = new Date(); date.setHours(this.hours, this.minutes, this.seconds || 0, 0); if (date > now) { date.setDate(date.getDate() - 1); } return date; } } else { // MDHM format: Month-Day-Hour-Minute (local time) const currentYear = now.getFullYear(); let date = new Date(currentYear, (this.month || 1) - 1, this.day!, this.hours, this.minutes, 0, 0); // If date is in the future, it's from last year if (date > now) { date = new Date(currentYear - 1, (this.month || 1) - 1, this.day!, this.hours, this.minutes, 0, 0); } return date; } } static fromString( str: string, withStructure: boolean = false ): { timestamp: Timestamp | undefined; segment?: Segment; } { if (str.length !== 7) return { timestamp: undefined }; const timeType = str.charAt(6); if (timeType === "z") { // DHM format: Day-Hour-Minute (UTC) const timestamp = new Timestamp(parseInt(str.substring(2, 4), 10), parseInt(str.substring(4, 6), 10), "DHM", { day: parseInt(str.substring(0, 2), 10), zulu: true }); const segment = withStructure ? { name: "timestamp", data: new TextEncoder().encode(str).buffer, isString: true, fields: [ { type: FieldType.STRING, name: "day (DD)", length: 2 }, { type: FieldType.STRING, name: "hour (HH)", length: 2 }, { type: FieldType.STRING, name: "minute (MM)", length: 2 }, { type: FieldType.CHAR, name: "timezone indicator", length: 1 } ] } : undefined; return { timestamp, segment }; } else if (timeType === "h") { // HMS format: Hour-Minute-Second (UTC) const timestamp = new Timestamp(parseInt(str.substring(0, 2), 10), parseInt(str.substring(2, 4), 10), "HMS", { seconds: parseInt(str.substring(4, 6), 10), zulu: true }); const segment = withStructure ? { name: "timestamp", data: new TextEncoder().encode(str).buffer, isString: true, fields: [ { type: FieldType.STRING, name: "hour (HH)", length: 2 }, { type: FieldType.STRING, name: "minute (MM)", length: 2 }, { type: FieldType.STRING, name: "second (SS)", length: 2 }, { type: FieldType.CHAR, name: "timezone indicator", length: 1 } ] } : undefined; return { timestamp, segment }; } else if (timeType === "/") { // MDHM format: Month-Day-Hour-Minute (local) const timestamp = new Timestamp(parseInt(str.substring(4, 6), 10), parseInt(str.substring(6, 8), 10), "MDHM", { month: parseInt(str.substring(0, 2), 10), day: parseInt(str.substring(2, 4), 10), zulu: false }); const segment = withStructure ? { name: "timestamp", data: new TextEncoder().encode(str).buffer, isString: true, fields: [ { type: FieldType.STRING, name: "month (MM)", length: 2 }, { type: FieldType.STRING, name: "day (DD)", length: 2 }, { type: FieldType.STRING, name: "hour (HH)", length: 2 }, { type: FieldType.STRING, name: "minute (MM)", length: 2 }, { type: FieldType.CHAR, name: "timezone indicator", length: 1 } ] } : undefined; return { timestamp, segment }; } return { timestamp: undefined }; } } export default Timestamp;