Skip to content

Commit

Permalink
Merge branch 'release/0.1.7'
Browse files Browse the repository at this point in the history
  • Loading branch information
oiffrig committed Jan 2, 2025
2 parents f4e7736 + 5b1f284 commit 5b8865d
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 16 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 0.1.7 - 2025-01-02

* Export version as `earthkit.time.__version__`
* Add options for relative year range in climatology tools

## 0.1.6 - 2024-11-04

* Update project metadata
Expand Down
24 changes: 22 additions & 2 deletions docs/cli/climdates.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ here, as follows::

Compute climatological date ranges, one day per year in a given range::

earthkit-climdates range [--sep <sep>] (--from-date <start> | --from-year <start>) (--to-date <end> | --to-year <end>) <date>
earthkit-climdates range [--sep <sep>] (--from-date <start> | --from-year <start> | --from-rel-year <start>) (--to-date <end> | --to-year <end> | --to-rel-year <end>) <date>

The list is printed using the given separator, as documented in :ref:`cli_sep`.

Expand All @@ -33,6 +33,11 @@ The list is printed using the given separator, as documented in :ref:`cli_sep`.

Return dates starting from this year

.. option:: --from-rel-year <start>

Return dates starting from this number after the year in :option:`date` (e.g.
``--from-rel-year -5`` will start 5 years earlier)

.. option:: --to-date <end>

Return dates up to this one
Expand All @@ -41,6 +46,11 @@ The list is printed using the given separator, as documented in :ref:`cli_sep`.

Return dates up to this year

.. option:: --to-rel-year <end>

Return dates up to this number after the year in :option:`date` (e.g.
``--to-rel-year -1`` will end in the year before)

.. option:: date

The date to use as a reference (YYYYMMDD)
Expand All @@ -54,7 +64,7 @@ recurring source (e.g. twice a week).

Usage::

earthkit-climdates mclim <sequence> [--sep <sep>] (--from-date <start> | --from-year <start>) (--to-date <end> | --to-year <end>) --before <num> --after <num> <date>
earthkit-climdates mclim <sequence> [--sep <sep>] (--from-date <start> | --from-year <start> | --from-rel-year <start>) (--to-date <end> | --to-year <end> | --to-rel-year <end>) --before <num> --after <num> <date>

The sequence is described as documented in :ref:`cli_seq`. The list is printed
using the given separator, as documented in :ref:`cli_sep`.
Expand All @@ -67,6 +77,11 @@ using the given separator, as documented in :ref:`cli_sep`.

Return dates starting from this year

.. option:: --from-rel-year <start>

Return dates starting from this number after the year of the current date
(e.g. ``--from-rel-year -5`` will start 5 years earlier)

.. option:: --to-date <end>

Return dates up to this one
Expand All @@ -83,6 +98,11 @@ using the given separator, as documented in :ref:`cli_sep`.

Pick up all inputs up to *num* days after the chosen date

.. option:: --to-rel-year <end>

Return dates up to this number after the year of the current date (e.g.
``--to-rel-year -1`` will end in the year before)

.. option:: date

The date to use as a reference (YYYYMMDD)
5 changes: 5 additions & 0 deletions docs/guide/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ To get one date per year on the same day as a given reference, use
20001023, 20011023, 20021023, 20031023, 20041023, 20051023
>>> print_dates(date_range(date(2005, 6, 2), date(2002, 6, 8), date(2004, 7, 1)))
20030602, 20040602
>>> from earthkit.time import RelativeYear
>>> print_dates(date_range(date(2010, 8, 5), RelativeYear(-3), RelativeYear(-1)))
20070805, 20080805, 20090805
To combine yearly dates with multiple reference dates taken from a sequence, use
:meth:`~earthkit.time.climatology.model_climate_dates`:
Expand All @@ -153,3 +156,5 @@ To combine yearly dates with multiple reference dates taken from a sequence, use
>>> seq = Sequence.from_resource("ecmwf-mon-thu")
>>> print_dates(model_climate_dates(date(2023, 8, 6), 2018, 2020, 7, 7, seq))
20180731, 20180803, 20180807, 20180810, 20190731, 20190803, 20190807, 20190810, 20200731, 20200803, 20200807, 20200810
>>> print_dates(model_climate_dates(date(2023, 1, 1), RelativeYear(-7), RelativeYear(-4), 5, 5, seq))
20151229, 20160102, 20160105, 20161229, 20170102, 20170105, 20171229, 20180102, 20180105, 20181229, 20190102, 20190105
4 changes: 4 additions & 0 deletions docs/guide/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ To get one date per year on the same day as a given reference, use
20001023, 20011023, 20021023, 20031023, 20041023, 20051023
$ earthkit-climdates range --sep ", " --from-date 20020608 --to-date 20040701 20050602
20030602, 20040602
$ earthkit-climdates range --sep ", " --from-rel-year -3 --to-rel-year -1 20100805
20070805, 20080805, 20090805
To combine yearly dates with multiple reference dates taken from a sequence, use
``earthkit-climdates mclim``:
Expand All @@ -158,3 +160,5 @@ To combine yearly dates with multiple reference dates taken from a sequence, use
$ earthkit-climdates mclim --sep ", " --from-year 2018 --to-year 2020 --before 7 --after 7 --preset ecmwf-mon-thu 20230806
20180731, 20180803, 20180807, 20180810, 20190731, 20190803, 20190807, 20190810, 20200731, 20200803, 20200807, 20200810
$ earthkit-climdates mclim --sep ", " --from-rel-year -7 --to-rel-year -4 --before 5 --after 5 --preset ecmwf-mon-thu 20230101
20151229, 20160102, 20160105, 20161229, 20170102, 20170105, 20171229, 20180102, 20180105, 20181229, 20190102, 20190105
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ requires = ["setuptools>=65", "wheel"]
build-backend = "setuptools.build_meta"

