Skip to content

Commit 587e115

Browse files
authored
Merge pull request #73 from springblock-org/bh/793-error-parsing-taf-time-string
fix: Fix parsing of TAF if the delivery time is absent
2 parents 2b68fa1 + 9378378 commit 587e115

File tree

4 files changed

+73
-19
lines changed

4 files changed

+73
-19
lines changed

metar_taf_parser/command/common.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,10 @@ def can_parse(self, visibility_string: str):
163163

164164

165165
class MinimalVisibilityCommand:
166-
regex = r'^(\d{4}[NnEeSsWw])$'
166+
regex = r'^(\d{4})(N|NE|E|SE|S|SW|W|NW)$'
167167

168168
def __init__(self):
169-
self._pattern = re.compile(MinimalVisibilityCommand.regex)
169+
self._pattern = re.compile(MinimalVisibilityCommand.regex, re.IGNORECASE)
170170

171171
def can_parse(self, visibility_string: str):
172172
return self._pattern.search(visibility_string)
@@ -179,8 +179,10 @@ def execute(self, container: AbstractWeatherContainer, visibility_string: str):
179179
:return:
180180
"""
181181
matches = self._pattern.search(visibility_string).groups()
182-
container.visibility.min_distance = int(matches[0][0:4])
183-
container.visibility.min_direction = matches[0][4]
182+
if container.visibility is None:
183+
container.visibility = Visibility()
184+
container.visibility.min_distance = int(matches[0])
185+
container.visibility.min_direction = matches[1]
184186
return True
185187

186188

metar_taf_parser/parser/parser.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,22 @@
1515

1616
def parse_delivery_time(abstract_weather_code, time_string):
1717
"""
18-
Parses the delivery time of a METAR/TAF
18+
Parses the delivery time of a METAR/TAF. It will return False
19+
if it is not a delivery time but a validity time. If the delivery time
20+
is not specified, we can assume the start of the validity time is the delivery time.
1921
:param abstract_weather_code: The TAF or METAR object
2022
:param time_string: The string representing the delivery time
2123
:return: None
2224
"""
23-
abstract_weather_code.day = int(time_string[0:2])
24-
abstract_weather_code.time = time(int(time_string[2:4]), int(time_string[4:6]))
25+
if len(time_string) > 6 and "/" in time_string:
26+
# This is a validity string, not a delivery time.
27+
abstract_weather_code.day = int(time_string[0:2])
28+
abstract_weather_code.time = time(hour=int(time_string[2:4]))
29+
return False
30+
else:
31+
abstract_weather_code.day = int(time_string[0:2])
32+
abstract_weather_code.time = time(int(time_string[2:4]), int(time_string[4:6]))
33+
return True
2534

2635

2736
def _parse_flags(abstract_weather_code, flag_string):
@@ -246,12 +255,7 @@ def __init__(self):
246255
self._validity_pattern = re.compile(r'^\d{4}/\d{4}$')
247256
self._taf_command_supplier = TAFCommandSupplier()
248257

249-
def parse(self, input: str):
250-
"""
251-
Parses a message into a TAF
252-
:param input: the message to parse
253-
:return: a TAF object or None if the message is invalid
254-
"""
258+
def _parse_initial_taf(self, input: str):
255259
taf = TAF()
256260
lines = self._extract_lines_tokens(input)
257261
if TAFParser.TAF != lines[0][0]:
@@ -265,10 +269,20 @@ def parse(self, input: str):
265269
taf.station = lines[0][index]
266270
index += 1
267271
taf.message = input
268-
parse_delivery_time(taf, lines[0][index])
269-
index += 1
272+
if parse_delivery_time(taf, lines[0][index]):
273+
index += 1
270274
taf.validity = _parse_validity(lines[0][index])
271275

276+
return taf, lines, index
277+
278+
def parse(self, input: str):
279+
"""
280+
Parses a message into a TAF
281+
:param input: the message to parse
282+
:return: a TAF object or None if the message is invalid
283+
"""
284+
taf, lines, index = self._parse_initial_taf(input)
285+
272286
for i in range(index + 1, len(lines[0])):
273287
token = lines[0][i]
274288
command = self._taf_command_supplier.get(token)
@@ -311,7 +325,7 @@ def _extract_lines_tokens(self, taf_code: str):
311325
lines_token[len(lines) - 1] = list(filter(lambda x: not x.startswith(TAFParser.TX) and not x.startswith(TAFParser.TN), last_line))
312326
return lines_token
313327

314-
def _parse_line(self, taf: TAF, line_tokens: list):
328+
def _parse_line(self, taf: 'TAF', line_tokens: list):
315329
"""
316330
Parses the tokens of the line and updates the TAF object.
317331
:param taf: TAF object to update

