Skip to content

Commit ebb4ae2

Browse files
authored
Merge pull request #859 from gaoflow/fix-datefmt-unrecognized-string
fix: return unrecognized date strings unchanged in datefmt
2 parents ac2dafa + 83f702a commit ebb4ae2

3 files changed

Lines changed: 34 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
- **TTM Q4 derivation no longer produces wrong/negative values for discrete-quarter reporters** — when a concept is reported as discrete quarters with no cumulative 9-month YTD fact (common for BDCs and investment companies), `TTMCalculator` derives Q4 as `FY - (Q1+Q2+Q3)`. It previously selected the three input quarters by their `fiscal_period` label, but the SEC tags comparative facts in re-filings with the *filing's* fiscal period, so the same calendar quarter could appear labeled Q1, Q2 and Q3 across successive 10-Qs — producing a wrong, often negative Q4 (e.g. GAIN `InvestmentCompanyDividendDistribution`: `57.2M - 3×28.8M = -29.2M`). Quarters are now selected by distinct calendar period (dedup by `period_end`, latest periodic filing wins), and derivation is skipped when a discrete Q4 is already reported. This affects `quarterize()`, TTM calculations, and quarterly statement views. ([#848](https://github.com/dgunning/edgartools/issues/848))
2121
- **`format_currency_short` rolls up to the next unit at magnitude boundaries** — a value just under 1B (>= ~999.95M) rounded to `1,000.0` within the millions bucket and rendered as the nonsensical `$1,000.0M` instead of `$1.0B`; it now promotes to the billions unit when the millions mantissa rounds up to 1,000.
2222
- **`datefmt` no longer crashes on `None` or non-date values** — the display-only date helper called `value.strftime` on its non-string branch unconditionally, so a `None` (e.g. a former name's open-ended `to` date, or a missing `date_of_change`) raised `AttributeError` and took down the whole header/former-name table render. It now returns `""` for `None`, formats `date`/`datetime` objects, and degrades to `str(value)` for anything unexpected. (Complements the unrecognized-string pass-through fix in #859.)
23+
- **`datefmt` no longer crashes on unrecognized date strings** — the helper parsed only `YYYYMMDD`, `YYYYMMDDHHMMSS` and `YYYY-MM-DD` strings and called `str.strftime` on everything else, raising `AttributeError: 'str' object has no attribute 'strftime'` for any other value (e.g. `2022/03/04`, a non-zero-padded date, or an empty string). Unrecognized strings are now returned unchanged so date display in filing-header and former-name tables degrades gracefully instead of crashing.
2324

2425
### Changed
2526

edgar/display/formatting.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ def datefmt(value: Union[datetime.datetime, datetime.date, str, None], fmt: str
125125
value = datetime.datetime.strptime(value, "%Y%m%d%H%M%S")
126126
elif re.match(r"^\d{4}-\d{2}-\d{2}$", value):
127127
value = datetime.datetime.strptime(value, "%Y-%m-%d")
128+
else:
129+
# Unrecognized date string: return it unchanged rather than
130+
# crashing on ``str.strftime``. datefmt is display-only and is
131+
# called with filing-derived strings that are not guaranteed to
132+
# match one of the patterns above (e.g. ``""`` or ``"2022/03/04"``).
133+
return value
128134
return value.strftime(fmt)
129135
# Non-string input. datefmt is display-only and is called with filing-derived
130136
# values that are not guaranteed to be set — e.g. a former name's null ``to``

tests/test_to_context.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""Tests for to_context() implementations on DataObject classes."""
2+
import datetime
23
from decimal import Decimal
34

45
import pytest
56

6-
from edgar.display.formatting import format_currency_short
7+
from edgar.display.formatting import datefmt, format_currency_short
78

89

910
class TestFormatCurrencyShort:
@@ -60,6 +61,31 @@ def test_just_under_billion_promotes_negative(self):
6061
assert format_currency_short(-999_950_000) == "-$1.0B"
6162

6263

64+
class TestDatefmt:
65+
"""Unit tests for the shared date formatting helper."""
66+
67+
def test_compact_yyyymmdd(self):
68+
assert datefmt("20220304", "%B %d, %Y") == "March 04, 2022"
69+
70+
def test_iso_date(self):
71+
assert datefmt("2022-03-04", "%B %d, %Y") == "March 04, 2022"
72+
73+
def test_compact_datetime(self):
74+
assert datefmt("20220304120000", "%Y-%m-%d") == "2022-03-04"
75+
76+
def test_datetime_object(self):
77+
assert datefmt(datetime.datetime(2022, 3, 4), "%B %d, %Y") == "March 04, 2022"
78+
79+
@pytest.mark.parametrize(
80+
"value",
81+
["2022/03/04", "2022-3-4", "March 4, 2022", "N/A", ""],
82+
)
83+
def test_unrecognized_string_passes_through(self, value):
84+
# Strings that match none of the known patterns must be returned
85+
# unchanged instead of raising ``AttributeError`` on ``str.strftime``.
86+
assert datefmt(value, "%B %d, %Y") == value
87+
88+
6389
class TestTenKToContext:
6490
"""Tests for TenK.to_context() using a known filing."""
6591

0 commit comments

Comments
 (0)