Repository: sktime/sktime

Issue:
[BUG] PolynomialTrendForecaster does not work with period index of frequency 30T
**Describe the bug**
PolynomialTrendForecaster does not work with period index of frequency 30T (30 mnins) due to incorrect X values 


**To Reproduce**

```python
import numpy as np
import pandas as pd
from sktime.forecasting.trend import PolynomialTrendForecaster


np.random.seed(5005)
index = pd.date_range(
    start="2020-10-01 00:00:00", end="2020-10-01 23:30:00", freq="30T"
)
data = pd.DataFrame(
    {
        "index": index,
        "target": np.random.randint(0, 255, index.shape[0], dtype=int),
    }
).set_index("index")
data.index = data.index.to_period()

forecaster = PolynomialTrendForecaster(degree=1)
forecaster.fit(data)
```

```python-traceback
 File "c:\Users\Nikhil\OneDrive\my_libraries\my_python_libraries\pycaret\pycaret_dev\time_series_debug.py", line 41, in <module>
    forecaster.fit(data)
  File "C:\Users\Nikhil\.conda\envs\pycaret_dev_sktime_0p11_3\lib\site-packages\sktime\forecasting\base\_base.py", line 265, in fit
    self._fit(y=y_inner, X=X_inner, fh=fh)
  File "C:\Users\Nikhil\.conda\envs\pycaret_dev_sktime_0p11_3\lib\site-packages\sktime\forecasting\trend.py", line 181, in _fit
    self.regressor_.fit(X, y)
  File "C:\Users\Nikhil\.conda\envs\pycaret_dev_sktime_0p11_3\lib\site-packages\sklearn\pipeline.py", line 382, in fit
    self._final_estimator.fit(Xt, y, **fit_params_last_step)
  File "C:\Users\Nikhil\.conda\envs\pycaret_dev_sktime_0p11_3\lib\site-packages\sklearn\linear_model\_base.py", line 684, in fit
    X, y = self._validate_data(
  File "C:\Users\Nikhil\.conda\envs\pycaret_dev_sktime_0p11_3\lib\site-packages\sklearn\base.py", line 596, in _validate_data
    X, y = check_X_y(X, y, **check_params)
  File "C:\Users\Nikhil\.conda\envs\pycaret_dev_sktime_0p11_3\lib\site-packages\sklearn\utils\validation.py", line 1092, in check_X_y
    check_consistent_length(X, y)
  File "C:\Users\Nikhil\.conda\envs\pycaret_dev_sktime_0p11_3\lib\site-packages\sklearn\utils\validation.py", line 387, in check_consistent_length
    raise ValueError(
ValueError: Found input variables with inconsistent numbers of samples: [1411, 48]
```