[project]
dynamic = ["version"]
name = "earthkit-time"
version = "0.1.6"
requires-python = ">= 3.8"
description = "Date and time manipulation routines for the use of weather data"
license = {file = "LICENSE"}
Expand Down Expand Up @@ -58,6 +58,9 @@ testpaths = [
]
consider_namespace_packages = true

[tool.setuptools.dynamic]
version = {attr = "earthkit.time.__version__"}

[tool.setuptools.packages.find]
where = ["src"]

Expand Down
6 changes: 5 additions & 1 deletion src/earthkit/time/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .climatology import date_range, model_climate_dates
from .climatology import RelativeYear, date_range, model_climate_dates
from .sequence import (
DailySequence,
MonthlySequence,
Expand All @@ -7,7 +7,11 @@
YearlySequence,
)

__version__ = "0.1.7"

__all__ = [
"__version__",
"RelativeYear",
"date_range",
"model_climate_dates",
"DailySequence",
Expand Down
5 changes: 5 additions & 0 deletions src/earthkit/time/cli/cliargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import List, Tuple

from ..calendar import Weekday, parse_date, parse_mmdd, to_weekday
from ..climatology import RelativeYear
from ..sequence import (
DailySequence,
MonthlySequence,
Expand Down Expand Up @@ -159,3 +160,7 @@ def add_sep_arg(parser: argparse.ArgumentParser):
default="\n",
help="output separator, see SEPARATORS for special values",
)


def relative_year(arg: str):
return RelativeYear(int(arg))
25 changes: 25 additions & 0 deletions src/earthkit/time/cli/climatology.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
add_sep_arg,
add_sequence_args,
create_sequence,
relative_year,
)
from .cliout import format_date_list

Expand Down Expand Up @@ -53,12 +54,24 @@ def get_parser() -> argparse.ArgumentParser:
range_start_group.add_argument(
"--from-year", type=int, dest="start", help="starting year"
)
range_start_group.add_argument(
"--from-rel-year",
type=relative_year,
dest="start",
help="starting year, relative to `date`",
)

range_end_group = range_action.add_mutually_exclusive_group(required=True)
range_end_group.add_argument(
"--to-date", type=parse_date, dest="end", help="ending date"
)
range_end_group.add_argument("--to-year", type=int, dest="end", help="ending year")
range_end_group.add_argument(
"--to-rel-year",
type=relative_year,
dest="end",
help="ending year, relative to `date`",
)

add_sep_arg(range_action)

Expand Down Expand Up @@ -86,12 +99,24 @@ def get_parser() -> argparse.ArgumentParser:
mclim_start_group.add_argument(
"--from-year", type=int, dest="start", help="starting year"
)
mclim_start_group.add_argument(
"--from-rel-year",
type=relative_year,
dest="start",
help="starting year, relative to `date`",
)

mclim_end_group = mclim_action.add_mutually_exclusive_group(required=True)
mclim_end_group.add_argument(
"--to-date", type=parse_date, dest="end", help="ending date"
)
mclim_end_group.add_argument("--to-year", type=int, dest="end", help="ending year")
mclim_end_group.add_argument(
"--to-rel-year",
type=relative_year,
dest="end",
help="ending year, relative to `date`",
)

