Skip to content

Commit 8401135

Browse files
authored
Merge pull request #3 from ecmwf/feature/sequence-nearest
Add "nearest" method to Sequence
2 parents c4fb9c7 + ab18ef0 commit 8401135

File tree

4 files changed

+149
-42
lines changed

4 files changed

+149
-42
lines changed

src/earthkit/time/cli/sequence.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ def seq_prev_action(parser: argparse.ArgumentParser, args: argparse.Namespace):
2323
print(format_date(seq.previous(args.date, strict=(not args.inclusive))))
2424

2525

26+
def seq_nearest_action(parser: argparse.ArgumentParser, args: argparse.Namespace):
27+
seq = create_sequence(parser, args)
28+
print(format_date(seq.nearest(args.date, resolve=args.resolve)))
29+
30+
2631
def seq_range_action(parser: argparse.ArgumentParser, args: argparse.Namespace):
2732
seq = create_sequence(parser, args)
2833
print(
@@ -87,6 +92,23 @@ def get_parser() -> argparse.ArgumentParser:
8792
help="if the given date is in the sequence, return it",
8893
)
8994

95+
nearest_action = parser.add_action(
96+
"nearest",
97+
seq_nearest_action,
98+
help="compute the nearest date in the given sequence",
99+
description="Compute the nearest date in the given sequence",
100+
epilog=SEQ_EPILOG,
101+
formatter_class=argparse.RawDescriptionHelpFormatter,
102+
)
103+
nearest_action.add_argument("date", type=parse_date, help="reference date")
104+
add_sequence_args(nearest_action)
105+
nearest_action.add_argument(
106+
"--resolve",
107+
choices=("previous", "next"),
108+
default="previous",
109+
help="return this date in case of a tie",
110+
)
111+
90112
range_action = parser.add_action(
91113
"range",
92114
seq_range_action,

src/earthkit/time/sequence.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,26 @@ def previous(self, reference: date, strict: bool = True) -> date:
5151
current -= oneday
5252
return current
5353

54+
def nearest(self, reference: date, resolve: str = "previous") -> date:
55+
"""Return the date closest to ``reference`` in the sequence.
56+
In case this is ambiguous, ``resolve`` defines which date to use
57+
(``"previous"`` or ``"next"``).
58+
"""
59+
if resolve not in ["previous", "next"]:
60+
raise ValueError('`resolve` must be either "previous" or "next"')
61+
before = self.previous(reference, strict=False)
62+
after = self.next(reference, strict=False)
63+
delta_b = reference - before
64+
delta_a = after - reference
65+
if delta_b < delta_a:
66+
return before
67+
elif delta_b > delta_a:
68+
return after
69+
elif resolve == "previous":
70+
return before
71+
else:
72+
return after
73+
5474
def range(
5575
self,
5676
start: date,

tests/cli/test_sequence_cli.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from earthkit.time.calendar import Weekday
77
from earthkit.time.cli.sequence import (
88
seq_bracket_action,
9+
seq_nearest_action,
910
seq_next_action,
1011
seq_prev_action,
1112
seq_range_action,
@@ -104,6 +105,56 @@ def test_seq_prev(args: dict, expected: str, capsys: pytest.CaptureFixture[str])
104105
assert captured.out == expected + "\n"
105106

106107

108+
@pytest.mark.parametrize(
109+
"args, expected",
110+
[
111+
pytest.param(
112+
{"daily": True, "date": date(2006, 7, 26)}, "20060726", id="daily"
113+
),
114+
pytest.param(
115+
{"daily": True, "date": date(2017, 3, 30), "exclude": ["30", "31"]},
116+
"20170329",
117+
id="daily-excludes",
118+
),
119+
pytest.param(
120+
{
121+
"weekly": [Weekday.TUESDAY, Weekday.THURSDAY, Weekday.SATURDAY],
122+
"date": date(2013, 10, 23),
123+
"resolve": "previous",
124+
},
125+
"20131022",
126+
id="weekly",
127+
),
128+
pytest.param(
129+
{"monthly": [1, 15], "date": date(1995, 8, 25)},
130+
"19950901",
131+
id="monthly",
132+
),
133+
pytest.param(
134+
{
135+
"yearly": [(1, 4), (12, 25)],
136+
"date": date(2009, 12, 30),
137+
"resolve": "next",
138+
},
139+
"20100104",
140+
id="yearly",
141+
),
142+
],
143+
)
144+
def test_seq_nearest(args: dict, expected: str, capsys: pytest.CaptureFixture[str]):
145+
parser = argparse.ArgumentParser()
146+
args.setdefault("daily", False)
147+
args.setdefault("weekly", None)
148+
args.setdefault("monthly", None)
149+
args.setdefault("yearly", None)
150+
args.setdefault("exclude", [])
151+
args.setdefault("resolve", "previous")
152+
args = argparse.Namespace(**args)
153+
seq_nearest_action(parser, args)
154+
captured = capsys.readouterr()
155+
assert captured.out == expected + "\n"
156+
157+
107158
@pytest.mark.parametrize(
108159
"args, expected",
109160
[

0 commit comments

Comments
 (0)