Skip to content

Commit 2aaa325

Browse files
authored
Merge pull request #107 from lsst-sqre/tickets/DM-51083
DM-51083: Fix handling of relative days/weeks/years
2 parents abd0905 + f4aec2c commit 2aaa325

File tree

3 files changed

+113
-4
lines changed

3 files changed

+113
-4
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@ Collect fragments into this file with: scriv collect --version X.Y.Z
88

99
<!-- scriv-insert-here -->
1010

11+
<a id='changelog-0.21.1'></a>
12+
13+
## 0.21.1 (2025-05-30)
14+
15+
### Bug fixes
16+
17+
- The `dynamic_default` date syntax for a relative number of weeks/months/years from the current date didn't work in the 0.21.0 release. We fixed that feature and changed the syntax to use `w`, `m`, and `y` for weeks, months, and years respectively. This syntax is similar to how relative numbers of days are treated (for example, `+5d` for 5 days from now). Some examples:
18+
19+
- `dynamic_default: 1w` for one week from now
20+
- `dynamic_default: -2m` for two months ago
21+
- `dynamic_default: 3y` for three years from now
22+
23+
The promised syntax of `dynamic_default: +1week` from the 0.21.0 release is no longer supported. The `week_end` / `week_start` syntax still uses the full words rather than the first letter abbreviations.
24+
1125
<a id='changelog-0.21.0'></a>
1226

1327
## 0.21.0 (2025-05-29)

src/timessquare/domain/pageparameters/_datedynamicdefault.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
DYNAMIC_DATE_PATTERN = re.compile(
11-
r"^(?:(?P<sign>[+-])(?P<offset>\d+)(?P<base>d|(?:week|month|year)_(?:start|end)))|"
11+
r"^(?:(?P<sign>[+-])(?P<offset>\d+)(?P<base>(?:week|month|year)_(?:start|end)|d|w|m|y))|"
1212
r"(?P<simple>today|yesterday|tomorrow|(?:week|month|year)_(?:start|end))$"
1313
)
1414
"""Pattern for dynamic date default configurations."""
@@ -22,8 +22,8 @@ class DateDynamicDefault:
2222
config_value : str
2323
The dynamic default value in the format defined by
2424
`DYNAMIC_DATE_PATTERN`. It can be a simple value like "today",
25-
"yesterday", "tomorrow", or a more complex expression like "+7d" or
26-
"-1month_start".
25+
"yesterday", "tomorrow", or a more complex expression like "+7d",
26+
"+2w", "+3m", "+1y", or "-1month_start".
2727
"""
2828

2929
def __init__(self, config_value: str) -> None:
@@ -81,7 +81,7 @@ def __call__(self, current_date: date | None = None) -> date:
8181
# Handle complex patterns
8282
return self._compute_complex_date(current_date)
8383

84-
def _compute_complex_date(self, current_date: date) -> date: # noqa: C901
84+
def _compute_complex_date(self, current_date: date) -> date: # noqa: C901,PLR0912
8585
"""Compute dates for complex patterns."""
8686
match = DYNAMIC_DATE_PATTERN.match(self.config_value)
8787
if not match:
@@ -105,6 +105,12 @@ def _compute_complex_date(self, current_date: date) -> date: # noqa: C901
105105
match base:
106106
case "d":
107107
return self._add_days(current_date, offset)
108+
case "w":
109+
return self._add_days(current_date, offset * 7)
110+
case "m":
111+
return self._add_months(current_date, offset)
112+
case "y":
113+
return self._add_years(current_date, offset)
108114
case "week_start":
109115
return self._compute_week_start(current_date, offset)
110116
case "week_end":
@@ -196,3 +202,12 @@ def _add_months(base_date: date, months: int) -> date:
196202
return base_date.replace(
197203
year=new_year, month=new_month, day=last_day
198204
)
205+
206+
@staticmethod
207+
def _add_years(base_date: date, years: int) -> date:
208+
"""Add years to a date."""
209+
try:
210+
return base_date.replace(year=base_date.year + years)
211+
except ValueError:
212+
# Handle leap year edge case (Feb 29 + 1 year)
213+
return base_date.replace(year=base_date.year + years, day=28)

tests/domain/pageparameters_test.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,71 @@ def now(cls, tz: timezone | None = None) -> datetime:
417417
)
418418
assert schema_minus_1_year_end.default == date(2024, 12, 31)
419419

420+
# Test short-form week patterns
421+
schema_plus_2w = create_and_validate_parameter_schema(
422+
"myvar",
423+
{
424+
"type": "string",
425+
"format": "date",
426+
"X-Dynamic-Default": "+2w",
427+
},
428+
)
429+
assert schema_plus_2w.default == date(2025, 1, 15) # 2 weeks = 14 days
430+
431+
schema_minus_3w = create_and_validate_parameter_schema(
432+
"myvar",
433+
{
434+
"type": "string",
435+
"format": "date",
436+
"X-Dynamic-Default": "-3w",
437+
},
438+
)
439+
assert schema_minus_3w.default == date(
440+
2024, 12, 11
441+
) # 3 weeks = 21 days ago
442+
443+
# Test short-form month patterns
444+
schema_plus_2m = create_and_validate_parameter_schema(
445+
"myvar",
446+
{
447+
"type": "string",
448+
"format": "date",
449+
"X-Dynamic-Default": "+2m",
450+
},
451+
)
452+
assert schema_plus_2m.default == date(2025, 3, 1) # 2 months later
453+
454+
schema_minus_1m = create_and_validate_parameter_schema(
455+
"myvar",
456+
{
457+
"type": "string",
458+
"format": "date",
459+
"X-Dynamic-Default": "-1m",
460+
},
461+
)
462+
assert schema_minus_1m.default == date(2024, 12, 1) # 1 month ago
463+
464+
# Test short-form year patterns
465+
schema_plus_1y = create_and_validate_parameter_schema(
466+
"myvar",
467+
{
468+
"type": "string",
469+
"format": "date",
470+
"X-Dynamic-Default": "+1y",
471+
},
472+
)
473+
assert schema_plus_1y.default == date(2026, 1, 1) # 1 year later
474+
475+
schema_minus_2y = create_and_validate_parameter_schema(
476+
"myvar",
477+
{
478+
"type": "string",
479+
"format": "date",
480+
"X-Dynamic-Default": "-2y",
481+
},
482+
)
483+
assert schema_minus_2y.default == date(2023, 1, 1) # 2 years ago
484+
420485

421486
def test_date_parameter_dynamic_default_validation() -> None:
422487
"""Test validation of X-Dynamic-Default values."""
@@ -427,6 +492,12 @@ def test_date_parameter_dynamic_default_validation() -> None:
427492
"tomorrow",
428493
"+5d",
429494
"-10d",
495+
"+2w",
496+
"-3w",
497+
"+1m",
498+
"-6m",
499+
"+1y",
500+
"-2y",
430501
"week_start",
431502
"week_end",
432503
"+2week_start",
@@ -458,6 +529,15 @@ def test_date_parameter_dynamic_default_validation() -> None:
458529
"d", # missing sign and offset
459530
"+d", # missing offset
460531
"5d", # missing sign
532+
"w", # missing sign and offset
533+
"+w", # missing offset
534+
"5w", # missing sign
535+
"m", # missing sign and offset
536+
"+m", # missing offset
537+
"3m", # missing sign
538+
"y", # missing sign and offset
539+
"+y", # missing offset
540+
"2y", # missing sign
461541
"week", # incomplete
462542
"month", # incomplete
463543
"year", # incomplete

0 commit comments

Comments
 (0)