mclim_action.add_argument(
"--before",
Expand Down
42 changes: 33 additions & 9 deletions src/earthkit/time/climatology.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
"""Date utilities to build a climatology"""

from dataclasses import dataclass
from datetime import date, timedelta
from typing import Iterator, Union

from .sequence import Sequence, YearlySequence
from .utilities import merge_sorted


@dataclass
class RelativeYear:
"""Wrapper for a year intended to be relative to a reference"""

value: int

def relative_to(self, reference: Union[date, int]) -> int:
if isinstance(reference, date):
reference = reference.year
return reference + self.value


def date_range(
reference: date,
start: Union[date, int],
end: Union[date, int],
start: Union[date, int, RelativeYear],
end: Union[date, int, RelativeYear],
recurrence: str = "yearly",
include_endpoint: bool = True,
) -> Iterator[date]:
Expand All @@ -24,10 +37,10 @@ def date_range(
reference: :class:`datetime.date`
Reference date setting the fixed part in the sequence (e.g., month and day
for a yearly recurrence)
start: :class:`datetime.date` or int
start: :class:`datetime.date`, int, or :class:`RelativeYear`
Start of the period. Either a full date or a meaningful identifier (e.g.
year for a yearly recurrence)
end: :class:`datetime.date` or int
end: :class:`datetime.date`, int, or :class:`RelativeYear`
End of the period. Included in the sequence unless ``include_endpoint`` is
``False``
recurrence: "yearly"
Expand All @@ -48,6 +61,8 @@ def date_range(
[datetime.date(1999, 4, 12), datetime.date(2000, 4, 12), datetime.date(2001, 4, 12)]
>>> list(date_range(date(2014, 8, 23), date(2010, 8, 16), date(2012, 8, 1)))
[datetime.date(2010, 8, 23), datetime.date(2011, 8, 23)]
>>> list(date_range(date(2014, 8, 23), RelativeYear(-3), RelativeYear(-1)))
[datetime.date(2011, 8, 23), datetime.date(2012, 8, 23), datetime.date(2013, 8, 23)]
"""

_known_recurrences = ["yearly"]
Expand All @@ -59,9 +74,13 @@ def date_range(
if reference.month == 2 and reference.day == 29:
reference = reference.replace(day=28)

if isinstance(start, RelativeYear):
start = start.relative_to(reference)
if not isinstance(start, date):
start = reference.replace(year=start)

if isinstance(end, RelativeYear):
end = end.relative_to(reference)
if not isinstance(end, date):
end = reference.replace(year=end)

Expand All @@ -71,8 +90,8 @@ def date_range(

def model_climate_dates(
reference: date,
start: Union[date, int],
end: Union[date, int],
start: Union[date, int, RelativeYear],
end: Union[date, int, RelativeYear],
before: Union[timedelta, int],
after: Union[timedelta, int],
sequence: Sequence,
Expand All @@ -88,9 +107,9 @@ def model_climate_dates(
----------
reference: :class:`datetime.date`
Reference date for the climate
start: :class:`datetime.date` or int
start: :class:`datetime.date`, int, or :class:`RelativeYear`
Start of the climatological period. Either a full date or a year
end: :class:`datetime.date` or int
end: :class:`datetime.date`, int, or :class:`RelativeYear`
End of the climatological period. Either a full date or a year
before: :class:`datetime.timedelta` or int
Cut-off before the reference date. Either a timedelta or a number of
Expand All @@ -108,7 +127,7 @@ def model_climate_dates(
Examples
--------
>>> from earthkit.time.calendar import MONDAY, THURSDAY
>>> from earthkit.time import MonthlySequence, WeeklySequence
>>> from earthkit.time import MonthlySequence, Sequence, WeeklySequence
>>> sequence = WeeklySequence([MONDAY, THURSDAY])
>>> [f"{d:%Y%m%d}" for d in model_climate_dates(date(2024, 2, 12), 2020, 2023, 7, 7, sequence)]
... # doctest: +NORMALIZE_WHITESPACE
Expand All @@ -123,6 +142,11 @@ def model_climate_dates(
'20210205', '20210207', '20210209', '20210211', '20210213', '20210215', '20210217', '20210219',
'20220205', '20220207', '20220209', '20220211', '20220213', '20220215', '20220217', '20220219',
'20230205', '20230207', '20230209', '20230211', '20230213', '20230215', '20230217', '20230219']
>>> sequence = Sequence.from_resource("ecmwf-2days")
>>> [f"{d:%Y%m%d}" for d in model_climate_dates(
... date(2024, 12, 31), RelativeYear(-3), RelativeYear(-1), 2, 2, sequence
... )]
['20211229', '20211231', '20220101', '20221229', '20221231', '20230101', '20231229', '20231231', '20240101']
"""
if not isinstance(before, timedelta):
before = timedelta(days=before)
Expand Down
Loading

0 comments on commit 5b8865d

Please sign in to comment.