metar-taf-parser 4.0.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -39,9 +39,8 @@ const metar = parseMetar(rawMetarString);
39
39
 
40
40
  // -or-
41
41
 
42
- // Optionally pass a date (approximately when the report was issued, +/- a week)
43
- // to get the issued date on the report:
44
- const datedMetar = parseMetar(rawMetarString, { date: new Date() });
42
+ // Optionally pass the date issued to add it to the report
43
+ const datedMetar = parseMetar(rawMetarString, { issued });
45
44
  ```
46
45
 
47
46
  #### `parseTAF`
@@ -55,9 +54,9 @@ const taf = parseTAF(rawTAFString);
55
54
 
56
55
  // -or-
57
56
 
58
- // Optionally pass a date (approximately when the report was issued, +/- a week)
59
- // to get the report issued and trend validity dates (start/end) on the report:
60
- const datedTAF = parseTAF(rawTAFString, { date: new Date() });
57
+ // Optionally pass the date issued to get the report issued and
58
+ // trend validity dates (start/end) on the report:
59
+ const datedTAF = parseTAF(rawTAFString, { issued });
61
60
  ```
62
61
 
63
62
  ### Higher level parsing: The Forecast abstraction
@@ -72,7 +71,7 @@ Returns a more normalized TAF report. Most notably: while the `parseTAF` functio
72
71
  import { parseTAFAsForecast } from "metar-taf-parser";
73
72
 
74
73
  // You must provide an issued date to use the Forecast abstraction
75
- const report = parseTAFAsForecast(rawTAFString, { date: tafIssuedDate });
74
+ const report = parseTAFAsForecast(rawTAFString, { issued: tafIssuedDate });
76
75
 
77
76
  console.log(report.forecast);
