Skip to content

Commit

Permalink
Add support for next forecast by remark (#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
fkrauthan authored Jan 2, 2025
1 parent 4a0d75d commit 6a89fdd
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 5 deletions.
25 changes: 23 additions & 2 deletions src/command/remark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ import {
IPrecipitationEndRemark,
PrecipitationEndCommand,
} from "./remark/PrecipitationEndCommand";
import {
INextForecastByRemark,
INextForecastByRemarkDated,
NextForecastByCommand,
} from "command/remark/NextForecastByCommand";

export type {
ICeilingHeightRemark,
Expand Down Expand Up @@ -184,6 +189,8 @@ export type {
IWaterEquivalentSnowRemark,
IWindPeakCommandRemark,
IWindShiftFropaRemark,
INextForecastByRemark,
INextForecastByRemarkDated,
};

export interface IBaseRemark {
Expand Down Expand Up @@ -242,6 +249,7 @@ export class RemarkCommandSupplier {
new SnowDepthCommand(locale),
new SunshineDurationCommand(locale),
new WaterEquivalentSnowCommand(locale),
new NextForecastByCommand(locale),
];
}

Expand Down Expand Up @@ -308,9 +316,13 @@ export enum RemarkType {
SnowDepth = "SnowDepth",
SunshineDuration = "SunshineDuration",
WaterEquivalentSnow = "WaterEquivalentSnow",

// Canada commands below
NextForecastBy = "NextForecastBy",
}

export type Remark =
// Remark types that are not date based
type RemarkBase =
| IUnknownRemark
| IDefaultCommandRemark
// Regular commands below
Expand All @@ -327,7 +339,6 @@ export type Remark =
| IObscurationRemark
| IPrecipitationAmount24HourRemark
| IPrecipitationAmount36HourRemark
| IPrecipitationAmount36HourRemark
| IPrecipitationBegRemark
| IPrecipitationBegEndRemark
| IPrecipitationEndRemark
Expand All @@ -354,3 +365,13 @@ export type Remark =
| IWindPeakCommandRemark
| IWindShiftRemark
| IWindShiftFropaRemark;

export type RemarkDated =
| RemarkBase
// Canadian commands below
| INextForecastByRemarkDated;

export type Remark =
| RemarkBase
// Canadian commands below
| INextForecastByRemark;
55 changes: 55 additions & 0 deletions src/command/remark/NextForecastByCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { format, _ } from "commons/i18n";
import { UnexpectedParseError } from "commons/errors";
import { IBaseRemark, RemarkType, Remark } from "../remark";
import { Command } from "./Command";

export interface INextForecastByRemark extends IBaseRemark {
type: RemarkType.NextForecastBy;

day: number;
hour: number;
minute: number;
}

export interface INextForecastByRemarkDated extends INextForecastByRemark {
type: RemarkType.NextForecastBy;

date: Date;
}

export class NextForecastByCommand extends Command {
#regex = /^NXT FCST BY (\d{2})(\d{2})(\d{2})Z/;

canParse(code: string): boolean {
return this.#regex.test(code);
}

execute(code: string, remark: Remark[]): [string, Remark[]] {
const matches = code.match(this.#regex);

if (!matches) throw new UnexpectedParseError("Match not found");

const day = +matches[1];
const hour = matches[2];
const minute = matches[3];

const description = format(
_("Remark.Next.Forecast.By", this.locale),
day,
hour,
minute,
);

remark.push({
type: RemarkType.NextForecastBy,
description,
raw: matches[0],

day,
hour: +hour,
minute: +minute,
});

return [code.replace(this.#regex, "").trim(), remark];
}
}
17 changes: 17 additions & 0 deletions src/dates/taf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import {
ITemperatureDated,
} from "model/model";
import { determineReportDate } from "helpers/date";
import { Remark, RemarkDated, RemarkType } from "command/remark";

export type TAFTrendDated = IAbstractTrend &
IBaseTAFTrend & {
validity: IBaseTAFTrend["validity"] & {
start: Date;
end?: Date;
};
remarks: RemarkDated[];
} & (
| {
type: WeatherChangeType.FM;
Expand Down Expand Up @@ -43,6 +45,19 @@ export interface ITAFDated extends ITAF {
maxTemperature?: ITemperatureDated;

trends: TAFTrendDated[];
remarks: RemarkDated[];
}

function remarksDatesHydrator(remarks: Remark[], date: Date): RemarkDated[] {
return remarks.map((remark) => {
if (remark.type === RemarkType.NextForecastBy) {
return {
...remark,
date: determineReportDate(date, remark.day, remark.hour, remark.minute),
};
}
return remark;
});
}

export function tafDatesHydrator(report: ITAF, date: Date): ITAFDated {
Expand Down Expand Up @@ -93,6 +108,7 @@ export function tafDatesHydrator(report: ITAF, date: Date): ITAFDated {
(trend) =>
({
...trend,
remarks: remarksDatesHydrator(trend.remarks, issued),
validity: (() => {
switch (trend.type) {
case WeatherChangeType.FM:
Expand Down Expand Up @@ -123,5 +139,6 @@ export function tafDatesHydrator(report: ITAF, date: Date): ITAFDated {
})(),
}) as TAFTrendDated,
),
remarks: remarksDatesHydrator(report.remarks, issued),
};
}
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export {
IBaseRemark,
IUnknownRemark,
Remark,
RemarkDated,
// Rest of remark types
ICeilingHeightRemark,
ICeilingSecondLocationRemark,
Expand Down Expand Up @@ -52,6 +53,9 @@ export {
IWaterEquivalentSnowRemark,
IWindPeakCommandRemark,
IWindShiftFropaRemark,
// Canadian remarks
INextForecastByRemark,
INextForecastByRemarkDated,
} from "command/remark";
export {
getCompositeForecastForDate,
Expand Down
5 changes: 5 additions & 0 deletions src/locale/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,11 @@ export default {
LGT: "light",
LTG: "lightning",
MOD: "moderate",
Next: {
Forecast: {
By: "next forecast by {0}, {1}:{2}Z"
},
},
NXT: "next",
ON: "on",
Obscuration: "{0} layer at {1} feet composed of {2}",
Expand Down
31 changes: 31 additions & 0 deletions tests/command/remark/NextForecastByCommand.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import en from "locale/en";
import { Remark, RemarkType } from "command/remark";
import { NextForecastByCommand } from "command/remark/NextForecastByCommand";

describe("NextForecastByCommand", () => {
const command = new NextForecastByCommand(en);
const code = "NXT FCST BY 160300Z";

describe(code, () => {
test("canParse", () => {
expect(command.canParse(code)).toBe(true);
});

test("execute", () => {
const [res, remarks] = command.execute(code, []);

expect(res).toBe("");
expect(remarks).toEqual<Remark[]>([
{
type: RemarkType.NextForecastBy,
description: "next forecast by 16, 03:00Z",
raw: code,

day: 16,
hour: 3,
minute: 0,
},
]);
});
});
});
15 changes: 15 additions & 0 deletions tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
parseTAFAsForecast,
WeatherChangeType,
} from "index";
import { INextForecastByRemarkDated } from "command/remark/NextForecastByCommand";

describe("public API", () => {
describe("parseMetar", () => {
Expand Down Expand Up @@ -58,6 +59,20 @@ TAF
);
});

test("sets next forecasted by remark date", () => {
const taf = parseTAF(
`
TAF CYVR 152340Z 1600/1706 29015KT P6SM FEW015 FM162200 28010KT P6SM SKC RMK NXT FCST BY 160300Z
`,
{ issued: new Date("2022-10-22") },
);
expect(taf.trends[0]).toBeDefined();
expect(taf.trends[0].remarks[0]).toBeDefined();
expect(
(taf.trends[0].remarks[0] as INextForecastByRemarkDated).date,
).toEqual(new Date("2022-10-16T03:00:00.000Z"));
});

test("should set maxTemperature, minTemperature with dates", () => {
const taf = parseTAF(
`
Expand Down
14 changes: 11 additions & 3 deletions tests/parser/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { PartialWeatherStatementError } from "commons/errors";

describe("RemarkParser", () => {
(() => {
const code = "Token AO1 End of remark";
const code = "Token AO1 End of remark NXT FCST BY 160300Z";

test(`parses "${code}"`, () => {
const remarks = new RemarkParser(en).parse(code);
Expand All @@ -55,6 +55,14 @@ describe("RemarkParser", () => {
type: RemarkType.Unknown,
raw: "End of remark",
},
{
type: RemarkType.NextForecastBy,
description: "next forecast by 16, 03:00Z",
raw: "NXT FCST BY 160300Z",
day: 16,
hour: 3,
minute: 0,
},
]);
});
})();
Expand Down Expand Up @@ -1191,7 +1199,7 @@ describe("TAFParser", () => {

expect(taf).toBeDefined();
expect(taf.remark).toBeDefined();
expect(taf.remarks).toHaveLength(1);
expect(taf.remarks).toHaveLength(2);
});

test("parse with trend remark", () => {
Expand All @@ -1201,7 +1209,7 @@ describe("TAFParser", () => {

expect(taf.trends).toHaveLength(3);
expect(taf.trends[2].remark).toBeDefined();
expect(taf.trends[2].remarks).toHaveLength(1);
expect(taf.trends[2].remarks).toHaveLength(2);
});

test("parses INTER trend", () => {
Expand Down

0 comments on commit 6a89fdd

Please sign in to comment.