From 3883a75a77607e5ff6a883ed47cced3d5f12b854 Mon Sep 17 00:00:00 2001 From: Alex Lee Date: Mon, 2 Dec 2024 11:13:18 -0600 Subject: [PATCH 1/3] Fix directional visibility handling (#1) --- metar_taf_parser/command/common.py | 10 ++++---- metar_taf_parser/tests/command/test_common.py | 23 +++++++++++++++++-- metar_taf_parser/tests/parser/test_parser.py | 2 +- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/metar_taf_parser/command/common.py b/metar_taf_parser/command/common.py index d6efb42..00c2421 100644 --- a/metar_taf_parser/command/common.py +++ b/metar_taf_parser/command/common.py @@ -163,10 +163,10 @@ def can_parse(self, visibility_string: str): class MinimalVisibilityCommand: - regex = r'^(\d{4}[NnEeSsWw])$' + regex = r'^(\d{4})(N|NE|E|SE|S|SW|W|NW)$' def __init__(self): - self._pattern = re.compile(MinimalVisibilityCommand.regex) + self._pattern = re.compile(MinimalVisibilityCommand.regex, re.IGNORECASE) def can_parse(self, visibility_string: str): return self._pattern.search(visibility_string) @@ -179,8 +179,10 @@ def execute(self, container: AbstractWeatherContainer, visibility_string: str): :return: """ matches = self._pattern.search(visibility_string).groups() - container.visibility.min_distance = int(matches[0][0:4]) - container.visibility.min_direction = matches[0][4] + if container.visibility is None: + container.visibility = Visibility() + container.visibility.min_distance = int(matches[0]) + container.visibility.min_direction = matches[1] return True diff --git a/metar_taf_parser/tests/command/test_common.py b/metar_taf_parser/tests/command/test_common.py index 81990cc..d286235 100644 --- a/metar_taf_parser/tests/command/test_common.py +++ b/metar_taf_parser/tests/command/test_common.py @@ -1,8 +1,14 @@ import unittest -from metar_taf_parser.command.common import CloudCommand, MainVisibilityNauticalMilesCommand, WindCommand, CommandSupplier +from metar_taf_parser.command.common import ( + CloudCommand, + CommandSupplier, + MainVisibilityNauticalMilesCommand, + MinimalVisibilityCommand, + WindCommand, +) from metar_taf_parser.model.enum import CloudQuantity, CloudType -from metar_taf_parser.model.model import Metar +from metar_taf_parser.model.model import TAF, Metar class CommonTestCase(unittest.TestCase): @@ -90,6 +96,19 @@ def test_execute(self): metar = Metar() self.assertTrue(command.execute(metar, 'VRB08KT')) + def test_minimal_visibility_command(self): + command = MinimalVisibilityCommand() + + for dir in ['N', 'ne', 's', 'SW']: + with self.subTest(dir): + vis_str = f'3000{dir}' + self.assertTrue(command.can_parse(vis_str)) + + taf = TAF() + self.assertTrue(command.execute(taf, vis_str)) + self.assertEqual(taf.visibility.min_distance, 3000) + self.assertEqual(taf.visibility.min_direction, dir) + def test_main_visibility_nautical_miles_command_with_greater_than(self): command = MainVisibilityNauticalMilesCommand() self.assertTrue(command.can_parse('P3SM')) diff --git a/metar_taf_parser/tests/parser/test_parser.py b/metar_taf_parser/tests/parser/test_parser.py index a409060..2915558 100644 --- a/metar_taf_parser/tests/parser/test_parser.py +++ b/metar_taf_parser/tests/parser/test_parser.py @@ -303,7 +303,7 @@ def test_parse_recent_rain(self): self.assertEqual('LTAE', metar.station) self.assertEqual(1, len(metar.weather_conditions)) self.assertEqual(Intensity.RECENT, metar.weather_conditions[0].intensity) - self.assertEquals(Descriptive.SHOWERS, metar.weather_conditions[0].descriptive) + self.assertEqual(Descriptive.SHOWERS, metar.weather_conditions[0].descriptive) self.assertEqual(1, len(metar.weather_conditions[0].phenomenons)) self.assertEqual(Phenomenon.RAIN, metar.weather_conditions[0].phenomenons[0]) From 72557babe73ce20cd444d1374a0771a96fe2d37f Mon Sep 17 00:00:00 2001 From: Benyakir Horowitz Date: Thu, 7 Aug 2025 08:49:25 +0200 Subject: [PATCH 2/3] docs(parser): update comment for TAF time string parsing Improve documentation in parse_delivery_time function to clarify the issue with validity time strings being parsed as delivery time. --- metar_taf_parser/parser/parser.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/metar_taf_parser/parser/parser.py b/metar_taf_parser/parser/parser.py index c328160..7c7007b 100644 --- a/metar_taf_parser/parser/parser.py +++ b/metar_taf_parser/parser/parser.py @@ -18,6 +18,11 @@ def parse_delivery_time(abstract_weather_code, time_string): Parses the delivery time of a METAR/TAF. It will return False if it is not a delivery time but a validity time. If the delivery time is not specified, we can assume the start of the validity time is the delivery time. + + This occurred in the line TEMPO 2308/2312 9999/8000 RA/DZ BKN020, where the delivery time is + 2300/2312, but the validity time that follows, 9999/9000 was parsed as a delivery time, + causing the parser to give erroneous results. + :param abstract_weather_code: The TAF or METAR object :param time_string: The string representing the delivery time :return: None From af786f3ccd9a0258afdba19ad4a237648c4e9444 Mon Sep 17 00:00:00 2001 From: Benyakir Horowitz Date: Tue, 9 Sep 2025 23:42:10 -0600 Subject: [PATCH 3/3] Change _extracT_lines_tokens to use raw string for repl arg --- metar_taf_parser/parser/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metar_taf_parser/parser/parser.py b/metar_taf_parser/parser/parser.py index 7c7007b..395ed34 100644 --- a/metar_taf_parser/parser/parser.py +++ b/metar_taf_parser/parser/parser.py @@ -318,7 +318,7 @@ def _extract_lines_tokens(self, taf_code: str): """ single_line = taf_code.replace('\n', ' ') clean_line = re.sub(r'\s{2,}', ' ', single_line) - lines = re.sub(r'\s(PROB\d{2}\sTEMPO|TEMPO|INTER|BECMG|FM(?![A-Z]{2}\s)|PROB)', '\n\g<1>', clean_line).splitlines() + lines = re.sub(r'\s(PROB\d{2}\sTEMPO|TEMPO|INTER|BECMG|FM(?![A-Z]{2}\s)|PROB)', r'\n\g<1>', clean_line).splitlines() lines_token = [self.tokenize(line) for line in lines] if len(lines_token) > 1: