Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions hytek_parser/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,17 @@ def int_or_none(value:str|None) -> int|None:
return int(value) if value and value.isdigit() else None

def date_or_none(value: str|None) -> date|None:
"""Safely return either a date from a numeric string value, or None
"""Safely return either a valid date from a numeric string value, or None

Args:
value (str|None): The value to cast to date

Returns:
date|None: Either the parsed date value or None"""
return datetime.strptime(value, "%m%d%Y").date() if value and value.isnumeric() else None
date|None: The parsed date, or None for blank, malformed, or invalid values"""
if not value or not value.isnumeric():
return None
try:
return datetime.strptime(value, "%m%d%Y").date()
except ValueError:
# Some Meet Manager exports contain numeric but impossible dates.
return None
12 changes: 8 additions & 4 deletions hytek_parser/hy3/line_parsers/e_event_parsers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from datetime import datetime
from typing import Any

from hytek_parser._utils import extract, get_age_group, safe_cast, select_from_enum
from hytek_parser._utils import (
date_or_none,
extract,
get_age_group,
safe_cast,
select_from_enum,
)
from hytek_parser.hy3._utils import parse_reaction_time, parse_time, parse_time_or_none
from hytek_parser.hy3.enums import (
Course,
Expand Down Expand Up @@ -121,8 +126,7 @@ def e2_parser(
# col 83-87. Signed; sentinels are blank / 0.00 in any sign spelling.
reaction_time = parse_reaction_time(extract(line, 83, 5))

raw_date = extract(line, 88, 8).strip()
date_ = datetime.strptime(raw_date, "%m%d%Y").date() if raw_date else None
date_ = date_or_none(extract(line, 88, 8).strip())

# Get entry
event_num, event = file.meet.last_event
Expand Down
12 changes: 8 additions & 4 deletions hytek_parser/hy3/line_parsers/f_relay_parsers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from datetime import datetime
from typing import Any

from hytek_parser._utils import extract, get_age_group, safe_cast, select_from_enum
from hytek_parser._utils import (
date_or_none,
extract,
get_age_group,
safe_cast,
select_from_enum,
)
from hytek_parser.hy3._utils import parse_reaction_time, parse_time, parse_time_or_none
from hytek_parser.hy3.enums import (
Course,
Expand Down Expand Up @@ -125,8 +130,7 @@ def f2_parser(
parse_reaction_time(extract(line, 83 + 5 * i, 5)) for i in range(4)
]

raw_date = extract(line, 103, 8).strip()
date_ = datetime.strptime(raw_date, "%m%d%Y").date() if raw_date else None
date_ = date_or_none(extract(line, 103, 8).strip())

# Get entry
event_num, event = file.meet.last_event
Expand Down
14 changes: 10 additions & 4 deletions tests/hy3/line_parsers/test_e_event_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,8 @@ def test_blank_distance_is_zero(self):
self.assertEqual(0.0, event.distance)


class TestE2BlankDateColumn(unittest.TestCase):
"""Bug 1 — MM2 2.0 (and other MM versions) export E2 lines with a blank
date column. e2_parser must populate timing fields and leave date as None
rather than raising ValueError on the empty strptime."""
class TestE2ResultDateColumn(unittest.TestCase):
"""Blank or invalid E2 dates must not discard otherwise valid results."""

def _build_file_with_event(self):
opts = {"default_country": "USA"}
Expand All @@ -140,6 +138,14 @@ def test_e2_parser_with_blank_date_does_not_raise(self):
self.assertEqual(54.79, entry.finals_time)
self.assertIsNone(entry.finals_date)

def test_e2_parser_with_invalid_date_does_not_raise(self):
file, opts = self._build_file_with_event()
e2_line = "E2F 54.79Y 0 2 3 4 12 0 0.00 54.93 0.00 54.79 0.00 99999999 0 25"
result = e2_parser(e2_line, file, opts)
entry = result.meet.events["22X"].last_entry
self.assertEqual(54.79, entry.finals_time)
self.assertIsNone(entry.finals_date)


class TestE1MeetDivision(unittest.TestCase):
"""capture Meet Division at cols 77-79 (e.g. 'VR', 'JV', 'A'/'AA'/numeric)."""
Expand Down
13 changes: 10 additions & 3 deletions tests/hy3/line_parsers/test_f_relay_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
from hytek_parser.hy3.line_parsers.f_relay_parsers import f1_parser, f2_parser, f3_parser


class TestF2BlankDateColumn(unittest.TestCase):
"""Bug 1 — F2 lines with blank date column must populate timing fields
and leave date None instead of raising."""
class TestF2ResultDateColumn(unittest.TestCase):
"""Blank or invalid F2 dates must not discard otherwise valid results."""

def _build_file_with_relay_entry(self):
opts = {"default_country": "USA"}
Expand All @@ -37,6 +36,14 @@ def test_f2_parser_with_blank_date_does_not_raise(self):
self.assertEqual(111.06, entry.finals_time)
self.assertIsNone(entry.finals_date)

def test_f2_parser_with_invalid_date_does_not_raise(self):
file, opts = self._build_file_with_relay_entry()
f2_line = "F2F 111.06Y 0 2 6 4 4 0 111.00 111.16 0.00 111.06 0.00 99999999 46"
result = f2_parser(f2_line, file, opts)
entry = result.meet.last_event[1].last_entry
self.assertEqual(111.06, entry.finals_time)
self.assertIsNone(entry.finals_date)


class TestF1PerEntryAttribution(unittest.TestCase):
"""Bug 2-A — relay_team_id and relay_swim_team_code must live on
Expand Down
3 changes: 3 additions & 0 deletions tests/hy3/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ def test_select_from_enum(self) -> None:

def test_date_or_none(self) -> None:
self.assertIsNone(date_or_none(""))
self.assertIsNone(date_or_none("notadate"))
self.assertIsNone(date_or_none("99999999"))
self.assertIsNone(date_or_none("02312025"))
self.assertEqual(datetime.date(1970, 1, 2), date_or_none("01021970"))

class TestParseReactionTime(unittest.TestCase):
Expand Down