metar_taf_parser/tests/command/test_common.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import unittest
22

3-
from metar_taf_parser.command.common import CloudCommand, MainVisibilityNauticalMilesCommand, WindCommand, CommandSupplier
3+
from metar_taf_parser.command.common import (
4+
CloudCommand,
5+
CommandSupplier,
6+
MainVisibilityNauticalMilesCommand,
7+
MinimalVisibilityCommand,
8+
WindCommand,
9+
)
410
from metar_taf_parser.model.enum import CloudQuantity, CloudType
5-
from metar_taf_parser.model.model import Metar
11+
from metar_taf_parser.model.model import TAF, Metar
612

713

814
class CommonTestCase(unittest.TestCase):
@@ -90,6 +96,19 @@ def test_execute(self):
9096
metar = Metar()
9197
self.assertTrue(command.execute(metar, 'VRB08KT'))
9298

99+
def test_minimal_visibility_command(self):
100+
command = MinimalVisibilityCommand()
101+
102+
for dir in ['N', 'ne', 's', 'SW']:
103+
with self.subTest(dir):
104+
vis_str = f'3000{dir}'
105+
self.assertTrue(command.can_parse(vis_str))
106+
107+
taf = TAF()
108+
self.assertTrue(command.execute(taf, vis_str))
109+
self.assertEqual(taf.visibility.min_distance, 3000)
110+
self.assertEqual(taf.visibility.min_direction, dir)
111+
93112
def test_main_visibility_nautical_miles_command_with_greater_than(self):
94113
command = MainVisibilityNauticalMilesCommand()
95114
self.assertTrue(command.can_parse('P3SM'))

metar_taf_parser/tests/parser/test_parser.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ def test_parse_recent_rain(self):
303303
self.assertEqual('LTAE', metar.station)
304304
self.assertEqual(1, len(metar.weather_conditions))
305305
self.assertEqual(Intensity.RECENT, metar.weather_conditions[0].intensity)
306-
self.assertEquals(Descriptive.SHOWERS, metar.weather_conditions[0].descriptive)
306+
self.assertEqual(Descriptive.SHOWERS, metar.weather_conditions[0].descriptive)
307307
self.assertEqual(1, len(metar.weather_conditions[0].phenomenons))
308308
self.assertEqual(Phenomenon.RAIN, metar.weather_conditions[0].phenomenons[0])
309309

@@ -334,6 +334,25 @@ def test_parse_temperature_min(self):
334334

335335
class TAFParserTestCase(unittest.TestCase):
336336

337+
def test_parse_without_delivery_time(self):
338+
code = """
339+
TAF KNYL 2603/2703 20006KT 9999 SKC QNH2974INS
340+
FM261000 14004KT 9999 SKC QNH2977INS
341+
FM261700 17007KT 9999 SKC QNH2974INS
342+
FM262100 19013KT 9999 SKC QNH2967INS AUTOMATED SENSOR METWATCH 2606 TIL 2614 TX42/2623Z TN24/2614Z
343+
"""
344+
taf = TAFParser().parse(code)
345+
346+
self.assertEqual('KNYL', taf.station)
347+
self.assertEqual(26, taf.day)
348+
self.assertEqual(3, taf.time.hour)
349+
self.assertEqual(0, taf.time.minute)
350+
351+
self.assertEqual(26, taf.validity.start_day)
352+
self.assertEqual(3, taf.validity.start_hour)
353+
self.assertEqual(27, taf.validity.end_day)
354+
self.assertEqual(3, taf.validity.end_hour)
355+
337356
def test_parse_with_invalid_line_breaks(self):
338357
code = 'TAF LFPG 150500Z 1506/1612 17005KT 6000 SCT012 \n' + 'TEMPO 1506/1509 3000 BR BKN006 PROB40 \n' + 'TEMPO 1506/1508 0400 BCFG BKN002 PROB40 \n' + 'TEMPO 1512/1516 4000 -SHRA FEW030TCU BKN040 \n' + 'BECMG 1520/1522 CAVOK \n' + 'TEMPO 1603/1608 3000 BR BKN006 PROB40 \n TEMPO 1604/1607 0400 BCFG BKN002 TX17/1512Z TN07/1605Z'
339358

0 commit comments

Comments
 (0)