Cleaned up the frame.ts by splitting payload parsing to subpackages

This commit is contained in:
2026-03-20 10:38:36 +01:00
parent 1aa8eb363f
commit 75e31c2008
26 changed files with 2695 additions and 2429 deletions

189
src/timestamp.ts Normal file
View File

@@ -0,0 +1,189 @@
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;