78
77
  ```
@@ -98,7 +97,7 @@ import {
98
97
  getCompositeForecastForDate,
99
98
  } from "metar-taf-parser";
100
99
 
101
- const report = parseTAFAsForecast(rawTAFString, { date: tafIssuedDate });
100
+ const report = parseTAFAsForecast(rawTAFString, { issued: tafIssuedDate });
102
101
 
103
102
  const forecastPerHour = eachHourOfInterval({
104
103
  start: report.start,
package/locale/en.js CHANGED
@@ -207,7 +207,7 @@ var en = {
207
207
  Hourly: "{0}/100 of an inch of precipitation fell in the last hour",
208
208
  },
209
209
  Beg: {
210
- "0": "{0} {1} beginning at {2}:{3} ",
210
+ "0": "{0} {1} beginning at {2}:{3}",
211
211
  End: "{0} {1} beginning at {2}:{3} ending at {4}:{5}",
212
212
  },
213
213
  End: "{0} {1} ending at {2}:{3}",
@@ -1101,7 +1101,29 @@ interface ICloud {
1101
1101
  quantity: CloudQuantity;
1102
1102
  type?: CloudType;
1103
1103
  }
1104
- interface IAbstractWeatherContainer {
1104
+ interface IFlags {
1105
+ /**
1106
+ * Amended TAF
1107
+ */
1108
+ amendment?: true;
1109
+ /**
1110
+ * Amended METAR
1111
+ */
1112
+ auto?: true;
1113
+ /**
1114
+ * Canceled TAF
1115
+ */
1116
+ canceled?: true;
1117
+ /**
1118
+ * Corrected METAR/TAF
1119
+ */
1120
+ corrected?: true;
1121
+ /**
1122
+ * No data
1123
+ */
1124
+ nil?: true;
1125
+ }
1126
+ interface IAbstractWeatherContainer extends IFlags {
1105
1127
  wind?: IWind;
1106
1128
  visibility?: Visibility;
1107
1129
  verticalVisibility?: number;
@@ -1120,11 +1142,11 @@ interface IAbstractValidity {
1120
1142
  * Exclusive for the TS port (because python has `time()` and js does not)
1121
1143
  */
1122
1144
  interface ITime {
1123
- hour: number;
1124
- minute: number;
1145
+ hour?: number;
1146
+ minute?: number;
1125
1147
  }
1126
1148
  interface IAbstractWeatherCode extends IAbstractWeatherContainer, ITime {
1127
- day: number;
1149
+ day?: number;
1128
1150
  airport?: IAirport;
1129
1151
  message: string;
1130
1152
  station: string;
@@ -1138,7 +1160,6 @@ interface IMetar extends IAbstractWeatherCode {
1138
1160
  dewPoint?: number;
1139
1161
  altimeter?: number;
1140
1162
  nosig?: true;
1141
- auto?: true;
1142
1163
  runwaysInfo: IRunwayInfo[];
1143
1164
  /**
1144
1165
  * Not used in North America
@@ -1149,7 +1170,6 @@ interface ITAF extends IAbstractWeatherCode {
1149
1170
  validity: IValidity;
1150
1171
  maxTemperature?: ITemperatureDated;
1151
1172
  minTemperature?: ITemperatureDated;
1152
- amendment?: true;
1153
1173
  trends: TAFTrend[];
1154
1174
  }
1155
1175
  interface IAbstractTrend extends IAbstractWeatherContainer {
@@ -1306,15 +1326,12 @@ interface IMetarTAFParserOptions {
1306
1326
  }
1307
1327
  interface IMetarTAFParserOptionsDated extends IMetarTAFParserOptions {
1308
1328
  /**
1309
- * This date should ideally be the date the report was issued. Otherwise, it
1310
- * can be be +/- one week of the actual report date and work properly.
1311
- *
1312
- * So if you know the report was recently issued, you can pass `new Date()`
1329
+ * This date should be the date the report was issued.
1313
1330
  *
1314
1331
  * This date is needed to create actual timestamps since the report only has
1315
- * day of month, hour, and minute.
1332
+ * day of month, hour, and minute (and sometimes not even that).
1316
1333
  */
1317
- date: Date;
1334
+ issued: Date;
1318
1335
  }
1319
1336
  declare function parseMetar(rawMetar: string, options?: IMetarTAFParserOptions): IMetar;
1320
1337
  declare function parseMetar(rawMetar: string, options?: IMetarTAFParserOptionsDated): IMetarDated;
@@ -1322,4 +1339,4 @@ declare function parseTAF(rawTAF: string, options?: IMetarTAFParserOptions): ITA
1322
1339
  declare function parseTAF(rawTAF: string, options?: IMetarTAFParserOptionsDated): ITAFDated;
1323
1340
  declare function parseTAFAsForecast(rawTAF: string, options: IMetarTAFParserOptionsDated): IForecastContainer;
1324
1341
 
1325
- export { CloudQuantity, CloudType, CommandExecutionError, Descriptive, Direction, Distance, DistanceUnit, Forecast, IAbstractTrend, IAbstractValidity, IAbstractWeatherCode, IAbstractWeatherCodeDated, IAbstractWeatherContainer, IAirport, IBaseRemark, IBaseTAFTrend, ICeilingHeightRemark, ICeilingSecondLocationRemark, ICloud, ICompositeForecast, ICountry, IEndValidity, IFMValidity, IForecastContainer, IHourlyMaximumMinimumTemperatureRemark, IHourlyMaximumTemperatureRemark, IHourlyMinimumTemperatureRemark, IHourlyPrecipitationAmountRemark, IHourlyPressureRemark, IHourlyTemperatureDewPointRemark, IIceAccretionRemark, IMetar, IMetarTAFParserOptions, IMetarTAFParserOptionsDated, IMetarTrend, IMetarTrendTime, IObscurationRemark, IPrecipitationAmount24HourRemark, IPrecipitationAmount36HourRemark, IPrecipitationBegEndRemark, IPrevailingVisibilityRemark, IRunwayInfo, ISeaLevelPressureRemark, ISecondLocationVisibilityRemark, ISectorVisibilityRemark, ISmallHailSizeRemark, ISnowIncreaseRemark, ISnowPelletsRemark, ISunshineDurationRemark, ISurfaceVisibilityRemark, ITAF, ITAFDated, ITemperatureDated, IThunderStormLocationMovingRemark, IThunderStormLocationRemark, ITime, ITornadicActivityBegEndRemark, ITornadicActivityBegRemark, ITornadicActivityEndRemark, ITowerVisibilityRemark, IUnknownRemark, IValidity, IValidityDated, IVariableSkyHeightRemark, IVariableSkyRemark, IVirgaDirectionRemark, IWaterEquivalentSnowRemark, IWeatherCondition, IWind, IWindPeakCommandRemark, IWindShear, IWindShiftFropaRemark, Intensity, InvalidWeatherStatementError, Locale, ParseError, Phenomenon, Remark, RemarkType, RunwayInfoTrend, RunwayInfoUnit, TAFTrend, TAFTrendDated, TimeIndicator, TimestampOutOfBoundsError, UnexpectedParseError, ValueIndicator, Visibility, WeatherChangeType, getCompositeForecastForDate, isWeatherConditionValid, parseMetar, parseTAF, parseTAFAsForecast };
1342
+ export { CloudQuantity, CloudType, CommandExecutionError, Descriptive, Direction, Distance, DistanceUnit, Forecast, IAbstractTrend, IAbstractValidity, IAbstractWeatherCode, IAbstractWeatherCodeDated, IAbstractWeatherContainer, IAirport, IBaseRemark, IBaseTAFTrend, ICeilingHeightRemark, ICeilingSecondLocationRemark, ICloud, ICompositeForecast, ICountry, IEndValidity, IFMValidity, IFlags, IForecastContainer, IHourlyMaximumMinimumTemperatureRemark, IHourlyMaximumTemperatureRemark, IHourlyMinimumTemperatureRemark, IHourlyPrecipitationAmountRemark, IHourlyPressureRemark, IHourlyTemperatureDewPointRemark, IIceAccretionRemark, IMetar, IMetarTAFParserOptions, IMetarTAFParserOptionsDated, IMetarTrend, IMetarTrendTime, IObscurationRemark, IPrecipitationAmount24HourRemark, IPrecipitationAmount36HourRemark, IPrecipitationBegEndRemark, IPrevailingVisibilityRemark, IRunwayInfo, ISeaLevelPressureRemark, ISecondLocationVisibilityRemark, ISectorVisibilityRemark, ISmallHailSizeRemark, ISnowIncreaseRemark, ISnowPelletsRemark, ISunshineDurationRemark, ISurfaceVisibilityRemark, ITAF, ITAFDated, ITemperatureDated, IThunderStormLocationMovingRemark, IThunderStormLocationRemark, ITime, ITornadicActivityBegEndRemark, ITornadicActivityBegRemark, ITornadicActivityEndRemark, ITowerVisibilityRemark, IUnknownRemark, IValidity, IValidityDated, IVariableSkyHeightRemark, IVariableSkyRemark, IVirgaDirectionRemark, IWaterEquivalentSnowRemark, IWeatherCondition, IWind, IWindPeakCommandRemark, IWindShear, IWindShiftFropaRemark, Intensity, InvalidWeatherStatementError, Locale, ParseError, Phenomenon, Remark, RemarkType, RunwayInfoTrend, RunwayInfoUnit, TAFTrend, TAFTrendDated, TimeIndicator, TimestampOutOfBoundsError, UnexpectedParseError, ValueIndicator, Visibility, WeatherChangeType, getCompositeForecastForDate, isWeatherConditionValid, parseMetar, parseTAF, parseTAFAsForecast };
@@ -2064,13 +2064,31 @@ function parseDeliveryTime(timeString) {
2064
2064
  const hour = +timeString.slice(2, 4);
2065
2065
  const minute = +timeString.slice(4, 6);
2066
2066
  if (isNaN(day) || isNaN(hour) || isNaN(minute))
2067
- throw new InvalidWeatherStatementError("Report time is invalid");
2067
+ return;
2068
2068
  return {
2069
2069
  day,
2070
2070
  hour,
2071
2071
  minute,
2072
2072
  };
2073
2073
  }
2074
+ function parseFlags(abstractWeatherCode, flag) {
2075
+ const flags = findFlags(flag);
2076
+ if (flags)
2077
+ Object.assign(abstractWeatherCode, flags);
2078
+ return !!flags;
2079
+ }
2080
+ var FlagMap;
2081
+ (function (FlagMap) {
2082
+ FlagMap["AMD"] = "amendment";
2083
+ FlagMap["AUTO"] = "auto";
2084
+ FlagMap["CNL"] = "canceled";
2085
+ FlagMap["COR"] = "corrected";
2086
+ FlagMap["NIL"] = "nil";
2087
+ })(FlagMap || (FlagMap = {}));
2088
+ function findFlags(flag) {
2089
+ if (flag in FlagMap)
2090
+ return { [FlagMap[flag]]: true };
2091
+ }
2074
2092
  /**
2075
2093
  * This function parses the array containing the remark and concat the array into a string
2076
2094
  * @param container the metar, taf or taf trend to update
@@ -2102,7 +2120,7 @@ function parseTemperature(input) {
2102
2120
  * @param input the string containing the validity
2103
2121
  * @returns Validity object
2104
2122
  */
2105
- function parseValidity(input, date) {
2123
+ function parseValidity(input) {
2106
2124
  const parts = pySplit(input, "/");
2107
2125
  return {
2108
2126
  startDay: +parts[0].slice(0, 2),
@@ -2184,7 +2202,6 @@ class AbstractParser {
2184
2202
  if (digitRegex.test(splitted[i])) {
2185
2203
  if (splitted[i + 1] && smRegex.test(splitted[i + 1])) {
2186
2204
  splitted.splice(i, 2, `${splitted[i]} ${splitted[i + 1]}`);
2187
- i--;
2188
2205
  }
2189
2206
  }
2190
2207
  }
@@ -2274,13 +2291,11 @@ class MetarParser extends AbstractParser {
2274
2291
  };
2275
2292
  let index = 2;
2276
2293
  while (index < metarTab.length) {
2277
- if (!super.generalParse(metar, metarTab[index])) {
2294
+ if (!super.generalParse(metar, metarTab[index]) &&
2295
+ !parseFlags(metar, metarTab[index])) {
2278
2296
  if (metarTab[index] === "NOSIG") {
2279
2297
  metar.nosig = true;
2280
2298
  }
2281
- else if (metarTab[index] === "AUTO") {
2282
- metar.auto = true;
2283
- }
2284
2299
  else if (metarTab[index] === this.TEMPO ||
2285
2300
  metarTab[index] === this.BECMG) {
2286
2301
  const trend = {
@@ -2328,25 +2343,25 @@ class TAFParser extends AbstractParser {
2328
2343
  * @throws ParseError if the message is invalid
2329
2344
  */
2330
2345
  parse(input) {
2331
- let amendment;
2332
2346
  const lines = this.extractLinesTokens(input);
2333
2347
  let index = 0;
2334
2348
  if (lines[0][0] === this.TAF)
2335
2349
  index = 1;
2336
2350
  if (lines[0][1] === this.TAF)
2337
2351
  index = 2;
2338
- if (lines[0][index] === "AMD") {
2339
- amendment = true;
2352
+ const flags = findFlags(lines[0][index]);
2353
+ if (flags) {
2340
2354
  index += 1;
2341
2355
  }
2342
2356
  const station = lines[0][index];
2343
2357
  index += 1;
2344
2358
  const time = parseDeliveryTime(lines[0][index]);
2345
- index += 1;
2359
+ if (time)
2360
+ index += 1;
2346
2361
  const validity = parseValidity(lines[0][index]);
2347
2362
  const taf = {
2348
2363
  station,
2349
- amendment,
2364
+ ...flags,
2350
2365
  ...time,
2351
2366
  validity,
2352
2367
  message: input,
@@ -2365,8 +2380,10 @@ class TAFParser extends AbstractParser {
2365
2380
  taf.maxTemperature = parseTemperature(token);
2366
2381
  else if (token.startsWith(this.TN))
2367
2382
  taf.minTemperature = parseTemperature(token);
2368
- else
2383
+ else {
2384
+ parseFlags(taf, token);
2369
2385
  this.generalParse(taf, token);
2386
+ }
2370
2387
  }
2371
2388
  // Handle the other lines
2372
2389
  for (let i = 1; i < lines.length; i++) {
@@ -2390,7 +2407,6 @@ class TAFParser extends AbstractParser {
2390
2407
  for (let i = 0; i < ls.length; i++) {
2391
2408
  if (/^PROB\d{2}$/.test(ls[i]) && /^TEMPO/.test(ls[i + 1])) {
2392
2409
  ls.splice(i, 2, `${ls[i]} ${ls[i + 1]}`);
2393
- i--;
2394
2410
  }
2395
2411
  }
2396
2412
  return ls;
@@ -2531,6 +2547,9 @@ _RemarkParser_supplier = new WeakMap();
2531
2547
  * @returns
2532
2548
  */
2533
2549
  function determineReportIssuedDate(date, day, hour, minute) {
2550
+ // Some TAF reports do not include a delivery time
2551
+ if (day == null || hour == null)
2552
+ return date;
2534
2553
  const months = [
2535
2554
  setDateComponents(addMonthsUTC(date, -1), day, hour, minute),
2536
2555
  setDateComponents(new Date(date), day, hour, minute),
@@ -2690,8 +2709,8 @@ function parse(rawReport, options, parser, datesHydrator) {
2690
2709
  const lang = options?.locale || en;
2691
2710
  try {
2692
2711
  const report = new parser(lang).parse(rawReport);
2693
- if (options && "date" in options) {
2694
- return datesHydrator(report, options.date);
2712
+ if (options && "issued" in options) {
2713
+ return datesHydrator(report, options.issued);
2695
2714
  }
2696
2715
  return report;
2697
2716
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metar-taf-parser",
3
- "version": "4.0.1",
3
+ "version": "5.0.0",
4
4
  "description": "Parse METAR and TAF reports",
5
5
  "homepage": "https://aeharding.github.io/metar-taf-parser",
6
6
  "keywords": [