**Expected behavior**
Expected to fit correctly as it should have probably been fixed (previously reported https://github.com/alan-turing-institute/sktime/issues/1876)

**Additional context**
Originally reported here: https://github.com/pycaret/pycaret/issues/2607

**Versions**
<details>

System:
    python: 3.8.13 (default, Mar 28 2022, 06:59:08) [MSC v.1916 64 bit (AMD64)]
executable: C:\Users\Nikhil\.conda\envs\pycaret_dev_sktime_0p11_3\python.exe
   machine: Windows-10-10.0.19044-SP0

Python dependencies:
          pip: 21.2.2
   setuptools: 61.2.0
      sklearn: 1.1.1
       sktime: 0.11.4
  statsmodels: 0.13.2
        numpy: 1.21.6
        scipy: 1.8.1
       pandas: 1.4.2
   matplotlib: 3.5.2
       joblib: 1.1.0
        numba: 0.55.2
     pmdarima: 1.8.5
      tsfresh: None

</details>

<!-- Thanks for contributing! -->



Discussion:
possibly related? #2117
Might be related, but it is coming purely from `_get_duration`. It is not getting the correct X (number of points) when index is of type period index with a frequency like 30T. 

https://github.com/alan-turing-institute/sktime/blob/80b0410900dfd03e3b97383a7cbe103ebe3a1703/sktime/forecasting/trend.py#L177
hm, this looks like something that should be in or close to the `ForecastingHorizon` and not in the `TrendForecaster` anyway.

@khrapovs, due to your recent work, would you be aware of a `ForecastingHorizon` feature that could replace this easily?
@ngupta23 @fkiraly PR with the fix is coming...

Resolving patch:
diff --git a/sktime/utils/datetime.py b/sktime/utils/datetime.py
--- a/sktime/utils/datetime.py
+++ b/sktime/utils/datetime.py
@@ -6,7 +6,7 @@
 __all__ = []
 
 import re
-from typing import Tuple
+from typing import Tuple, Union
 
 import numpy as np
 import pandas as pd
@@ -14,7 +14,10 @@
 from sktime.utils.validation.series import check_time_index, is_integer_index
 
 
-def _coerce_duration_to_int(duration, freq=None):
+def _coerce_duration_to_int(
+    duration: Union[int, pd.Timedelta, pd.tseries.offsets.BaseOffset, pd.Index],
+    freq: str = None,
+) -> Union[int, pd.Index]:
     """Coerce durations into integer representations for a given unit of duration.
 
     Parameters
@@ -31,8 +34,8 @@ def _coerce_duration_to_int(duration, freq=None):
     """
     if isinstance(duration, int):
         return duration
-    elif isinstance(duration, pd.tseries.offsets.DateOffset):
-        return duration.n
+    elif isinstance(duration, pd.tseries.offsets.BaseOffset):
+        return int(duration.n / _get_intervals_count_and_unit(freq)[0])
     elif isinstance(duration, pd.Index) and isinstance(
         duration[0], pd.tseries.offsets.BaseOffset
     ):


Test patch:
diff --git a/sktime/forecasting/base/tests/test_fh.py b/sktime/forecasting/base/tests/test_fh.py
--- a/sktime/forecasting/base/tests/test_fh.py
+++ b/sktime/forecasting/base/tests/test_fh.py
@@ -255,7 +255,7 @@ def test_coerce_duration_to_int(duration):
         np.testing.assert_array_equal(ret, range(3))
 
     if isinstance(duration, pd.tseries.offsets.BaseOffset):
-        assert ret == 3
+        assert ret == 1
 
 
 @pytest.mark.parametrize("duration", DURATIONS_NOT_ALLOWED)
diff --git a/sktime/utils/tests/test_datetime.py b/sktime/utils/tests/test_datetime.py
--- a/sktime/utils/tests/test_datetime.py
+++ b/sktime/utils/tests/test_datetime.py
@@ -1,14 +1,14 @@
 # -*- coding: utf-8 -*-
 """Tests for datetime functions."""
 
-__author__ = ["xiaobenbenecho"]
+__author__ = ["xiaobenbenecho", "khrapovs"]
 
 import datetime
 
 import numpy as np
 import pandas as pd
 
-from sktime.utils.datetime import _get_freq
+from sktime.utils.datetime import _coerce_duration_to_int, _get_freq
 
 
 def test_get_freq():
@@ -34,3 +34,27 @@ def test_get_freq():
     assert _get_freq(x2) == "W"
     assert _get_freq(x3) is None
     assert _get_freq(x4) is None
+
+
+def test_coerce_duration_to_int() -> None:
+    """Test _coerce_duration_to_int."""
+    assert _coerce_duration_to_int(duration=0) == 0
+    assert _coerce_duration_to_int(duration=3) == 3
+
+    duration = pd.offsets.Minute(75)
+    assert _coerce_duration_to_int(duration=duration, freq="25T") == 3
+
+    duration = pd.Timedelta(minutes=75)
+    assert _coerce_duration_to_int(duration=duration, freq="25T") == 3
+
+    duration = pd.to_timedelta([75, 100], unit="m")
+    pd.testing.assert_index_equal(
+        _coerce_duration_to_int(duration=duration, freq="25T"),
+        pd.Index([3, 4], dtype=int),
+    )
+
+    duration = pd.Index([pd.offsets.Minute(75), pd.offsets.Minute(100)])
+    pd.testing.assert_index_equal(
+        _coerce_duration_to_int(duration=duration, freq="25T"),
+        pd.Index([3, 4], dtype=int),
+    )