diff --git a/metarParser-parsers/src/main/java/io/github/mivek/parser/MetarParser.java b/metarParser-parsers/src/main/java/io/github/mivek/parser/MetarParser.java index d055ecdb..b62f8cb5 100644 --- a/metarParser-parsers/src/main/java/io/github/mivek/parser/MetarParser.java +++ b/metarParser-parsers/src/main/java/io/github/mivek/parser/MetarParser.java @@ -83,7 +83,7 @@ public Metar parse(final String code) throws ParseException { } /** - * Parses the report-type prefix, station and delivery time. + * Parses the report-type prefix, the report modifiers, the station and the delivery time. * * @param m the metar being built. * @param metarTab the tokenized message. @@ -101,6 +101,8 @@ private int parseIdentification(final Metar m, final String[] metarTab, final St m.setReportType(io.github.mivek.enums.ReportType.SPECI); startIndex = 1; } + // a modifier may follow the report type, as in "METAR COR LFPG 081130Z" + startIndex = parseLeadingFlags(m, metarTab, startIndex); } if (startIndex >= metarTab.length) { throw new ParseException(ErrorCodes.ERROR_CODE_INVALID_MESSAGE, ParseErrorType.STATION, null, startIndex); @@ -112,11 +114,31 @@ private int parseIdentification(final Metar m, final String[] metarTab, final St m.setStation(stationToken); m.setAirport(getAirportSupplier().get(stationToken)); m.setMessage(code); - if (startIndex + 1 >= metarTab.length) { - throw new ParseException(ErrorCodes.ERROR_CODE_INVALID_MESSAGE, ParseErrorType.DELIVERY_TIME, null, startIndex + 1); + // it may also sit between the station and the delivery time, as in "LFPG COR 081130Z" + int timeIndex = parseLeadingFlags(m, metarTab, startIndex + 1); + if (timeIndex >= metarTab.length) { + throw new ParseException(ErrorCodes.ERROR_CODE_INVALID_MESSAGE, ParseErrorType.DELIVERY_TIME, null, timeIndex); } - parseDeliveryTime(m, metarTab[startIndex + 1], startIndex + 1); - return startIndex + 2; + parseDeliveryTime(m, metarTab[timeIndex], timeIndex); + return timeIndex + 1; + } + + /** + * Consumes the report modifiers (COR, AMD, ...) preceding the delivery time. Modifiers placed + * after the delivery time are consumed by the body of the message instead. + * + * @param m the metar being built. + * @param metarTab the tokenized message. + * @param index the index to start at. + * @return the index of the first token that is not a modifier. + */ + private int parseLeadingFlags(final Metar m, final String[] metarTab, final int index) { + int flagIndex = index; + // index 0 holds the report type or the station, never a modifier + while (flagIndex > 0 && flagIndex < metarTab.length && parseFlags(m, metarTab[flagIndex])) { + flagIndex++; + } + return flagIndex; } /** diff --git a/metarParser-parsers/src/test/java/io/github/mivek/parser/MetarParserTest.java b/metarParser-parsers/src/test/java/io/github/mivek/parser/MetarParserTest.java index 17e4f082..f27151e3 100644 --- a/metarParser-parsers/src/test/java/io/github/mivek/parser/MetarParserTest.java +++ b/metarParser-parsers/src/test/java/io/github/mivek/parser/MetarParserTest.java @@ -507,4 +507,67 @@ void testParseWithoutDeliveryTimeThrowsDeliveryTimeError() { assertEquals(ParseErrorType.DELIVERY_TIME, e.getType()); } + @Test + void testParseWithCorrectionBetweenStationAndDeliveryTime() throws ParseException { + Metar m = parser.parse("MRLB COR 071700Z 11013KT 9999 FEW045 34/22 A2983"); + + assertTrue(m.isCorrected()); + assertEquals("MRLB", m.getStation()); + assertEquals(Integer.valueOf(7), m.getDay()); + assertEquals(17, m.getTime().getHour()); + assertEquals(0, m.getTime().getMinute()); + assertEquals(Integer.valueOf(34), m.getTemperature()); + } + + @Test + void testParseWithCorrectionBetweenReportTypeAndStation() throws ParseException { + Metar m = parser.parse("METAR COR LFPG 081130Z 00000KT 0350 FG SCT000 M01/M01 Q1026"); + + assertTrue(m.isCorrected()); + assertEquals(ReportType.METAR, m.getReportType()); + assertEquals("LFPG", m.getStation()); + assertEquals(Integer.valueOf(8), m.getDay()); + assertEquals(11, m.getTime().getHour()); + assertEquals(30, m.getTime().getMinute()); + } + + @Test + void testParseWithCorrectionAfterDeliveryTime() throws ParseException { + Metar m = parser.parse("MRLB 071700Z COR 11013KT 9999 FEW045 34/22 A2983"); + + assertTrue(m.isCorrected()); + assertEquals(Integer.valueOf(7), m.getDay()); + assertEquals(Integer.valueOf(34), m.getTemperature()); + } + + @Test + void testParseWithoutCorrectionIsNotFlaggedAsCorrected() throws ParseException { + Metar m = parser.parse("MRLB 071700Z 11013KT 9999 FEW045 34/22 A2983"); + + assertFalse(m.isCorrected()); + assertEquals(Integer.valueOf(7), m.getDay()); + } + + @Test + void testParseWithModifierButWithoutDeliveryTimeThrowsDeliveryTimeError() { + ParseException e = assertThrows(ParseException.class, () -> parser.parse("LFPG COR")); + assertEquals(ParseErrorType.DELIVERY_TIME, e.getType()); + assertEquals(2, e.getPosition()); + } + + @Test + void testParseWithModifierButWithoutStationThrowsStationError() { + ParseException e = assertThrows(ParseException.class, () -> parser.parse("METAR COR")); + assertEquals(ParseErrorType.STATION, e.getType()); + assertEquals(2, e.getPosition()); + } + + @Test + void testParseKeepsAutoAsStationWhenItLeadsTheMessage() { + // a modifier is only expected after the report type or the station, never in first position + ParseException e = assertThrows(ParseException.class, () -> parser.parse("AUTO 11013KT 9999")); + assertEquals(ParseErrorType.DELIVERY_TIME, e.getType()); + assertEquals("11013KT", e.getOffendingToken()); + } + }