1788 lines
53 KiB
Python
1788 lines
53 KiB
Python
from __future__ import annotations
|
|
|
|
import warnings
|
|
from collections.abc import Callable
|
|
from itertools import product
|
|
from typing import Literal
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
import pytest
|
|
|
|
from xarray import CFTimeIndex
|
|
from xarray.coding.cftime_offsets import (
|
|
_MONTH_ABBREVIATIONS,
|
|
BaseCFTimeOffset,
|
|
Day,
|
|
Hour,
|
|
Microsecond,
|
|
Millisecond,
|
|
Minute,
|
|
MonthBegin,
|
|
MonthEnd,
|
|
QuarterBegin,
|
|
QuarterEnd,
|
|
Second,
|
|
Tick,
|
|
YearBegin,
|
|
YearEnd,
|
|
_legacy_to_new_freq,
|
|
_new_to_legacy_freq,
|
|
cftime_range,
|
|
date_range,
|
|
date_range_like,
|
|
get_date_type,
|
|
to_cftime_datetime,
|
|
to_offset,
|
|
)
|
|
from xarray.coding.frequencies import infer_freq
|
|
from xarray.core.dataarray import DataArray
|
|
from xarray.tests import (
|
|
_CFTIME_CALENDARS,
|
|
assert_no_warnings,
|
|
has_cftime,
|
|
has_pandas_ge_2_2,
|
|
requires_cftime,
|
|
requires_pandas_3,
|
|
)
|
|
|
|
cftime = pytest.importorskip("cftime")
|
|
|
|
|
|
def _id_func(param):
|
|
"""Called on each parameter passed to pytest.mark.parametrize"""
|
|
return str(param)
|
|
|
|
|
|
@pytest.fixture(params=_CFTIME_CALENDARS)
|
|
def calendar(request):
|
|
return request.param
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("offset", "expected_n"),
|
|
[
|
|
(BaseCFTimeOffset(), 1),
|
|
(YearBegin(), 1),
|
|
(YearEnd(), 1),
|
|
(QuarterBegin(), 1),
|
|
(QuarterEnd(), 1),
|
|
(Tick(), 1),
|
|
(Day(), 1),
|
|
(Hour(), 1),
|
|
(Minute(), 1),
|
|
(Second(), 1),
|
|
(Millisecond(), 1),
|
|
(Microsecond(), 1),
|
|
(BaseCFTimeOffset(n=2), 2),
|
|
(YearBegin(n=2), 2),
|
|
(YearEnd(n=2), 2),
|
|
(QuarterBegin(n=2), 2),
|
|
(QuarterEnd(n=2), 2),
|
|
(Tick(n=2), 2),
|
|
(Day(n=2), 2),
|
|
(Hour(n=2), 2),
|
|
(Minute(n=2), 2),
|
|
(Second(n=2), 2),
|
|
(Millisecond(n=2), 2),
|
|
(Microsecond(n=2), 2),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_cftime_offset_constructor_valid_n(offset, expected_n):
|
|
assert offset.n == expected_n
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("offset", "invalid_n"),
|
|
[
|
|
(BaseCFTimeOffset, 1.5),
|
|
(YearBegin, 1.5),
|
|
(YearEnd, 1.5),
|
|
(QuarterBegin, 1.5),
|
|
(QuarterEnd, 1.5),
|
|
(MonthBegin, 1.5),
|
|
(MonthEnd, 1.5),
|
|
(Tick, 1.5),
|
|
(Day, 1.5),
|
|
(Hour, 1.5),
|
|
(Minute, 1.5),
|
|
(Second, 1.5),
|
|
(Millisecond, 1.5),
|
|
(Microsecond, 1.5),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_cftime_offset_constructor_invalid_n(offset, invalid_n):
|
|
with pytest.raises(TypeError):
|
|
offset(n=invalid_n)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("offset", "expected_month"),
|
|
[
|
|
(YearBegin(), 1),
|
|
(YearEnd(), 12),
|
|
(YearBegin(month=5), 5),
|
|
(YearEnd(month=5), 5),
|
|
(QuarterBegin(), 3),
|
|
(QuarterEnd(), 3),
|
|
(QuarterBegin(month=5), 5),
|
|
(QuarterEnd(month=5), 5),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_year_offset_constructor_valid_month(offset, expected_month):
|
|
assert offset.month == expected_month
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("offset", "invalid_month", "exception"),
|
|
[
|
|
(YearBegin, 0, ValueError),
|
|
(YearEnd, 0, ValueError),
|
|
(YearBegin, 13, ValueError),
|
|
(YearEnd, 13, ValueError),
|
|
(YearBegin, 1.5, TypeError),
|
|
(YearEnd, 1.5, TypeError),
|
|
(QuarterBegin, 0, ValueError),
|
|
(QuarterEnd, 0, ValueError),
|
|
(QuarterBegin, 1.5, TypeError),
|
|
(QuarterEnd, 1.5, TypeError),
|
|
(QuarterBegin, 13, ValueError),
|
|
(QuarterEnd, 13, ValueError),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_year_offset_constructor_invalid_month(offset, invalid_month, exception):
|
|
with pytest.raises(exception):
|
|
offset(month=invalid_month)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("offset", "expected"),
|
|
[
|
|
(BaseCFTimeOffset(), None),
|
|
(MonthBegin(), "MS"),
|
|
(MonthEnd(), "ME"),
|
|
(YearBegin(), "YS-JAN"),
|
|
(YearEnd(), "YE-DEC"),
|
|
(QuarterBegin(), "QS-MAR"),
|
|
(QuarterEnd(), "QE-MAR"),
|
|
(Day(), "D"),
|
|
(Hour(), "h"),
|
|
(Minute(), "min"),
|
|
(Second(), "s"),
|
|
(Millisecond(), "ms"),
|
|
(Microsecond(), "us"),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_rule_code(offset, expected):
|
|
assert offset.rule_code() == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("offset", "expected"),
|
|
[
|
|
(BaseCFTimeOffset(), "<BaseCFTimeOffset: n=1>"),
|
|
(YearBegin(), "<YearBegin: n=1, month=1>"),
|
|
(QuarterBegin(), "<QuarterBegin: n=1, month=3>"),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_str_and_repr(offset, expected):
|
|
assert str(offset) == expected
|
|
assert repr(offset) == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"offset",
|
|
[BaseCFTimeOffset(), MonthBegin(), QuarterBegin(), YearBegin()],
|
|
ids=_id_func,
|
|
)
|
|
def test_to_offset_offset_input(offset):
|
|
assert to_offset(offset) == offset
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("freq", "expected"),
|
|
[
|
|
("M", MonthEnd()),
|
|
("2M", MonthEnd(n=2)),
|
|
("ME", MonthEnd()),
|
|
("2ME", MonthEnd(n=2)),
|
|
("MS", MonthBegin()),
|
|
("2MS", MonthBegin(n=2)),
|
|
("D", Day()),
|
|
("2D", Day(n=2)),
|
|
("H", Hour()),
|
|
("2H", Hour(n=2)),
|
|
("h", Hour()),
|
|
("2h", Hour(n=2)),
|
|
("T", Minute()),
|
|
("2T", Minute(n=2)),
|
|
("min", Minute()),
|
|
("2min", Minute(n=2)),
|
|
("S", Second()),
|
|
("2S", Second(n=2)),
|
|
("L", Millisecond(n=1)),
|
|
("2L", Millisecond(n=2)),
|
|
("ms", Millisecond(n=1)),
|
|
("2ms", Millisecond(n=2)),
|
|
("U", Microsecond(n=1)),
|
|
("2U", Microsecond(n=2)),
|
|
("us", Microsecond(n=1)),
|
|
("2us", Microsecond(n=2)),
|
|
# negative
|
|
("-2M", MonthEnd(n=-2)),
|
|
("-2ME", MonthEnd(n=-2)),
|
|
("-2MS", MonthBegin(n=-2)),
|
|
("-2D", Day(n=-2)),
|
|
("-2H", Hour(n=-2)),
|
|
("-2h", Hour(n=-2)),
|
|
("-2T", Minute(n=-2)),
|
|
("-2min", Minute(n=-2)),
|
|
("-2S", Second(n=-2)),
|
|
("-2L", Millisecond(n=-2)),
|
|
("-2ms", Millisecond(n=-2)),
|
|
("-2U", Microsecond(n=-2)),
|
|
("-2us", Microsecond(n=-2)),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
@pytest.mark.filterwarnings("ignore::FutureWarning") # Deprecation of "M" etc.
|
|
def test_to_offset_sub_annual(freq, expected):
|
|
assert to_offset(freq) == expected
|
|
|
|
|
|
_ANNUAL_OFFSET_TYPES = {
|
|
"A": YearEnd,
|
|
"AS": YearBegin,
|
|
"Y": YearEnd,
|
|
"YS": YearBegin,
|
|
"YE": YearEnd,
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("month_int", "month_label"), list(_MONTH_ABBREVIATIONS.items()) + [(0, "")]
|
|
)
|
|
@pytest.mark.parametrize("multiple", [None, 2, -1])
|
|
@pytest.mark.parametrize("offset_str", ["AS", "A", "YS", "Y"])
|
|
@pytest.mark.filterwarnings("ignore::FutureWarning") # Deprecation of "A" etc.
|
|
def test_to_offset_annual(month_label, month_int, multiple, offset_str):
|
|
freq = offset_str
|
|
offset_type = _ANNUAL_OFFSET_TYPES[offset_str]
|
|
if month_label:
|
|
freq = "-".join([freq, month_label])
|
|
if multiple:
|
|
freq = f"{multiple}{freq}"
|
|
result = to_offset(freq)
|
|
|
|
if multiple and month_int:
|
|
expected = offset_type(n=multiple, month=month_int)
|
|
elif multiple:
|
|
expected = offset_type(n=multiple)
|
|
elif month_int:
|
|
expected = offset_type(month=month_int)
|
|
else:
|
|
expected = offset_type()
|
|
assert result == expected
|
|
|
|
|
|
_QUARTER_OFFSET_TYPES = {"Q": QuarterEnd, "QS": QuarterBegin, "QE": QuarterEnd}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("month_int", "month_label"), list(_MONTH_ABBREVIATIONS.items()) + [(0, "")]
|
|
)
|
|
@pytest.mark.parametrize("multiple", [None, 2, -1])
|
|
@pytest.mark.parametrize("offset_str", ["QS", "Q", "QE"])
|
|
@pytest.mark.filterwarnings("ignore::FutureWarning") # Deprecation of "Q" etc.
|
|
def test_to_offset_quarter(month_label, month_int, multiple, offset_str):
|
|
freq = offset_str
|
|
offset_type = _QUARTER_OFFSET_TYPES[offset_str]
|
|
if month_label:
|
|
freq = "-".join([freq, month_label])
|
|
if multiple:
|
|
freq = f"{multiple}{freq}"
|
|
result = to_offset(freq)
|
|
|
|
if multiple and month_int:
|
|
expected = offset_type(n=multiple, month=month_int)
|
|
elif multiple:
|
|
if month_int:
|
|
expected = offset_type(n=multiple)
|
|
else:
|
|
if offset_type == QuarterBegin:
|
|
expected = offset_type(n=multiple, month=1)
|
|
elif offset_type == QuarterEnd:
|
|
expected = offset_type(n=multiple, month=12)
|
|
elif month_int:
|
|
expected = offset_type(month=month_int)
|
|
else:
|
|
if offset_type == QuarterBegin:
|
|
expected = offset_type(month=1)
|
|
elif offset_type == QuarterEnd:
|
|
expected = offset_type(month=12)
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize("freq", ["Z", "7min2", "AM", "M-", "AS-", "QS-", "1H1min"])
|
|
def test_invalid_to_offset_str(freq):
|
|
with pytest.raises(ValueError):
|
|
to_offset(freq)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("argument", "expected_date_args"),
|
|
[("2000-01-01", (2000, 1, 1)), ((2000, 1, 1), (2000, 1, 1))],
|
|
ids=_id_func,
|
|
)
|
|
def test_to_cftime_datetime(calendar, argument, expected_date_args):
|
|
date_type = get_date_type(calendar)
|
|
expected = date_type(*expected_date_args)
|
|
if isinstance(argument, tuple):
|
|
argument = date_type(*argument)
|
|
result = to_cftime_datetime(argument, calendar=calendar)
|
|
assert result == expected
|
|
|
|
|
|
def test_to_cftime_datetime_error_no_calendar():
|
|
with pytest.raises(ValueError):
|
|
to_cftime_datetime("2000")
|
|
|
|
|
|
def test_to_cftime_datetime_error_type_error():
|
|
with pytest.raises(TypeError):
|
|
to_cftime_datetime(1)
|
|
|
|
|
|
_EQ_TESTS_A = [
|
|
BaseCFTimeOffset(),
|
|
YearBegin(),
|
|
YearEnd(),
|
|
YearBegin(month=2),
|
|
YearEnd(month=2),
|
|
QuarterBegin(),
|
|
QuarterEnd(),
|
|
QuarterBegin(month=2),
|
|
QuarterEnd(month=2),
|
|
MonthBegin(),
|
|
MonthEnd(),
|
|
Day(),
|
|
Hour(),
|
|
Minute(),
|
|
Second(),
|
|
Millisecond(),
|
|
Microsecond(),
|
|
]
|
|
_EQ_TESTS_B = [
|
|
BaseCFTimeOffset(n=2),
|
|
YearBegin(n=2),
|
|
YearEnd(n=2),
|
|
YearBegin(n=2, month=2),
|
|
YearEnd(n=2, month=2),
|
|
QuarterBegin(n=2),
|
|
QuarterEnd(n=2),
|
|
QuarterBegin(n=2, month=2),
|
|
QuarterEnd(n=2, month=2),
|
|
MonthBegin(n=2),
|
|
MonthEnd(n=2),
|
|
Day(n=2),
|
|
Hour(n=2),
|
|
Minute(n=2),
|
|
Second(n=2),
|
|
Millisecond(n=2),
|
|
Microsecond(n=2),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(("a", "b"), product(_EQ_TESTS_A, _EQ_TESTS_B), ids=_id_func)
|
|
def test_neq(a, b):
|
|
assert a != b
|
|
|
|
|
|
_EQ_TESTS_B_COPY = [
|
|
BaseCFTimeOffset(n=2),
|
|
YearBegin(n=2),
|
|
YearEnd(n=2),
|
|
YearBegin(n=2, month=2),
|
|
YearEnd(n=2, month=2),
|
|
QuarterBegin(n=2),
|
|
QuarterEnd(n=2),
|
|
QuarterBegin(n=2, month=2),
|
|
QuarterEnd(n=2, month=2),
|
|
MonthBegin(n=2),
|
|
MonthEnd(n=2),
|
|
Day(n=2),
|
|
Hour(n=2),
|
|
Minute(n=2),
|
|
Second(n=2),
|
|
Millisecond(n=2),
|
|
Microsecond(n=2),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("a", "b"), zip(_EQ_TESTS_B, _EQ_TESTS_B_COPY, strict=True), ids=_id_func
|
|
)
|
|
def test_eq(a, b):
|
|
assert a == b
|
|
|
|
|
|
_MUL_TESTS = [
|
|
(BaseCFTimeOffset(), 3, BaseCFTimeOffset(n=3)),
|
|
(BaseCFTimeOffset(), -3, BaseCFTimeOffset(n=-3)),
|
|
(YearEnd(), 3, YearEnd(n=3)),
|
|
(YearBegin(), 3, YearBegin(n=3)),
|
|
(QuarterEnd(), 3, QuarterEnd(n=3)),
|
|
(QuarterBegin(), 3, QuarterBegin(n=3)),
|
|
(MonthEnd(), 3, MonthEnd(n=3)),
|
|
(MonthBegin(), 3, MonthBegin(n=3)),
|
|
(Tick(), 3, Tick(n=3)),
|
|
(Day(), 3, Day(n=3)),
|
|
(Hour(), 3, Hour(n=3)),
|
|
(Minute(), 3, Minute(n=3)),
|
|
(Second(), 3, Second(n=3)),
|
|
(Millisecond(), 3, Millisecond(n=3)),
|
|
(Microsecond(), 3, Microsecond(n=3)),
|
|
(Day(), 0.5, Hour(n=12)),
|
|
(Hour(), 0.5, Minute(n=30)),
|
|
(Hour(), -0.5, Minute(n=-30)),
|
|
(Minute(), 0.5, Second(n=30)),
|
|
(Second(), 0.5, Millisecond(n=500)),
|
|
(Millisecond(), 0.5, Microsecond(n=500)),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(("offset", "multiple", "expected"), _MUL_TESTS, ids=_id_func)
|
|
def test_mul(offset, multiple, expected):
|
|
assert offset * multiple == expected
|
|
|
|
|
|
@pytest.mark.parametrize(("offset", "multiple", "expected"), _MUL_TESTS, ids=_id_func)
|
|
def test_rmul(offset, multiple, expected):
|
|
assert multiple * offset == expected
|
|
|
|
|
|
def test_mul_float_multiple_next_higher_resolution():
|
|
"""Test more than one iteration through _next_higher_resolution is required."""
|
|
assert 1e-6 * Second() == Microsecond()
|
|
assert 1e-6 / 60 * Minute() == Microsecond()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"offset",
|
|
[YearBegin(), YearEnd(), QuarterBegin(), QuarterEnd(), MonthBegin(), MonthEnd()],
|
|
ids=_id_func,
|
|
)
|
|
def test_nonTick_offset_multiplied_float_error(offset):
|
|
"""Test that the appropriate error is raised if a non-Tick offset is
|
|
multiplied by a float."""
|
|
with pytest.raises(TypeError, match="unsupported operand type"):
|
|
offset * 0.5
|
|
|
|
|
|
def test_Microsecond_multiplied_float_error():
|
|
"""Test that the appropriate error is raised if a Tick offset is multiplied
|
|
by a float which causes it not to be representable by a
|
|
microsecond-precision timedelta."""
|
|
with pytest.raises(
|
|
ValueError, match="Could not convert to integer offset at any resolution"
|
|
):
|
|
Microsecond() * 0.5
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("offset", "expected"),
|
|
[
|
|
(BaseCFTimeOffset(), BaseCFTimeOffset(n=-1)),
|
|
(YearEnd(), YearEnd(n=-1)),
|
|
(YearBegin(), YearBegin(n=-1)),
|
|
(QuarterEnd(), QuarterEnd(n=-1)),
|
|
(QuarterBegin(), QuarterBegin(n=-1)),
|
|
(MonthEnd(), MonthEnd(n=-1)),
|
|
(MonthBegin(), MonthBegin(n=-1)),
|
|
(Day(), Day(n=-1)),
|
|
(Hour(), Hour(n=-1)),
|
|
(Minute(), Minute(n=-1)),
|
|
(Second(), Second(n=-1)),
|
|
(Millisecond(), Millisecond(n=-1)),
|
|
(Microsecond(), Microsecond(n=-1)),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_neg(offset: BaseCFTimeOffset, expected: BaseCFTimeOffset) -> None:
|
|
assert -offset == expected
|
|
|
|
|
|
_ADD_TESTS = [
|
|
(Day(n=2), (1, 1, 3)),
|
|
(Hour(n=2), (1, 1, 1, 2)),
|
|
(Minute(n=2), (1, 1, 1, 0, 2)),
|
|
(Second(n=2), (1, 1, 1, 0, 0, 2)),
|
|
(Millisecond(n=2), (1, 1, 1, 0, 0, 0, 2000)),
|
|
(Microsecond(n=2), (1, 1, 1, 0, 0, 0, 2)),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(("offset", "expected_date_args"), _ADD_TESTS, ids=_id_func)
|
|
def test_add_sub_monthly(offset, expected_date_args, calendar):
|
|
date_type = get_date_type(calendar)
|
|
initial = date_type(1, 1, 1)
|
|
expected = date_type(*expected_date_args)
|
|
result = offset + initial
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize(("offset", "expected_date_args"), _ADD_TESTS, ids=_id_func)
|
|
def test_radd_sub_monthly(offset, expected_date_args, calendar):
|
|
date_type = get_date_type(calendar)
|
|
initial = date_type(1, 1, 1)
|
|
expected = date_type(*expected_date_args)
|
|
result = initial + offset
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("offset", "expected_date_args"),
|
|
[
|
|
(Day(n=2), (1, 1, 1)),
|
|
(Hour(n=2), (1, 1, 2, 22)),
|
|
(Minute(n=2), (1, 1, 2, 23, 58)),
|
|
(Second(n=2), (1, 1, 2, 23, 59, 58)),
|
|
(Millisecond(n=2), (1, 1, 2, 23, 59, 59, 998000)),
|
|
(Microsecond(n=2), (1, 1, 2, 23, 59, 59, 999998)),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_rsub_sub_monthly(offset, expected_date_args, calendar):
|
|
date_type = get_date_type(calendar)
|
|
initial = date_type(1, 1, 3)
|
|
expected = date_type(*expected_date_args)
|
|
result = initial - offset
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize("offset", _EQ_TESTS_A, ids=_id_func)
|
|
def test_sub_error(offset, calendar):
|
|
date_type = get_date_type(calendar)
|
|
initial = date_type(1, 1, 1)
|
|
with pytest.raises(TypeError):
|
|
offset - initial
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("a", "b"), zip(_EQ_TESTS_A, _EQ_TESTS_B, strict=True), ids=_id_func
|
|
)
|
|
def test_minus_offset(a, b):
|
|
result = b - a
|
|
expected = a
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("a", "b"),
|
|
list(zip(np.roll(_EQ_TESTS_A, 1), _EQ_TESTS_B, strict=True)) # type: ignore[arg-type]
|
|
+ [(YearEnd(month=1), YearEnd(month=2))],
|
|
ids=_id_func,
|
|
)
|
|
def test_minus_offset_error(a, b):
|
|
with pytest.raises(TypeError):
|
|
b - a
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("initial_date_args", "offset", "expected_date_args"),
|
|
[
|
|
((1, 1, 1), MonthBegin(), (1, 2, 1)),
|
|
((1, 1, 1), MonthBegin(n=2), (1, 3, 1)),
|
|
((1, 1, 7), MonthBegin(), (1, 2, 1)),
|
|
((1, 1, 7), MonthBegin(n=2), (1, 3, 1)),
|
|
((1, 3, 1), MonthBegin(n=-1), (1, 2, 1)),
|
|
((1, 3, 1), MonthBegin(n=-2), (1, 1, 1)),
|
|
((1, 3, 3), MonthBegin(n=-1), (1, 3, 1)),
|
|
((1, 3, 3), MonthBegin(n=-2), (1, 2, 1)),
|
|
((1, 2, 1), MonthBegin(n=14), (2, 4, 1)),
|
|
((2, 4, 1), MonthBegin(n=-14), (1, 2, 1)),
|
|
((1, 1, 1, 5, 5, 5, 5), MonthBegin(), (1, 2, 1, 5, 5, 5, 5)),
|
|
((1, 1, 3, 5, 5, 5, 5), MonthBegin(), (1, 2, 1, 5, 5, 5, 5)),
|
|
((1, 1, 3, 5, 5, 5, 5), MonthBegin(n=-1), (1, 1, 1, 5, 5, 5, 5)),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_add_month_begin(calendar, initial_date_args, offset, expected_date_args):
|
|
date_type = get_date_type(calendar)
|
|
initial = date_type(*initial_date_args)
|
|
result = initial + offset
|
|
expected = date_type(*expected_date_args)
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("initial_date_args", "offset", "expected_year_month", "expected_sub_day"),
|
|
[
|
|
((1, 1, 1), MonthEnd(), (1, 1), ()),
|
|
((1, 1, 1), MonthEnd(n=2), (1, 2), ()),
|
|
((1, 3, 1), MonthEnd(n=-1), (1, 2), ()),
|
|
((1, 3, 1), MonthEnd(n=-2), (1, 1), ()),
|
|
((1, 2, 1), MonthEnd(n=14), (2, 3), ()),
|
|
((2, 4, 1), MonthEnd(n=-14), (1, 2), ()),
|
|
((1, 1, 1, 5, 5, 5, 5), MonthEnd(), (1, 1), (5, 5, 5, 5)),
|
|
((1, 2, 1, 5, 5, 5, 5), MonthEnd(n=-1), (1, 1), (5, 5, 5, 5)),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_add_month_end(
|
|
calendar, initial_date_args, offset, expected_year_month, expected_sub_day
|
|
):
|
|
date_type = get_date_type(calendar)
|
|
initial = date_type(*initial_date_args)
|
|
result = initial + offset
|
|
reference_args = expected_year_month + (1,)
|
|
reference = date_type(*reference_args)
|
|
|
|
# Here the days at the end of each month varies based on the calendar used
|
|
expected_date_args = (
|
|
expected_year_month + (reference.daysinmonth,) + expected_sub_day
|
|
)
|
|
expected = date_type(*expected_date_args)
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
(
|
|
"initial_year_month",
|
|
"initial_sub_day",
|
|
"offset",
|
|
"expected_year_month",
|
|
"expected_sub_day",
|
|
),
|
|
[
|
|
((1, 1), (), MonthEnd(), (1, 2), ()),
|
|
((1, 1), (), MonthEnd(n=2), (1, 3), ()),
|
|
((1, 3), (), MonthEnd(n=-1), (1, 2), ()),
|
|
((1, 3), (), MonthEnd(n=-2), (1, 1), ()),
|
|
((1, 2), (), MonthEnd(n=14), (2, 4), ()),
|
|
((2, 4), (), MonthEnd(n=-14), (1, 2), ()),
|
|
((1, 1), (5, 5, 5, 5), MonthEnd(), (1, 2), (5, 5, 5, 5)),
|
|
((1, 2), (5, 5, 5, 5), MonthEnd(n=-1), (1, 1), (5, 5, 5, 5)),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_add_month_end_onOffset(
|
|
calendar,
|
|
initial_year_month,
|
|
initial_sub_day,
|
|
offset,
|
|
expected_year_month,
|
|
expected_sub_day,
|
|
):
|
|
date_type = get_date_type(calendar)
|
|
reference_args = initial_year_month + (1,)
|
|
reference = date_type(*reference_args)
|
|
initial_date_args = initial_year_month + (reference.daysinmonth,) + initial_sub_day
|
|
initial = date_type(*initial_date_args)
|
|
result = initial + offset
|
|
reference_args = expected_year_month + (1,)
|
|
reference = date_type(*reference_args)
|
|
|
|
# Here the days at the end of each month varies based on the calendar used
|
|
expected_date_args = (
|
|
expected_year_month + (reference.daysinmonth,) + expected_sub_day
|
|
)
|
|
expected = date_type(*expected_date_args)
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("initial_date_args", "offset", "expected_date_args"),
|
|
[
|
|
((1, 1, 1), YearBegin(), (2, 1, 1)),
|
|
((1, 1, 1), YearBegin(n=2), (3, 1, 1)),
|
|
((1, 1, 1), YearBegin(month=2), (1, 2, 1)),
|
|
((1, 1, 7), YearBegin(n=2), (3, 1, 1)),
|
|
((2, 2, 1), YearBegin(n=-1), (2, 1, 1)),
|
|
((1, 1, 2), YearBegin(n=-1), (1, 1, 1)),
|
|
((1, 1, 1, 5, 5, 5, 5), YearBegin(), (2, 1, 1, 5, 5, 5, 5)),
|
|
((2, 1, 1, 5, 5, 5, 5), YearBegin(n=-1), (1, 1, 1, 5, 5, 5, 5)),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_add_year_begin(calendar, initial_date_args, offset, expected_date_args):
|
|
date_type = get_date_type(calendar)
|
|
initial = date_type(*initial_date_args)
|
|
result = initial + offset
|
|
expected = date_type(*expected_date_args)
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("initial_date_args", "offset", "expected_year_month", "expected_sub_day"),
|
|
[
|
|
((1, 1, 1), YearEnd(), (1, 12), ()),
|
|
((1, 1, 1), YearEnd(n=2), (2, 12), ()),
|
|
((1, 1, 1), YearEnd(month=1), (1, 1), ()),
|
|
((2, 3, 1), YearEnd(n=-1), (1, 12), ()),
|
|
((1, 3, 1), YearEnd(n=-1, month=2), (1, 2), ()),
|
|
((1, 1, 1, 5, 5, 5, 5), YearEnd(), (1, 12), (5, 5, 5, 5)),
|
|
((1, 1, 1, 5, 5, 5, 5), YearEnd(n=2), (2, 12), (5, 5, 5, 5)),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_add_year_end(
|
|
calendar, initial_date_args, offset, expected_year_month, expected_sub_day
|
|
):
|
|
date_type = get_date_type(calendar)
|
|
initial = date_type(*initial_date_args)
|
|
result = initial + offset
|
|
reference_args = expected_year_month + (1,)
|
|
reference = date_type(*reference_args)
|
|
|
|
# Here the days at the end of each month varies based on the calendar used
|
|
expected_date_args = (
|
|
expected_year_month + (reference.daysinmonth,) + expected_sub_day
|
|
)
|
|
expected = date_type(*expected_date_args)
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
(
|
|
"initial_year_month",
|
|
"initial_sub_day",
|
|
"offset",
|
|
"expected_year_month",
|
|
"expected_sub_day",
|
|
),
|
|
[
|
|
((1, 12), (), YearEnd(), (2, 12), ()),
|
|
((1, 12), (), YearEnd(n=2), (3, 12), ()),
|
|
((2, 12), (), YearEnd(n=-1), (1, 12), ()),
|
|
((3, 12), (), YearEnd(n=-2), (1, 12), ()),
|
|
((1, 1), (), YearEnd(month=2), (1, 2), ()),
|
|
((1, 12), (5, 5, 5, 5), YearEnd(), (2, 12), (5, 5, 5, 5)),
|
|
((2, 12), (5, 5, 5, 5), YearEnd(n=-1), (1, 12), (5, 5, 5, 5)),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_add_year_end_onOffset(
|
|
calendar,
|
|
initial_year_month,
|
|
initial_sub_day,
|
|
offset,
|
|
expected_year_month,
|
|
expected_sub_day,
|
|
):
|
|
date_type = get_date_type(calendar)
|
|
reference_args = initial_year_month + (1,)
|
|
reference = date_type(*reference_args)
|
|
initial_date_args = initial_year_month + (reference.daysinmonth,) + initial_sub_day
|
|
initial = date_type(*initial_date_args)
|
|
result = initial + offset
|
|
reference_args = expected_year_month + (1,)
|
|
reference = date_type(*reference_args)
|
|
|
|
# Here the days at the end of each month varies based on the calendar used
|
|
expected_date_args = (
|
|
expected_year_month + (reference.daysinmonth,) + expected_sub_day
|
|
)
|
|
expected = date_type(*expected_date_args)
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("initial_date_args", "offset", "expected_date_args"),
|
|
[
|
|
((1, 1, 1), QuarterBegin(), (1, 3, 1)),
|
|
((1, 1, 1), QuarterBegin(n=2), (1, 6, 1)),
|
|
((1, 1, 1), QuarterBegin(month=2), (1, 2, 1)),
|
|
((1, 1, 7), QuarterBegin(n=2), (1, 6, 1)),
|
|
((2, 2, 1), QuarterBegin(n=-1), (1, 12, 1)),
|
|
((1, 3, 2), QuarterBegin(n=-1), (1, 3, 1)),
|
|
((1, 1, 1, 5, 5, 5, 5), QuarterBegin(), (1, 3, 1, 5, 5, 5, 5)),
|
|
((2, 1, 1, 5, 5, 5, 5), QuarterBegin(n=-1), (1, 12, 1, 5, 5, 5, 5)),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_add_quarter_begin(calendar, initial_date_args, offset, expected_date_args):
|
|
date_type = get_date_type(calendar)
|
|
initial = date_type(*initial_date_args)
|
|
result = initial + offset
|
|
expected = date_type(*expected_date_args)
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("initial_date_args", "offset", "expected_year_month", "expected_sub_day"),
|
|
[
|
|
((1, 1, 1), QuarterEnd(), (1, 3), ()),
|
|
((1, 1, 1), QuarterEnd(n=2), (1, 6), ()),
|
|
((1, 1, 1), QuarterEnd(month=1), (1, 1), ()),
|
|
((2, 3, 1), QuarterEnd(n=-1), (1, 12), ()),
|
|
((1, 3, 1), QuarterEnd(n=-1, month=2), (1, 2), ()),
|
|
((1, 1, 1, 5, 5, 5, 5), QuarterEnd(), (1, 3), (5, 5, 5, 5)),
|
|
((1, 1, 1, 5, 5, 5, 5), QuarterEnd(n=2), (1, 6), (5, 5, 5, 5)),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_add_quarter_end(
|
|
calendar, initial_date_args, offset, expected_year_month, expected_sub_day
|
|
):
|
|
date_type = get_date_type(calendar)
|
|
initial = date_type(*initial_date_args)
|
|
result = initial + offset
|
|
reference_args = expected_year_month + (1,)
|
|
reference = date_type(*reference_args)
|
|
|
|
# Here the days at the end of each month varies based on the calendar used
|
|
expected_date_args = (
|
|
expected_year_month + (reference.daysinmonth,) + expected_sub_day
|
|
)
|
|
expected = date_type(*expected_date_args)
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
(
|
|
"initial_year_month",
|
|
"initial_sub_day",
|
|
"offset",
|
|
"expected_year_month",
|
|
"expected_sub_day",
|
|
),
|
|
[
|
|
((1, 12), (), QuarterEnd(), (2, 3), ()),
|
|
((1, 12), (), QuarterEnd(n=2), (2, 6), ()),
|
|
((1, 12), (), QuarterEnd(n=-1), (1, 9), ()),
|
|
((1, 12), (), QuarterEnd(n=-2), (1, 6), ()),
|
|
((1, 1), (), QuarterEnd(month=2), (1, 2), ()),
|
|
((1, 12), (5, 5, 5, 5), QuarterEnd(), (2, 3), (5, 5, 5, 5)),
|
|
((1, 12), (5, 5, 5, 5), QuarterEnd(n=-1), (1, 9), (5, 5, 5, 5)),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_add_quarter_end_onOffset(
|
|
calendar,
|
|
initial_year_month,
|
|
initial_sub_day,
|
|
offset,
|
|
expected_year_month,
|
|
expected_sub_day,
|
|
):
|
|
date_type = get_date_type(calendar)
|
|
reference_args = initial_year_month + (1,)
|
|
reference = date_type(*reference_args)
|
|
initial_date_args = initial_year_month + (reference.daysinmonth,) + initial_sub_day
|
|
initial = date_type(*initial_date_args)
|
|
result = initial + offset
|
|
reference_args = expected_year_month + (1,)
|
|
reference = date_type(*reference_args)
|
|
|
|
# Here the days at the end of each month varies based on the calendar used
|
|
expected_date_args = (
|
|
expected_year_month + (reference.daysinmonth,) + expected_sub_day
|
|
)
|
|
expected = date_type(*expected_date_args)
|
|
assert result == expected
|
|
|
|
|
|
# Note for all sub-monthly offsets, pandas always returns True for onOffset
|
|
@pytest.mark.parametrize(
|
|
("date_args", "offset", "expected"),
|
|
[
|
|
((1, 1, 1), MonthBegin(), True),
|
|
((1, 1, 1, 1), MonthBegin(), True),
|
|
((1, 1, 5), MonthBegin(), False),
|
|
((1, 1, 5), MonthEnd(), False),
|
|
((1, 3, 1), QuarterBegin(), True),
|
|
((1, 3, 1, 1), QuarterBegin(), True),
|
|
((1, 3, 5), QuarterBegin(), False),
|
|
((1, 12, 1), QuarterEnd(), False),
|
|
((1, 1, 1), YearBegin(), True),
|
|
((1, 1, 1, 1), YearBegin(), True),
|
|
((1, 1, 5), YearBegin(), False),
|
|
((1, 12, 1), YearEnd(), False),
|
|
((1, 1, 1), Day(), True),
|
|
((1, 1, 1, 1), Day(), True),
|
|
((1, 1, 1), Hour(), True),
|
|
((1, 1, 1), Minute(), True),
|
|
((1, 1, 1), Second(), True),
|
|
((1, 1, 1), Millisecond(), True),
|
|
((1, 1, 1), Microsecond(), True),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_onOffset(calendar, date_args, offset, expected):
|
|
date_type = get_date_type(calendar)
|
|
date = date_type(*date_args)
|
|
result = offset.onOffset(date)
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("year_month_args", "sub_day_args", "offset"),
|
|
[
|
|
((1, 1), (), MonthEnd()),
|
|
((1, 1), (1,), MonthEnd()),
|
|
((1, 12), (), QuarterEnd()),
|
|
((1, 1), (), QuarterEnd(month=1)),
|
|
((1, 12), (), YearEnd()),
|
|
((1, 1), (), YearEnd(month=1)),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_onOffset_month_or_quarter_or_year_end(
|
|
calendar, year_month_args, sub_day_args, offset
|
|
):
|
|
date_type = get_date_type(calendar)
|
|
reference_args = year_month_args + (1,)
|
|
reference = date_type(*reference_args)
|
|
date_args = year_month_args + (reference.daysinmonth,) + sub_day_args
|
|
date = date_type(*date_args)
|
|
result = offset.onOffset(date)
|
|
assert result
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("offset", "initial_date_args", "partial_expected_date_args"),
|
|
[
|
|
(YearBegin(), (1, 3, 1), (2, 1)),
|
|
(YearBegin(), (1, 1, 1), (1, 1)),
|
|
(YearBegin(n=2), (1, 3, 1), (2, 1)),
|
|
(YearBegin(n=2, month=2), (1, 3, 1), (2, 2)),
|
|
(YearEnd(), (1, 3, 1), (1, 12)),
|
|
(YearEnd(n=2), (1, 3, 1), (1, 12)),
|
|
(YearEnd(n=2, month=2), (1, 3, 1), (2, 2)),
|
|
(YearEnd(n=2, month=4), (1, 4, 30), (1, 4)),
|
|
(QuarterBegin(), (1, 3, 2), (1, 6)),
|
|
(QuarterBegin(), (1, 4, 1), (1, 6)),
|
|
(QuarterBegin(n=2), (1, 4, 1), (1, 6)),
|
|
(QuarterBegin(n=2, month=2), (1, 4, 1), (1, 5)),
|
|
(QuarterEnd(), (1, 3, 1), (1, 3)),
|
|
(QuarterEnd(n=2), (1, 3, 1), (1, 3)),
|
|
(QuarterEnd(n=2, month=2), (1, 3, 1), (1, 5)),
|
|
(QuarterEnd(n=2, month=4), (1, 4, 30), (1, 4)),
|
|
(MonthBegin(), (1, 3, 2), (1, 4)),
|
|
(MonthBegin(), (1, 3, 1), (1, 3)),
|
|
(MonthBegin(n=2), (1, 3, 2), (1, 4)),
|
|
(MonthEnd(), (1, 3, 2), (1, 3)),
|
|
(MonthEnd(), (1, 4, 30), (1, 4)),
|
|
(MonthEnd(n=2), (1, 3, 2), (1, 3)),
|
|
(Day(), (1, 3, 2, 1), (1, 3, 2, 1)),
|
|
(Hour(), (1, 3, 2, 1, 1), (1, 3, 2, 1, 1)),
|
|
(Minute(), (1, 3, 2, 1, 1, 1), (1, 3, 2, 1, 1, 1)),
|
|
(Second(), (1, 3, 2, 1, 1, 1, 1), (1, 3, 2, 1, 1, 1, 1)),
|
|
(Millisecond(), (1, 3, 2, 1, 1, 1, 1000), (1, 3, 2, 1, 1, 1, 1000)),
|
|
(Microsecond(), (1, 3, 2, 1, 1, 1, 1), (1, 3, 2, 1, 1, 1, 1)),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_rollforward(calendar, offset, initial_date_args, partial_expected_date_args):
|
|
date_type = get_date_type(calendar)
|
|
initial = date_type(*initial_date_args)
|
|
if isinstance(offset, MonthBegin | QuarterBegin | YearBegin):
|
|
expected_date_args = partial_expected_date_args + (1,)
|
|
elif isinstance(offset, MonthEnd | QuarterEnd | YearEnd):
|
|
reference_args = partial_expected_date_args + (1,)
|
|
reference = date_type(*reference_args)
|
|
expected_date_args = partial_expected_date_args + (reference.daysinmonth,)
|
|
else:
|
|
expected_date_args = partial_expected_date_args
|
|
expected = date_type(*expected_date_args)
|
|
result = offset.rollforward(initial)
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("offset", "initial_date_args", "partial_expected_date_args"),
|
|
[
|
|
(YearBegin(), (1, 3, 1), (1, 1)),
|
|
(YearBegin(n=2), (1, 3, 1), (1, 1)),
|
|
(YearBegin(n=2, month=2), (1, 3, 1), (1, 2)),
|
|
(YearBegin(), (1, 1, 1), (1, 1)),
|
|
(YearBegin(n=2, month=2), (1, 2, 1), (1, 2)),
|
|
(YearEnd(), (2, 3, 1), (1, 12)),
|
|
(YearEnd(n=2), (2, 3, 1), (1, 12)),
|
|
(YearEnd(n=2, month=2), (2, 3, 1), (2, 2)),
|
|
(YearEnd(month=4), (1, 4, 30), (1, 4)),
|
|
(QuarterBegin(), (1, 3, 2), (1, 3)),
|
|
(QuarterBegin(), (1, 4, 1), (1, 3)),
|
|
(QuarterBegin(n=2), (1, 4, 1), (1, 3)),
|
|
(QuarterBegin(n=2, month=2), (1, 4, 1), (1, 2)),
|
|
(QuarterEnd(), (2, 3, 1), (1, 12)),
|
|
(QuarterEnd(n=2), (2, 3, 1), (1, 12)),
|
|
(QuarterEnd(n=2, month=2), (2, 3, 1), (2, 2)),
|
|
(QuarterEnd(n=2, month=4), (1, 4, 30), (1, 4)),
|
|
(MonthBegin(), (1, 3, 2), (1, 3)),
|
|
(MonthBegin(n=2), (1, 3, 2), (1, 3)),
|
|
(MonthBegin(), (1, 3, 1), (1, 3)),
|
|
(MonthEnd(), (1, 3, 2), (1, 2)),
|
|
(MonthEnd(n=2), (1, 3, 2), (1, 2)),
|
|
(MonthEnd(), (1, 4, 30), (1, 4)),
|
|
(Day(), (1, 3, 2, 1), (1, 3, 2, 1)),
|
|
(Hour(), (1, 3, 2, 1, 1), (1, 3, 2, 1, 1)),
|
|
(Minute(), (1, 3, 2, 1, 1, 1), (1, 3, 2, 1, 1, 1)),
|
|
(Second(), (1, 3, 2, 1, 1, 1, 1), (1, 3, 2, 1, 1, 1, 1)),
|
|
(Millisecond(), (1, 3, 2, 1, 1, 1, 1000), (1, 3, 2, 1, 1, 1, 1000)),
|
|
(Microsecond(), (1, 3, 2, 1, 1, 1, 1), (1, 3, 2, 1, 1, 1, 1)),
|
|
],
|
|
ids=_id_func,
|
|
)
|
|
def test_rollback(calendar, offset, initial_date_args, partial_expected_date_args):
|
|
date_type = get_date_type(calendar)
|
|
initial = date_type(*initial_date_args)
|
|
if isinstance(offset, MonthBegin | QuarterBegin | YearBegin):
|
|
expected_date_args = partial_expected_date_args + (1,)
|
|
elif isinstance(offset, MonthEnd | QuarterEnd | YearEnd):
|
|
reference_args = partial_expected_date_args + (1,)
|
|
reference = date_type(*reference_args)
|
|
expected_date_args = partial_expected_date_args + (reference.daysinmonth,)
|
|
else:
|
|
expected_date_args = partial_expected_date_args
|
|
expected = date_type(*expected_date_args)
|
|
result = offset.rollback(initial)
|
|
assert result == expected
|
|
|
|
|
|
_CFTIME_RANGE_TESTS = [
|
|
(
|
|
"0001-01-01",
|
|
"0001-01-04",
|
|
None,
|
|
"D",
|
|
"neither",
|
|
False,
|
|
[(1, 1, 2), (1, 1, 3)],
|
|
),
|
|
(
|
|
"0001-01-01",
|
|
"0001-01-04",
|
|
None,
|
|
"D",
|
|
"both",
|
|
False,
|
|
[(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)],
|
|
),
|
|
(
|
|
"0001-01-01",
|
|
"0001-01-04",
|
|
None,
|
|
"D",
|
|
"left",
|
|
False,
|
|
[(1, 1, 1), (1, 1, 2), (1, 1, 3)],
|
|
),
|
|
(
|
|
"0001-01-01",
|
|
"0001-01-04",
|
|
None,
|
|
"D",
|
|
"right",
|
|
False,
|
|
[(1, 1, 2), (1, 1, 3), (1, 1, 4)],
|
|
),
|
|
(
|
|
"0001-01-01T01:00:00",
|
|
"0001-01-04",
|
|
None,
|
|
"D",
|
|
"both",
|
|
False,
|
|
[(1, 1, 1, 1), (1, 1, 2, 1), (1, 1, 3, 1)],
|
|
),
|
|
(
|
|
"0001-01-01 01:00:00",
|
|
"0001-01-04",
|
|
None,
|
|
"D",
|
|
"both",
|
|
False,
|
|
[(1, 1, 1, 1), (1, 1, 2, 1), (1, 1, 3, 1)],
|
|
),
|
|
(
|
|
"0001-01-01T01:00:00",
|
|
"0001-01-04",
|
|
None,
|
|
"D",
|
|
"both",
|
|
True,
|
|
[(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)],
|
|
),
|
|
(
|
|
"0001-01-01",
|
|
None,
|
|
4,
|
|
"D",
|
|
"both",
|
|
False,
|
|
[(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)],
|
|
),
|
|
(
|
|
None,
|
|
"0001-01-04",
|
|
4,
|
|
"D",
|
|
"both",
|
|
False,
|
|
[(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)],
|
|
),
|
|
(
|
|
(1, 1, 1),
|
|
"0001-01-04",
|
|
None,
|
|
"D",
|
|
"both",
|
|
False,
|
|
[(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)],
|
|
),
|
|
(
|
|
(1, 1, 1),
|
|
(1, 1, 4),
|
|
None,
|
|
"D",
|
|
"both",
|
|
False,
|
|
[(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)],
|
|
),
|
|
(
|
|
"0001-01-30",
|
|
"0011-02-01",
|
|
None,
|
|
"3YS-JUN",
|
|
"both",
|
|
False,
|
|
[(1, 6, 1), (4, 6, 1), (7, 6, 1), (10, 6, 1)],
|
|
),
|
|
("0001-01-04", "0001-01-01", None, "D", "both", False, []),
|
|
(
|
|
"0010",
|
|
None,
|
|
4,
|
|
YearBegin(n=-2),
|
|
"both",
|
|
False,
|
|
[(10, 1, 1), (8, 1, 1), (6, 1, 1), (4, 1, 1)],
|
|
),
|
|
(
|
|
"0010",
|
|
None,
|
|
4,
|
|
"-2YS",
|
|
"both",
|
|
False,
|
|
[(10, 1, 1), (8, 1, 1), (6, 1, 1), (4, 1, 1)],
|
|
),
|
|
(
|
|
"0001-01-01",
|
|
"0001-01-04",
|
|
4,
|
|
None,
|
|
"both",
|
|
False,
|
|
[(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 1, 4)],
|
|
),
|
|
(
|
|
"0001-06-01",
|
|
None,
|
|
4,
|
|
"3QS-JUN",
|
|
"both",
|
|
False,
|
|
[(1, 6, 1), (2, 3, 1), (2, 12, 1), (3, 9, 1)],
|
|
),
|
|
(
|
|
"0001-06-01",
|
|
None,
|
|
4,
|
|
"-1MS",
|
|
"both",
|
|
False,
|
|
[(1, 6, 1), (1, 5, 1), (1, 4, 1), (1, 3, 1)],
|
|
),
|
|
(
|
|
"0001-01-30",
|
|
None,
|
|
4,
|
|
"-1D",
|
|
"both",
|
|
False,
|
|
[(1, 1, 30), (1, 1, 29), (1, 1, 28), (1, 1, 27)],
|
|
),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("start", "end", "periods", "freq", "inclusive", "normalize", "expected_date_args"),
|
|
_CFTIME_RANGE_TESTS,
|
|
ids=_id_func,
|
|
)
|
|
def test_cftime_range(
|
|
start, end, periods, freq, inclusive, normalize, calendar, expected_date_args
|
|
):
|
|
date_type = get_date_type(calendar)
|
|
expected_dates = [date_type(*args) for args in expected_date_args]
|
|
|
|
if isinstance(start, tuple):
|
|
start = date_type(*start)
|
|
if isinstance(end, tuple):
|
|
end = date_type(*end)
|
|
|
|
result = cftime_range(
|
|
start=start,
|
|
end=end,
|
|
periods=periods,
|
|
freq=freq,
|
|
inclusive=inclusive,
|
|
normalize=normalize,
|
|
calendar=calendar,
|
|
)
|
|
resulting_dates = result.values
|
|
|
|
assert isinstance(result, CFTimeIndex)
|
|
|
|
if freq is not None:
|
|
np.testing.assert_equal(resulting_dates, expected_dates)
|
|
else:
|
|
# If we create a linear range of dates using cftime.num2date
|
|
# we will not get exact round number dates. This is because
|
|
# datetime arithmetic in cftime is accurate approximately to
|
|
# 1 millisecond (see https://unidata.github.io/cftime/api.html).
|
|
deltas = resulting_dates - expected_dates
|
|
deltas = np.array([delta.total_seconds() for delta in deltas])
|
|
assert np.max(np.abs(deltas)) < 0.001
|
|
|
|
|
|
def test_cftime_range_name():
|
|
result = cftime_range(start="2000", periods=4, name="foo")
|
|
assert result.name == "foo"
|
|
|
|
result = cftime_range(start="2000", periods=4)
|
|
assert result.name is None
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("start", "end", "periods", "freq", "inclusive"),
|
|
[
|
|
(None, None, 5, "YE", None),
|
|
("2000", None, None, "YE", None),
|
|
(None, "2000", None, "YE", None),
|
|
(None, None, None, None, None),
|
|
("2000", "2001", None, "YE", "up"),
|
|
("2000", "2001", 5, "YE", None),
|
|
],
|
|
)
|
|
def test_invalid_cftime_range_inputs(
|
|
start: str | None,
|
|
end: str | None,
|
|
periods: int | None,
|
|
freq: str | None,
|
|
inclusive: Literal["up", None],
|
|
) -> None:
|
|
with pytest.raises(ValueError):
|
|
cftime_range(start, end, periods, freq, inclusive=inclusive) # type: ignore[arg-type]
|
|
|
|
|
|
_CALENDAR_SPECIFIC_MONTH_END_TESTS = [
|
|
("noleap", [(2, 28), (4, 30), (6, 30), (8, 31), (10, 31), (12, 31)]),
|
|
("all_leap", [(2, 29), (4, 30), (6, 30), (8, 31), (10, 31), (12, 31)]),
|
|
("360_day", [(2, 30), (4, 30), (6, 30), (8, 30), (10, 30), (12, 30)]),
|
|
("standard", [(2, 29), (4, 30), (6, 30), (8, 31), (10, 31), (12, 31)]),
|
|
("gregorian", [(2, 29), (4, 30), (6, 30), (8, 31), (10, 31), (12, 31)]),
|
|
("julian", [(2, 29), (4, 30), (6, 30), (8, 31), (10, 31), (12, 31)]),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("calendar", "expected_month_day"),
|
|
_CALENDAR_SPECIFIC_MONTH_END_TESTS,
|
|
ids=_id_func,
|
|
)
|
|
def test_calendar_specific_month_end(
|
|
calendar: str, expected_month_day: list[tuple[int, int]]
|
|
) -> None:
|
|
year = 2000 # Use a leap-year to highlight calendar differences
|
|
result = cftime_range(
|
|
start="2000-02", end="2001", freq="2ME", calendar=calendar
|
|
).values
|
|
date_type = get_date_type(calendar)
|
|
expected = [date_type(year, *args) for args in expected_month_day]
|
|
np.testing.assert_equal(result, expected)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("calendar", "expected_month_day"),
|
|
_CALENDAR_SPECIFIC_MONTH_END_TESTS,
|
|
ids=_id_func,
|
|
)
|
|
def test_calendar_specific_month_end_negative_freq(
|
|
calendar: str, expected_month_day: list[tuple[int, int]]
|
|
) -> None:
|
|
year = 2000 # Use a leap-year to highlight calendar differences
|
|
result = cftime_range(
|
|
start="2001",
|
|
end="2000",
|
|
freq="-2ME",
|
|
calendar=calendar,
|
|
).values
|
|
date_type = get_date_type(calendar)
|
|
expected = [date_type(year, *args) for args in expected_month_day[::-1]]
|
|
np.testing.assert_equal(result, expected)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("calendar", "start", "end", "expected_number_of_days"),
|
|
[
|
|
("noleap", "2000", "2001", 365),
|
|
("all_leap", "2000", "2001", 366),
|
|
("360_day", "2000", "2001", 360),
|
|
("standard", "2000", "2001", 366),
|
|
("gregorian", "2000", "2001", 366),
|
|
("julian", "2000", "2001", 366),
|
|
("noleap", "2001", "2002", 365),
|
|
("all_leap", "2001", "2002", 366),
|
|
("360_day", "2001", "2002", 360),
|
|
("standard", "2001", "2002", 365),
|
|
("gregorian", "2001", "2002", 365),
|
|
("julian", "2001", "2002", 365),
|
|
],
|
|
)
|
|
def test_calendar_year_length(
|
|
calendar: str, start: str, end: str, expected_number_of_days: int
|
|
) -> None:
|
|
result = cftime_range(start, end, freq="D", inclusive="left", calendar=calendar)
|
|
assert len(result) == expected_number_of_days
|
|
|
|
|
|
@pytest.mark.parametrize("freq", ["YE", "ME", "D"])
|
|
def test_dayofweek_after_cftime_range(freq: str) -> None:
|
|
result = cftime_range("2000-02-01", periods=3, freq=freq).dayofweek
|
|
# TODO: remove once requiring pandas 2.2+
|
|
freq = _new_to_legacy_freq(freq)
|
|
expected = pd.date_range("2000-02-01", periods=3, freq=freq).dayofweek
|
|
np.testing.assert_array_equal(result, expected)
|
|
|
|
|
|
@pytest.mark.parametrize("freq", ["YE", "ME", "D"])
|
|
def test_dayofyear_after_cftime_range(freq: str) -> None:
|
|
result = cftime_range("2000-02-01", periods=3, freq=freq).dayofyear
|
|
# TODO: remove once requiring pandas 2.2+
|
|
freq = _new_to_legacy_freq(freq)
|
|
expected = pd.date_range("2000-02-01", periods=3, freq=freq).dayofyear
|
|
np.testing.assert_array_equal(result, expected)
|
|
|
|
|
|
def test_cftime_range_standard_calendar_refers_to_gregorian() -> None:
|
|
from cftime import DatetimeGregorian
|
|
|
|
(result,) = cftime_range("2000", periods=1)
|
|
assert isinstance(result, DatetimeGregorian)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"start,calendar,use_cftime,expected_type",
|
|
[
|
|
("1990-01-01", "standard", None, pd.DatetimeIndex),
|
|
("1990-01-01", "proleptic_gregorian", True, CFTimeIndex),
|
|
("1990-01-01", "noleap", None, CFTimeIndex),
|
|
("1990-01-01", "gregorian", False, pd.DatetimeIndex),
|
|
("1400-01-01", "standard", None, CFTimeIndex),
|
|
("3400-01-01", "standard", None, CFTimeIndex),
|
|
],
|
|
)
|
|
def test_date_range(
|
|
start: str, calendar: str, use_cftime: bool | None, expected_type
|
|
) -> None:
|
|
dr = date_range(
|
|
start, periods=14, freq="D", calendar=calendar, use_cftime=use_cftime
|
|
)
|
|
|
|
assert isinstance(dr, expected_type)
|
|
|
|
|
|
def test_date_range_errors() -> None:
|
|
with pytest.raises(ValueError, match="Date range is invalid"):
|
|
date_range(
|
|
"1400-01-01", periods=1, freq="D", calendar="standard", use_cftime=False
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="Date range is invalid"):
|
|
date_range(
|
|
"2480-01-01",
|
|
periods=1,
|
|
freq="D",
|
|
calendar="proleptic_gregorian",
|
|
use_cftime=False,
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="Invalid calendar "):
|
|
date_range(
|
|
"1900-01-01", periods=1, freq="D", calendar="noleap", use_cftime=False
|
|
)
|
|
|
|
|
|
@requires_cftime
|
|
@pytest.mark.parametrize(
|
|
"start,freq,cal_src,cal_tgt,use_cftime,exp0,exp_pd",
|
|
[
|
|
("2020-02-01", "4ME", "standard", "noleap", None, "2020-02-28", False),
|
|
("2020-02-01", "ME", "noleap", "gregorian", True, "2020-02-29", True),
|
|
("2020-02-01", "QE-DEC", "noleap", "gregorian", True, "2020-03-31", True),
|
|
("2020-02-01", "YS-FEB", "noleap", "gregorian", True, "2020-02-01", True),
|
|
("2020-02-01", "YE-FEB", "noleap", "gregorian", True, "2020-02-29", True),
|
|
("2020-02-01", "-1YE-FEB", "noleap", "gregorian", True, "2019-02-28", True),
|
|
("2020-02-28", "3h", "all_leap", "gregorian", False, "2020-02-28", True),
|
|
("2020-03-30", "ME", "360_day", "gregorian", False, "2020-03-31", True),
|
|
("2020-03-31", "ME", "gregorian", "360_day", None, "2020-03-30", False),
|
|
("2020-03-31", "-1ME", "gregorian", "360_day", None, "2020-03-30", False),
|
|
],
|
|
)
|
|
def test_date_range_like(start, freq, cal_src, cal_tgt, use_cftime, exp0, exp_pd):
|
|
expected_freq = freq
|
|
|
|
source = date_range(start, periods=12, freq=freq, calendar=cal_src)
|
|
|
|
out = date_range_like(source, cal_tgt, use_cftime=use_cftime)
|
|
|
|
assert len(out) == 12
|
|
|
|
assert infer_freq(out) == expected_freq
|
|
|
|
assert out[0].isoformat().startswith(exp0)
|
|
|
|
if exp_pd:
|
|
assert isinstance(out, pd.DatetimeIndex)
|
|
else:
|
|
assert isinstance(out, CFTimeIndex)
|
|
assert out.calendar == cal_tgt
|
|
|
|
|
|
@requires_cftime
|
|
@pytest.mark.parametrize(
|
|
"freq", ("YE", "YS", "YE-MAY", "MS", "ME", "QS", "h", "min", "s")
|
|
)
|
|
@pytest.mark.parametrize("use_cftime", (True, False))
|
|
def test_date_range_like_no_deprecation(freq, use_cftime):
|
|
# ensure no internal warnings
|
|
# TODO: remove once freq string deprecation is finished
|
|
|
|
source = date_range("2000", periods=3, freq=freq, use_cftime=False)
|
|
|
|
with assert_no_warnings():
|
|
date_range_like(source, "standard", use_cftime=use_cftime)
|
|
|
|
|
|
def test_date_range_like_same_calendar():
|
|
src = date_range("2000-01-01", periods=12, freq="6h", use_cftime=False)
|
|
out = date_range_like(src, "standard", use_cftime=False)
|
|
assert src is out
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:Converting non-default")
|
|
def test_date_range_like_errors():
|
|
src = date_range("1899-02-03", periods=20, freq="D", use_cftime=False)
|
|
src = src[np.arange(20) != 10] # Remove 1 day so the frequency is not inferable.
|
|
|
|
with pytest.raises(
|
|
ValueError,
|
|
match="`date_range_like` was unable to generate a range as the source frequency was not inferable.",
|
|
):
|
|
date_range_like(src, "gregorian")
|
|
|
|
src = DataArray(
|
|
np.array(
|
|
[["1999-01-01", "1999-01-02"], ["1999-01-03", "1999-01-04"]],
|
|
dtype=np.datetime64,
|
|
),
|
|
dims=("x", "y"),
|
|
)
|
|
with pytest.raises(
|
|
ValueError,
|
|
match="'source' must be a 1D array of datetime objects for inferring its range.",
|
|
):
|
|
date_range_like(src, "noleap")
|
|
|
|
da = DataArray([1, 2, 3, 4], dims=("time",))
|
|
with pytest.raises(
|
|
ValueError,
|
|
match="'source' must be a 1D array of datetime objects for inferring its range.",
|
|
):
|
|
date_range_like(da, "noleap")
|
|
|
|
|
|
def as_timedelta_not_implemented_error():
|
|
tick = Tick()
|
|
with pytest.raises(NotImplementedError):
|
|
tick.as_timedelta()
|
|
|
|
|
|
@pytest.mark.parametrize("function", [cftime_range, date_range])
|
|
def test_cftime_or_date_range_invalid_inclusive_value(function: Callable) -> None:
|
|
if function == cftime_range and not has_cftime:
|
|
pytest.skip("requires cftime")
|
|
|
|
with pytest.raises(ValueError, match="nclusive"):
|
|
function("2000", periods=3, inclusive="foo")
|
|
|
|
|
|
@pytest.mark.parametrize("function", [cftime_range, date_range])
|
|
def test_cftime_or_date_range_inclusive_None(function) -> None:
|
|
if function == cftime_range and not has_cftime:
|
|
pytest.skip("requires cftime")
|
|
|
|
result_None = function("2000-01-01", "2000-01-04")
|
|
result_both = function("2000-01-01", "2000-01-04", inclusive="both")
|
|
np.testing.assert_equal(result_None.values, result_both.values)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"freq", ["A", "AS", "Q", "M", "H", "T", "S", "L", "U", "Y", "A-MAY"]
|
|
)
|
|
def test_to_offset_deprecation_warning(freq):
|
|
# Test for deprecations outlined in GitHub issue #8394
|
|
with pytest.warns(FutureWarning, match="is deprecated"):
|
|
to_offset(freq)
|
|
|
|
|
|
@pytest.mark.skipif(has_pandas_ge_2_2, reason="only relevant for pandas lt 2.2")
|
|
@pytest.mark.parametrize(
|
|
"freq, expected",
|
|
(
|
|
["Y", "YE"],
|
|
["A", "YE"],
|
|
["Q", "QE"],
|
|
["M", "ME"],
|
|
["AS", "YS"],
|
|
["YE", "YE"],
|
|
["QE", "QE"],
|
|
["ME", "ME"],
|
|
["YS", "YS"],
|
|
),
|
|
)
|
|
@pytest.mark.parametrize("n", ("", "2"))
|
|
def test_legacy_to_new_freq(freq, expected, n):
|
|
freq = f"{n}{freq}"
|
|
result = _legacy_to_new_freq(freq)
|
|
|
|
expected = f"{n}{expected}"
|
|
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.skipif(has_pandas_ge_2_2, reason="only relevant for pandas lt 2.2")
|
|
@pytest.mark.parametrize("year_alias", ("YE", "Y", "A"))
|
|
@pytest.mark.parametrize("n", ("", "2"))
|
|
def test_legacy_to_new_freq_anchored(year_alias, n):
|
|
for month in _MONTH_ABBREVIATIONS.values():
|
|
freq = f"{n}{year_alias}-{month}"
|
|
result = _legacy_to_new_freq(freq)
|
|
|
|
expected = f"{n}YE-{month}"
|
|
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.skipif(has_pandas_ge_2_2, reason="only relevant for pandas lt 2.2")
|
|
@pytest.mark.filterwarnings("ignore:'[AY]' is deprecated")
|
|
@pytest.mark.parametrize(
|
|
"freq, expected",
|
|
(["A", "A"], ["YE", "A"], ["Y", "A"], ["QE", "Q"], ["ME", "M"], ["YS", "AS"]),
|
|
)
|
|
@pytest.mark.parametrize("n", ("", "2"))
|
|
def test_new_to_legacy_freq(freq, expected, n):
|
|
freq = f"{n}{freq}"
|
|
result = _new_to_legacy_freq(freq)
|
|
|
|
expected = f"{n}{expected}"
|
|
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.skipif(has_pandas_ge_2_2, reason="only relevant for pandas lt 2.2")
|
|
@pytest.mark.filterwarnings("ignore:'[AY]-.{3}' is deprecated")
|
|
@pytest.mark.parametrize("year_alias", ("A", "Y", "YE"))
|
|
@pytest.mark.parametrize("n", ("", "2"))
|
|
def test_new_to_legacy_freq_anchored(year_alias, n):
|
|
for month in _MONTH_ABBREVIATIONS.values():
|
|
freq = f"{n}{year_alias}-{month}"
|
|
result = _new_to_legacy_freq(freq)
|
|
|
|
expected = f"{n}A-{month}"
|
|
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.skipif(has_pandas_ge_2_2, reason="only for pandas lt 2.2")
|
|
@pytest.mark.parametrize(
|
|
"freq, expected",
|
|
(
|
|
# pandas-only freq strings are passed through
|
|
("BH", "BH"),
|
|
("CBH", "CBH"),
|
|
("N", "N"),
|
|
),
|
|
)
|
|
def test_legacy_to_new_freq_pd_freq_passthrough(freq, expected):
|
|
result = _legacy_to_new_freq(freq)
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:'.' is deprecated ")
|
|
@pytest.mark.skipif(has_pandas_ge_2_2, reason="only for pandas lt 2.2")
|
|
@pytest.mark.parametrize(
|
|
"freq, expected",
|
|
(
|
|
# these are each valid in pandas lt 2.2
|
|
("T", "T"),
|
|
("min", "min"),
|
|
("S", "S"),
|
|
("s", "s"),
|
|
("L", "L"),
|
|
("ms", "ms"),
|
|
("U", "U"),
|
|
("us", "us"),
|
|
# pandas-only freq strings are passed through
|
|
("bh", "bh"),
|
|
("cbh", "cbh"),
|
|
("ns", "ns"),
|
|
),
|
|
)
|
|
def test_new_to_legacy_freq_pd_freq_passthrough(freq, expected):
|
|
result = _new_to_legacy_freq(freq)
|
|
assert result == expected
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:Converting a CFTimeIndex with:")
|
|
@pytest.mark.parametrize("start", ("2000", "2001"))
|
|
@pytest.mark.parametrize("end", ("2000", "2001"))
|
|
@pytest.mark.parametrize(
|
|
"freq",
|
|
(
|
|
"MS",
|
|
pytest.param("-1MS", marks=requires_pandas_3),
|
|
"YS",
|
|
pytest.param("-1YS", marks=requires_pandas_3),
|
|
"ME",
|
|
pytest.param("-1ME", marks=requires_pandas_3),
|
|
"YE",
|
|
pytest.param("-1YE", marks=requires_pandas_3),
|
|
),
|
|
)
|
|
def test_cftime_range_same_as_pandas(start, end, freq) -> None:
|
|
result = date_range(start, end, freq=freq, calendar="standard", use_cftime=True)
|
|
result = result.to_datetimeindex(time_unit="ns")
|
|
expected = date_range(start, end, freq=freq, use_cftime=False)
|
|
|
|
np.testing.assert_array_equal(result, expected)
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:Converting a CFTimeIndex with:")
|
|
@pytest.mark.parametrize(
|
|
"start, end, periods",
|
|
[
|
|
("2022-01-01", "2022-01-10", 2),
|
|
("2022-03-01", "2022-03-31", 2),
|
|
("2022-01-01", "2022-01-10", None),
|
|
("2022-03-01", "2022-03-31", None),
|
|
],
|
|
)
|
|
def test_cftime_range_no_freq(start, end, periods):
|
|
"""
|
|
Test whether cftime_range produces the same result as Pandas
|
|
when freq is not provided, but start, end and periods are.
|
|
"""
|
|
# Generate date ranges using cftime_range
|
|
cftimeindex = cftime_range(start=start, end=end, periods=periods)
|
|
result = cftimeindex.to_datetimeindex(time_unit="ns")
|
|
expected = pd.date_range(start=start, end=end, periods=periods)
|
|
|
|
np.testing.assert_array_equal(result, expected)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"start, end, periods",
|
|
[
|
|
("2022-01-01", "2022-01-10", 2),
|
|
("2022-03-01", "2022-03-31", 2),
|
|
("2022-01-01", "2022-01-10", None),
|
|
("2022-03-01", "2022-03-31", None),
|
|
],
|
|
)
|
|
def test_date_range_no_freq(start, end, periods):
|
|
"""
|
|
Test whether date_range produces the same result as Pandas
|
|
when freq is not provided, but start, end and periods are.
|
|
"""
|
|
# Generate date ranges using date_range
|
|
result = date_range(start=start, end=end, periods=periods)
|
|
expected = pd.date_range(start=start, end=end, periods=periods)
|
|
|
|
np.testing.assert_array_equal(result, expected)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"offset",
|
|
[
|
|
MonthBegin(n=1),
|
|
MonthEnd(n=1),
|
|
QuarterBegin(n=1),
|
|
QuarterEnd(n=1),
|
|
YearBegin(n=1),
|
|
YearEnd(n=1),
|
|
],
|
|
ids=lambda x: f"{x}",
|
|
)
|
|
@pytest.mark.parametrize("has_year_zero", [False, True])
|
|
def test_offset_addition_preserves_has_year_zero(offset, has_year_zero):
|
|
with warnings.catch_warnings():
|
|
warnings.filterwarnings("ignore", message="this date/calendar/year zero")
|
|
datetime = cftime.DatetimeGregorian(-1, 12, 31, has_year_zero=has_year_zero)
|
|
|
|
result = datetime + offset
|
|
assert result.has_year_zero == datetime.has_year_zero
|
|
if has_year_zero:
|
|
assert result.year == 0
|
|
else:
|
|
assert result.year == 1
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"offset",
|
|
[
|
|
MonthBegin(n=1),
|
|
MonthEnd(n=1),
|
|
QuarterBegin(n=1),
|
|
QuarterEnd(n=1),
|
|
YearBegin(n=1),
|
|
YearEnd(n=1),
|
|
],
|
|
ids=lambda x: f"{x}",
|
|
)
|
|
@pytest.mark.parametrize("has_year_zero", [False, True])
|
|
def test_offset_subtraction_preserves_has_year_zero(offset, has_year_zero):
|
|
datetime = cftime.DatetimeGregorian(1, 1, 1, has_year_zero=has_year_zero)
|
|
result = datetime - offset
|
|
assert result.has_year_zero == datetime.has_year_zero
|
|
if has_year_zero:
|
|
assert result.year == 0
|
|
else:
|
|
assert result.year == -1
|
|
|
|
|
|
@pytest.mark.parametrize("has_year_zero", [False, True])
|
|
def test_offset_day_option_end_accounts_for_has_year_zero(has_year_zero):
|
|
offset = MonthEnd(n=1)
|
|
|
|
with warnings.catch_warnings():
|
|
warnings.filterwarnings("ignore", message="this date/calendar/year zero")
|
|
datetime = cftime.DatetimeGregorian(-1, 1, 31, has_year_zero=has_year_zero)
|
|
|
|
result = datetime + offset
|
|
assert result.has_year_zero == datetime.has_year_zero
|
|
if has_year_zero:
|
|
assert result.day == 28
|
|
else:
|
|
assert result.day == 29
|