From 23525ee50e6efbe2c110dd862b3244dda2f52223 Mon Sep 17 00:00:00 2001 From: Waldir Lisboa Rocha Date: Wed, 16 Sep 2026 18:00:07 +0200 Subject: [PATCH] Parse month- and day-level consecutives in sets (#79) [1774-11..1774-12] and [1785-07-30..1785-08-06] raised TypeError: the results names inside a month or day bound (year, month, day) reached Consecutives.__init__ as keyword arguments, and each bound arrived as a list of its parts. The bounds are now combined into one string, and Consecutives builds itself from the two bounds only. --- edtf/parser/grammar.py | 6 ++++-- edtf/parser/parser_classes.py | 12 ++++++++++++ tests/test_parser.py | 8 ++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/edtf/parser/grammar.py b/edtf/parser/grammar.py index 0624a92..7f392eb 100644 --- a/edtf/parser/grammar.py +++ b/edtf/parser/grammar.py @@ -284,8 +284,10 @@ def f(toks): # (* ** Inclusive list and choice list** *) consecutives = ( - (yearMonthDay("lower") + ".." + yearMonthDay("upper")) - ^ (yearMonth("lower") + ".." + yearMonth("upper")) + # Combine so each bound reaches Consecutives as one string, e.g. "1912-03", + # rather than as the list of its parts. + (Combine(yearMonthDay)("lower") + ".." + Combine(yearMonthDay)("upper")) + ^ (Combine(yearMonth)("lower") + ".." + Combine(yearMonth)("upper")) ^ ( year_basic("lower") + ".." + year_basic("upper") ) # using year_basic because some tests were throwing `'list' object has no attribute 'expandtabs'` - somewhere, pyparsing.parse_string() was being passed a list diff --git a/edtf/parser/parser_classes.py b/edtf/parser/parser_classes.py index 67dd8ee..c519413 100644 --- a/edtf/parser/parser_classes.py +++ b/edtf/parser/parser_classes.py @@ -7,6 +7,7 @@ from typing import Optional from dateutil.relativedelta import relativedelta +from pyparsing import ParseResults from edtf import appsettings from edtf.convert import ( @@ -1021,6 +1022,17 @@ class PartialUnspecified(Unspecified): class Consecutives(Interval): # Treating Consecutive ranges as intervals where one bound is optional + @classmethod + def parse_action(cls, toks): + # Only the two bounds, each as its string. A month or day bound keeps + # the results names of its parts (year, month, day), so it arrives as + # a ParseResults holding the combined string, and those names are not + # arguments of __init__ (#79). + def bound(value): + return value[0] if isinstance(value, ParseResults) else value + + return cls(lower=bound(toks.get("lower")), upper=bound(toks.get("upper"))) + def __init__(self, lower=None, upper=None): # noqa if lower and not isinstance(lower, EDTFObject): self.lower = Date.parse(lower) diff --git a/tests/test_parser.py b/tests/test_parser.py index 8b3c1d9..7d12818 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -179,11 +179,19 @@ ("[1760-01, 1760-02, 1760-12..]", ("1760-01-01", "inf")), # Either the year 1667 or the month December of 1760. ("[1667, 1760-12]", ("1667-01-01", "1760-12-31")), + # November or December 1774 (consecutive months, #79) + ("[1774-11..1774-12]", ("1774-11-01", "1774-12-31")), + # One of the days from 30 July to 6 August 1785 (consecutive days, #79) + ("[1785-07-30..1785-08-06]", ("1785-07-30", "1785-08-06")), + # The year 1667 or one of the months March to May 1912 + ("[1667, 1912-03..1912-05]", ("1667-01-01", "1912-05-31")), # Multiple Dates # All of the years 1667, 1668, 1670, 1671, 1672 ("{1667,1668, 1670..1672}", ("1667-01-01", "1672-12-31")), # The year 1960 and the month December of 1961. ("{1960, 1961-12}", ("1960-01-01", "1961-12-31")), + # All of the months March to May 1912 (consecutive months, #79) + ("{1912-03..1912-05}", ("1912-03-01", "1912-05-31")), # Previously tested masked precision, now eliminated from the spec # A date during the 1960s ("196X", ("1960-01-01", "1969-12-31")),